DEV Community

Tayyib Cankat
Tayyib Cankat

Posted on • Updated on • Originally published at t410.me

How to CI/CD using PM2 for your Node.js project

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
Enter fullscreen mode Exit fullscreen mode

Then we initialize the folder as a node project.

npm init -y
Enter fullscreen mode Exit fullscreen mode

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

npm i express
Enter fullscreen mode Exit fullscreen mode

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

Here's my index.js

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

In index.html we do nothing special.

Now we can use

npm start
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

PM2 deploy success output
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Conclusion

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

Here's the sample Repo:

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.

Top comments (2)

Collapse
 
ocalde profile image
Oscar Calderon

This is really interesting, thanks!! I like to play with a very small VM I have in Vulture, and I wanted something really lightweight for CD.
However there's something I don't understand. It seems it is assumed that the ecosystem file will be in your local computer. In my case, I have the ecosystem file in the VM itself along with my apps. Do I still need to configure the ssh key for it to deploy in itself? Or is there some alternate configuration to tell it to deploy locally instead of in a remote server?

Collapse
 
t410 profile image
Tayyib Cankat

Thank you for your interest. Well, the ecosystem file contains the necessary information along with instructions for the machine that the app will run on. So if you want your VM to run the app with PM2, and no CD you can of course use ecosystem file without SSH configurations.

If you want to deploy the app and then run it on a remote VM without connecting to that VM from your local and physical machine then you can configure SSH information and PM2 will automate the connection, pulling from git, npm installing, running the app steps.

So you are letting PM2 do the deploying and running footwork. To deploy, PM2 needs an SSH key to connect to the remote machine. That SSH configuration part is essential for CD operations.

I hope this clarifies your question.