Migrate Node.js App from Heroku to Digital Ocean Ubuntu 20.04 with Nginx, Pm2, SSL
Tags: devops, node, digitalocean, deployment
Published: 2020-07-24 19:55:24

[Heroku](https://www.heroku.com/) has been a really great service for developers that don't want to spend time on infrastructure. I've uploaded all my toy projects there so far, because it's faster to make them available online, and I can focus more on learning and doing things I like. However, there comes a time when I want to learn more about "behind the scene", so I want to experiment with Digital Ocean, since so many people already mentioned it. Surprisingly not many tutorials were written for what I need in details, so after quite some googling, asking around and experimenting myself, I finally got it working. 😂

Are you ready?

![I am ready](https://media.giphy.com/media/CjmvTCZf2U3p09Cn0h/giphy.gif)

## 1. Sign up for a Digital Ocean account

First you need to sign up an account. [Click Here](https://m.do.co/c/49a33d4c6eea), it is an affiliate link which will give you **$200 for FREE for 60 days**. How cool is that! 😉 You can use this period to experiment and learn without any cost. Note you do need to sign up with a credit card so you can be verified.

## 2. Set up the droplet 
Next to your avatar on the top right corner, you will find a bright green button says **Create**. Click on it and then click **droplets**. And you will see this page:
![Digital Ocean Droplets](https://dev-to-uploads.s3.amazonaws.com/i/d0erypyrgau76zlza08b.jpg)
Here we choose **Ubuntu 20.04 LTS, Shared CPU Basic, $5/Month plan**. Of course feel free to choose what suits you the best!

Scroll down and choose a **data center** that's near your target audiences, then select additional options, check **IPv6** and **monitoring**.  

I like to use **ssh** as authentication, so you can generate a key. I am sure there is a lot of tutorials online for this already. I also suggested you choose a name for your droplets. Depending on you, you can choose to have backup or not. After all that, click the green button **Create Droplet**. Wait a few seconds, your droplet is ready! You will be able to copy the ip address now, and from now on, we will leave Digital Ocean interface and do things in the terminal. 

## 3. Login and update your ubuntu system 

Now open a terminal and type 
```
ssh root@youripaddress
```
Note normally you shouldn't do stuff with root. More power more responsibility! You might mess up things by accident, so it's better to create a user. For simplicity I will continue with root for now. Note when you login as user later, you gotta make sure to give user enough right to write some files, such as some nginx conf files. I will paste some read further links at the end.

It will ask you if you want to continue, then type yes. Then you will see root@DropletName:~#. 

Let's update now. Upgrade is optional. Some people don't do it at all, it's up to you.
```
sudo apt update
sudo apt upgrade
```

## 4. Let's install Node.js

In the terminal type: (This is the latest version so far but you can replace 14 with 12 if you want stable version)
```
curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt install nodejs
node -v
```

When you see output of node version number like 
```
v14.6.0
```

We have successfully installed node on our system.YEAH!

## 5. Let's move our repo to our droplet

Since we already have a node.js app on github, we can easily git clone it here. Let's first create a folder called Apps for all our apps. 

```
mkdir apps
cd apps 
git clone git clone https://github.com/username/repo.git
ls -a
```
Now you should see something like this
```
.  ..  repo
```

If the output is your repo's name, we successfully copied our repo here. 

## 6. Install dependencies and test run

Now first let's go into your repo here
```
cd repo
npm install
npm start npm 
```
Now go to the ipaddress:3000 (or whatever port you're running on)

can you see your app running? If so, well done! 👍 Press Ctrl + C to stop as we still have a lot to do. 

## 7. Set up Pm2 to keep your app running

Now let's install pm2 globally. You can read more about pm2 [here](https://pm2.keymetrics.io/docs/usage/pm2-doc-single-page/).

```
sudo npm install pm2@latest -g
pm2 start app 
```
For instance my app's file is in src, so I cd to my apps/repo then run 
```
pm2 start src/index.js
```
Something like this shows up, and you should still be able to see your ipaddress:3000 (or whatever port you use) in your browser to see your app running. 
![pm2 run successfully](https://dev-to-uploads.s3.amazonaws.com/i/mzafiavwz7fvj7pndbd0.png)

To make sure your app starts when reboot you can type:
```
pm2 startup ubuntu
```

## 8. Set up firewall to block port 

When you type 
```
sudo ufw status
```
You will probably see Status:inactive. Now let's enable it
```
sudo ufw enable
sudo ufw status
```
Now it should say Status: active. Let's allow a few ports.
```
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
```
ssh is port 22, http port 80, https port 443.

## 9. Set up and configure Nginx as reverse proxy 

```
sudo apt update
sudo apt install nginx
sudo ufw allow 'Nginx HTTP'
sudo ufw status
```
Now you should see something like this:
![ufw status](https://dev-to-uploads.s3.amazonaws.com/i/8gk6q5om02hr6q7y0yfp.png)


Now we can go to the http://ipaddress, (note we don't need to type port any more) and you shall see the following.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7ezi52uai7dkxwp1pkpx.png)
That means you're doing right so far!

Now here comes the tricky part. Since I plan to host more toy projects within one droplets, which will also point to different domain names (or subdomain names). We'd better create something extra, instead of modifying the default. 

Let's say we're going to serve the current app at a.example.com. First let's create something like this:
```
sudo mkdir -p /var/www/a.example.com/html
nano /var/www/a.example.com/html/index.html
```

Then we paste those simple html here, it won't show up so don't need to make it pretty. 
```
<html>
    <head>
        <title>Welcome to a.example.com!</title>
    </head>
    <body>
        <h1>Success! a.example.com server block is working!</h1>
    </body>
</html>
```
To save you press Ctrl + X, yes, then press enter. Now we have this file to refer to later in our another conf file.

First let's create something like :
```
sudo nano /etc/nginx/sites-available/a.example.com
```
copy and paste the following to that

```
 server {
     listen 80;
     listen [::]:80;

       server_name a.example.com;

       root /var/www/a.example.com/html;
       index index.html;

       location / {
        proxy_pass http://localhost:3000; #whatever port your app runs on
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
       }
}

```
Ctrl + X, yes, press enter. 

Now that we have our server block file, we need to enable it. We can do this by creating symbolic link from this file to the sites-enabled directory, which Nginx reads from during startup. Later when we add more apps and more domains names, we can copy the same steps.

We can create these links by typing:
```
sudo ln -s /etc/nginx/sites-available/a.example.com /etc/nginx/sites-enabled/
```

To avoid a possible hash bucket memory problem that can arise from adding additional server names, it is necessary to adjust a single value in the /etc/nginx/nginx.conf file. Open the file:
```
sudo nano /etc/nginx/nginx.conf
```
Find the **server_names_hash_bucket_size 64** directive and remove the # symbol to uncomment the line.

Now let's see if our setting is ok. Type
```
sudo nginx -t
```
If you see those lines, then you're successful.
```
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```
Now we can restart Nginx.
```
sudo service nginx restart
```
## 10. Set your domain/subdomain name

You need to set an A record. Since I manage my domain name at Netlify, it's relatively easy to do. For instance, I want the domain to sound like meowlo.annietaylorchen.com, so I set it up as below. 
![set subdomain](https://dev-to-uploads.s3.amazonaws.com/i/ak9rz24keumw38hz5380.png)
Note it can take up to 24 to 28 hours for your domain name to propagate, so you need to be a little patient at this point.

Use [whatsmydns](https://www.whatsmydns.net/) to check if your domain name is available or not.

## 11. Set up environment variables 

If you're using some api keys, we need to set up environment variables. Since I already use dotenv in my node.js app, I will do as follow. I don't know whether this is the best practice, but it somehow worked for me. 

Since we copied our git from github and naturally we didn't push our .env file there, now on our Digital Ocean droplet we also don't have that. But we need to use it when running our apps. 

Now cd to your app folder, and type 
```
touch .env
nano .env
```
Now you copy your api keys like 
```
API_KEY=49upogjergeu
```
then Ctrl + X, yes, press enter to save. Now you type 
```
cat .env
```
You should be able to see them showing up in your terminal. Ok, now make sure when you use dotenv you write something like 
```
if (process.env.NODE_ENV == 'production'){
    require('dotenv').config()
}
```
and in your npm script it's sth like:
```
"start": "NODE_ENV=production node src/index.js",
```
Now let's inform pm2 about this 
```
NODE_ENV=production pm2 restart src/index.js --update-env
```
Now your apis should be working! 😺

## 12. Add SSL with LetsEncrypt 
Now let's go back to our home directory.
```
cd
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d a.example.com
sudo certbot renew --dry-run
```
Simply follow the instruction they prompt in the terminal. If you don't see any error, we're all set. Now refresh your browser, you should see your a.example.com serving your apps, api working, and it's secure too! Pfew! That's a lot, I know! Congrats if you make it to the end. Let's high five!🙌

![happy dance](https://media.giphy.com/media/iPTTjEt19igne/giphy.gif)

Last but not least, I have made it as short as possible. There are quite some details I didn't cover, but I think if you have time you should read further later. 

1. [Full Node.js Deployment - NGINX, SSL With Lets Encrypt by Brad Traversy](https://www.youtube.com/watch?v=oykl1Ih9pMg) - note this is not the latest version I used, but many concepts are the same.
2. [How To Install Git on Ubuntu 20.04](https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-20-04) - ubuntu usually comes with git already, but you can set up with your own name and email etc.
3. [How To Set Up Automatic Deployment with Git with a VPS](https://www.digitalocean.com/community/tutorials/how-to-set-up-automatic-deployment-with-git-with-a-vps) - teaches you how to commit from local repo through git, and how to set up beta version 
4. [How To Set Up a Node.js Application for Production on Ubuntu 20.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-20-04) - general guide for node.js app 
5. [How To Install Nginx on Ubuntu 20.04](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04) - general guide for nginx
6. [How To Set Up Nginx Server Blocks (Virtual Hosts) on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-16-04) - if you want to serve different sites on the same droplets this is useful
7. [How To Secure Nginx with Let's Encrypt on Ubuntu 20.04](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04) - how to add SSL to your site
8. [How To Install and Use PostgreSQL on Ubuntu 20.04](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-20-04) - my simple node.js app doesn't require a database, but you will probably need one