Deploying Node.js apps in Amazon Linux with pm2
By: Tanveer Hassan
Tags: Software Engineering, AWS, Programming, Nodejs, JavaScript

Running a Node.js application can be as trivial as node index.js, but running it in production and keeping it running are completely different. Whenever the application crashes or the server reboots unexpectedly, we want the application to come back alive.

There are several ways we can properly run a Node.js application in production. In this article, I will be talking about how to deploy one using pm2 in an AWS EC2 instance running Amazon Linux.

AWS EC2

Spin up an EC2 instance of your liking. Consider the load your server will be going through and the cost. Here you can get a pricing list for different types of instances:

Choose Amazon Linux AMI. This is a free offering from Amazon.

The Amazon Linux AMI is a supported and maintained Linux image provided by Amazon Web Services for use on Amazon Elastic Compute Cloud (Amazon EC2). It is designed to provide a stable, secure, and high performance execution environment for applications running on Amazon EC2. It supports the latest EC2 instance type features and includes packages that enable easy integration with AWS. Amazon Web Services provides ongoing security and maintenance updates to all instances running the Amazon Linux AMI. The Amazon Linux AMI is provided at no additional charge to Amazon EC2 users.

Learn more at:

Server configuration

After the instance is up and running, SSH into it, preferably using a non-root account.

Update packages:

sudo yum update -y

Install necessary dev tools:

sudo yum install -y gcc gcc-c++ make openssl-devel git

Install Node.js:

curl --silent --location https://rpm.nodesource.com/setup_10.x | sudo bash - sudo yum install -y nodejs

This will install version 10 of Node.js. If you want to install a different version you can change the location.

We will run our application using pm2. Pm2 is a process manager for Node.js. It has a lot of useful features such as monitoring, clustering, reloading, log management, etc. I will discuss some of the features we will use and configure in our application.

The features I find most noteworthy:

Clustering — runs multiple instances of an application (depending on configuration, in our case we will use number of cores to determine this) Reloading — reloads applications when they crash or the server reboots.

Install pm2:

sudo npm install pm2@latest -g

Generate a pm2 startup script:

pm2 startup

This will daemonize pm2 and initialize it on system reboots.

Learn more here:

https://pm2.keymetrics.io/docs/usage/startup

The source code

You can use https to clone the source code. However, I find that using a deploy key is much better and I can give read-only access to the server.

Here is a simplified way of how to generate and use deploy keys:

Generate a new ssh key using:

ssh-keygen

Do not enter a passphrase.

Copy the public key contents printed by the command:

cat ~/.ssh/id_rsa.pub

If you are using Github, add it to the Deploy Keys section of your repository’s Settings page.

After the repository is cloned. Run the scripts you need to run in order to get your project ready.

For example, if my project uses yarn as the package manager and typescript as the language which needs to be transpiled to javascript when deploying, I will run the following commands:

yarn install yarn build

The second command runs the build script from my package.json file which states: “build”: “tsc”

We can now run the application by running:

node dist/index.js

But we are not going to. Because we want to use pm2 to run our application.

The Ecosystem File

Pm2 provides a way to configure our application in an ecosystem file where we can easily tune the various configurable options provided.

You can generate an ecosystem file by running:

pm2 ecosystem

Our application’s ecosystem file contains:

ecosystem.config.js:

module.exports = {

apps : [{

name: ‘My App’,

script: ‘dist/index.js’,

instances: ‘max’,

max_memory_restart: ‘256M’,

env: {

NODE_ENV: ‘development’

},

env_production: {

NODE_ENV: ‘production’

}

}]

};

What this configuration tells pm2 is, run the application and name it My App. Run it using the script dist/index.js. Spawn as many instances of the application according to the number of CPUs present.

Mind the NODE_ENV environment variable. This has several benefits when running an express application. It boosts the performance of the app by tweaking a few things such as (Taken from express documentation):

1. Cache view templates.

2. Cache CSS files generated from CSS extensions.

3. Generate less verbose error messages.

Read more here:

There are a lot more options in pm2 that you can tweak, I am leaving those at default values. Check them out here:

Run the application:

pm2 reload ecosystem.config.js --env production

This command reloads the application with production environment declared in the ecosystem file. This process is also done with zero downtime. It compares the ecosystem configuration and currently running processes and updates as necessary.

We want to be able to write up a script for everytime we need to deploy. This way, the app is not shut down and started again (which a restart does).

Read more about it:

When our application is up and running, we have to save the process list we want to respawn for when the system reboots unexpectedly:

pm2 save

We can check our running applications with:

pm2 status

Monitor our apps:

pm2 monit

View logs:

pm2 logs

Let’s create a handy script to deploy when there is a change:

deploy.sh:

#!/bin/bash git pull

yarn install

npm run build

pm2 reload ecosystem.config.js --env production # EOF

Make the file executable:

chmod +x deploy.sh

Now, every time you need to deploy changes, simply run:

./deploy.sh

Conclusion

Let’s recap:

Create an EC2 instance running Amazon Linux

Update packages (might include security updates).

Install the desired Node.js version.

Use a process manager to run the application (such as pm2).

Use deploy keys to pull code from the source repository.

Create an ecosystem configuration file so that it is maintainable in the future.

Create a deploy script so that it is easy to run future deployments.

Run the deployment script whenever there is a change to be deployed.

Congratulations! Your application is up and running.

There are several other ways to achieve the same end goal, such as using forever instead of pm2, or even using Docker instead and deploy to Amazon ECS. This is a documentation of how I deploy Node.js applications in production if running them on EC2 instances.

When your deployments become more frequent, you should consider a CI/CD integration to build and deploy whenever there is a change in the source code.

Make sure you monitor and keep an eye on your server’s resource usage.

Last but not least, make sure you have proper logging in your application. I cannot stress enough how important proper logging is.

Tweet me at @war1oc if you have anything to ask or add.

Check out other articles from our engineering team:

https://medium.com/monstar-lab-bangladesh-engineering

Visit our website to learn more about us:

www.monstar-lab.co.bd