How to CI/CD using PM2 for your Node.js project
Tags: tutorial, pm2, node, cicd
Published: 2021-02-15 20:06:02

## Why?
You have a Node.js project in your local machine but you don't know how to deploy it to your remote server or you use old fashioned way by copying the contents from your computer to the remote server using FTP?

Well, you can automate this process and make your life easier using PM2 \*insert hooray gif here\*

## What?
PM2 is a Process Manager for Node.js. It's like Task Manager in Windows and Activity Monitor in macOS. 

You can *-including but not limited to-* manage your application, scale, start and stop. But the most important feature we want is **deploying**.

In this post, we will learn how to deploy our application to our remote server and run/build it with a single console command.

## How?

### Step 1: Create a Project
First, we obviously need a project.
We create a folder and cd into it.

```
mkdir pm2-deploy; cd pm2-deploy
```
Then we initialize the folder as a node project.

```
npm init -y
```

We can then go ahead and install `express` to serve static files in node environment.

```
npm i express
```

And we need to create a JS file to write our code that will serve the folder `public` which we also need to create.

I have created `index.js` in the root directory. You can rename it whatever you want but don't forget that you need to change the `main` field in the `package.json` file also.

We also need an HTML file to be served in that public folder.

Your file structure now looks like this:
![File structure](https://dev-to-uploads.s3.amazonaws.com/i/job8p4e8o9u3bimaxdrg.png)

Here's my index.js
{% gist https://gist.github.com/T410/5e8322dde47b2a28e3062fa5d42d81a7 %}

express is serving a static folder named `public` and its contents in port 3000; Nothing fancy here.

{% gist https://gist.github.com/T410/351b594bdd6f61f1ba84fa05c9f78fee %}

In `index.html` we do nothing special.
{% gist https://gist.github.com/T410/38599f88d279a302bc9eb68cc07a4a40 %}

Now we can use 
```
npm start
```
We should see the console.log output which is `PM2 Project is now live @ localhost:3000`.

We can check if that's working by going to that port. Go to `localhost:3000` in the browser, If you see `YAY!` that's great.

### Step 2: Install PM2 globally
We need to install PM2 npm package globally. We can install it by using 
```
npm i -g pm2
```

Now onto Step 3!

### Step 3: Initialize git
We cannot have a CI/CD without a version control system, right? So we need to push our project to a git service. I will use Github for that. 

When you create a git repo you will see the necessary instructions on how to push an existing project.
But here are the necessary commands, just in case:
```
git init
git remote add origin git@github.com:<your_github_username>/<your_repository_name>.git
git add .
git commit -m "Initial Commit"
git branch -M main
git push -u origin main
```
> Note: I strongly recommend using SSH Connection instead of using HTTPS for Github. Your life will get better and securerer :)

### Step 4: Configuring the remote machine
In this step, I won't go into the details of how to create/reserve a virtual remote machine but keep in mind that I am using Ubuntu on an EC2 (AWS) machine.

First, we need to connect to the remote machine using SSH

`ssh -i path_to_key_file remote_username@remote_ip`

I assume you have already done nvm, npm installations, if not you can go ahead and check the nvm repo here: https://github.com/nvm-sh/nvm#installing-and-updating

One important thing to do here. We need to move the lines that were added by nvm to our .bashrc file to the top. Your system may be using .bash_profile or something else. Just pay attention to the output of the nvm installation.

These are the lines we need to move. Open your favorite editor and move them to the top of the file.
```
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
```

After saving and exiting the file we can install PM2 here too as we did in Step 2.
```npm i -g pm2```

After the installation
```pm2 startup```
will give you a simple instruction on how to make PM2 start automatically every time your remote system reboots. I strongly recommend doing that.

Now that we installed PM2, we need to create an SSH key and add it to Github.

In the remote machine, you can go ahead and type
```
ssh-keygen -t ed25519 -C "<your_github_email>"
```
>Further reading on how to create ssh key https://docs.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent

The keygen will ask you the name of the key. If you want to change it (**_I strongly advise you not to do that_**) you need to give the full path here.

You may just hit Enter when asking for password.

After creating the key we need to copy the contents of the public key.
```
cat /home/ubuntu/.ssh/id_ed25519.pub
```
Go ahead and copy the text you see starting with `ssh-` and ending with your e-mail (included).

Then go to https://github.com/settings/keys while logged in to Github then click `New SSH Key` button. You can give a title and paste the copied text into the key field.

We now have given authorization to our remote machine to connect to our Github. But we need to connect to Github *just once* to mark the connection trusted in our remote machine. To do that we can clone the repository into the remote machine.
```
git clone git@github.com:T410/pm2-deploy.git
```
Of course, it will be your username and your repo name.
The console will ask you if you want to continue connecting. Type `yes` end hit Enter.

> Note: We cloned the repo into the user folder. /home/ubuntu for me. Now the full project path is `/home/ubuntu/pm2-deploy`. We will use this path to update the `ecosystem.config.js` file in the next step.

And now we are good to close the remote connection to the server.

### Step 5: Configuring the ecosystem.config.js file

Now that we have a remote server up&running and have already pushed the project into our repository, we need to properly configure the `ecosystem.config.js` file to tell PM2 where our project is, what to do with that, and where to push that.

The file will look like this:

{% gist https://gist.github.com/T410/5e20e39159bea8c15df78078fc030b7c %}

Notice there are 2 sections we are exporting: 
* apps
* deploy

The `name` field in the `apps` section is the name of our project which will be shown in PM2 process list.

The `script` field is the script that PM2 will run when we deploy the project to the remote server. In this case, it will be the same as the `main` field in the `package.json` file.

The rest of the fields are pretty self-explanatory.
* `user` is the username that you use to connect to the remote server using SSH
* `host` is the IP of the remote server
* `path` where do you want your project to be deployed in your remote server? Remember we already cloned the repo into our remote server. We can go ahead and write that path here
* `repo` is the repository URL in a format like `git:github.com:/user/repo.git`
* `ref` is the reference branch. Which branch we want the remote server to pull
* `key` is the **LOCAL PATH** of the key that we use to connect our machine using SSH
* `"post-deploy"` takes commands which will be run at the **remote machine** after pulling the repo from Github

## Step 6: Deploying
We have configured our machine and PM2. We can now deploy our project to the remote machine.

Before deploying we need to commit and push the changes we have made. After that, for the first run, we need to tell PM2 that it needs to setup the project.

```
pm2 deploy ecosystem.config.js production setup
```

With this command PM2 connects to the remote, clones the repo from Github. We can now deploy the project.

```
pm2 deploy ecosystem.config.js production
```
![PM2 deploy success output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4llhqxtaapopfwy3rgr.png)
Yay!

Now you are asking yourself: Now what?
Well, we didn't set up a server like nginx but we can test if the project is working or not with `curl`. Of course, we need to connect to the remote machine before doing that.

```
curl http://localhost:3000
```

If you see the index.html output on the screen that's great news! You have done it!

And also you can list the apps PM2 running with this command
```
pm2 ls
```

## Conclusion
We made great progress here. We learned how to deploy our project with just one command.

Here's the sample Repo:
{% github T410/pm2-deploy %}

I know I didn't tell you how to install nginx and serve the port we are using for our project but I will definitely do that in the near future and update here.

If you have any issues with anything, feel free to tell me what's wrong on the comments section.