How to deploy a MERN stack application manually

Published: 2021-12-03 | Last Updated: 2022-01-12 | ~5 Minute Read

Table of Contents


I have been working on an application recently which is based on the MERN stack and I’m now reaching a point where I would like to deploy it.

Upon looking for references online I noticed that deploying to cloud services is all the craze and with good reason, it’s fast and simple to do. As part of my learning experience I would like to experience a manual deployment to a server. I will document that process here.


I will be deploying this to a Digital Ocean droplet. I used Ubuntu as the host OS for this deployment, below are the server versions:

root@byp:~# cat /etc/lsb-release 
    DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"

Base Software

Once the ssh access configuration and general system updates were done, I installed the following packages:

# apt install nodejs npm mongodb nginx

That should be all needed for a basic deployment, on to the application deployment.

I then added a user to the system:

    root@byp:~# adduser mernuser
    Adding user `mernuser' ...
    Adding new group `mernuser' (1000) ...
    Adding new user `mernuser' (1000) with group `mernuser' ...
    Creating home directory `/home/mernuser' ...
    Copying files from `/etc/skel' ...
    New password: 
    Retype new password: 
    passwd: password updated successfully
    Changing the user information for mernuser
    Enter the new value, or press ENTER for the default
            Full Name []: 
            Room Number []: 
            Work Phone []: 
            Home Phone []: 
            Other []: 
    Is the information correct? [Y/n] y
    root@byp:~# usermod -aG sudo mernuser

MERN App deployment

For this specific application I did not use a public git repository, so I will be transferring the files manually, it’s just for testing so I won’t be doing continuous deployment:

scp -r mernapp/

I removed the node_modules folders where appropriate so that we don’t transfer all that data, we’ll just rebuild once the files are on the target server.

Once the files are on the server from the mernapp folder I ran the following so that all the package.json dependencies are installed locally:

npm install

In my case I have a package.json in two locations, the /frontend folder and the root / of the folder so I had to do this inside each directory to install the corresponding dependencies.

Then I did the production build of the front end:

npm run build

With this the application should be ready to be served.

Preparing NodeJS

The previous steps should leave the front end ready, now for the backend:

The following code will set the node server to run in production mode:

// Production build
if (process.env.NODE_ENV === "production") {
        app.use(express.static(path.join(__dirname, "/frontend/build")));

} else {
        app.get("/", (req, res) => {
                res.send("API is running...");

Starting NodeJS

I then installed a process manager for node pm2. It was fairly straight forward to use:

// Install the pm2 process manager
npm install -g pm2

// Switch to the user that owns the files on your server to start the service
su - mernuser 
pm2 start "npm start"

// You can see the status with the following
pm2 list

// In case you need to look at the execution logs in real time
pm2 logs

Once I had the app running, pm2 list reported:

mernuser@host:/var/www/html/mernapp# pm2 start "npm start"
[PM2] Starting /usr/bin/bash in fork_mode (1 instance)
[PM2] Done.
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
│ 1  │ npm start          │ fork     │ 0    │ online    │ 0%       │ 3.4mb    │

Web Server

I will be using Nginx as a web server, first the base configuration:

server {
        listen 80;
        listen [::]:80;
        root /var/www/html/mernapp/frontend/build/;

        index index.html index.htm index.nginx-debian.html;

        location /api {
            proxy_pass http://localhost:5000;

        location / {
            try_files $uri /index.html;

This configuration shows the app publicly now.

SSL with Let’s Encrypt

In order to enable SSL on the site I simply followed the instructions provided on the official certbot page for my specific configuration and executed the following command:

certbot --nginx

That came back with a successful setup and just to test renewal:

sudo certbot renew --dry-run

At this point I was able to visit my app online over HTTPS.

Closing Thoughts

Overall the process is pretty straight forward but I suppose the cloud explosion has provided additional tools and services that simplify the process even more. For simple testing like this though I think deploying like this is a bit simpler.

Have a comment on one of my posts? Start a discussion in my public inbox by sending an email to ~grokkingnix/ [mailing list etiquette] [mailing list archive]

Posts from blogs I follow:

Introducing a Falkon extension RSS Finder

This weekend I decided to semi automate the process of searching for RSS feeds on websites while using Falkon web brosers. Many websites provide RSS feeds but do not provide any visible link or icon to access them (eg. many Wordpress based sites) and I ha…

via My land January 23, 2022
Help Chile write free software values, privacy, and digital sovereignty into their constitution

For those out of the loop, a group which included myself up until recently,1 Chile is in the midst of a revolution. They’re fighting against the increased cost of living, privatization of essential services, and worsening inequality — problems facing everyon…

via Drew DeVault's blog January 19, 2022
A warning to business owners and managers, you are a big part of the problem!

In my last couple of articles, mainly So-called modern web developers are the culprits and Is the madness ever going to end? I have written about some of the major problems with so-called modern web development and I have addressed the issues to the devel…

via January 13, 2022

Generated by openring