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:
EC2 Instance Pricing — Amazon Web Services (AWS)
With On-Demand instances you only pay for EC2 instances you use. The use of On-Demand instances frees you from the…aws.amazon.com
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:
AWS | Amazon Linux AMI
The Amazon Linux AMI includes packages and configurations that provide tight integration with Amazon Web Services. The…aws.amazon.com
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](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.
Managing deploy keys
There are four ways to manage SSH keys on your servers when automating deployment scripts: SSH agent forwarding HTTPS…developer.github.com
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):
- Cache view templates.
- Cache CSS files generated from CSS extensions.
- Generate less verbose error messages.
Read more here:
Performance Best Practices Using Express in Production
This article discusses performance and reliability best practices for Express applications deployed to production. This…expressjs.com
There are a lot more options in pm2 that you can tweak, I am leaving those at default values. Check them out here:
Ecosystem File · PM2
Advanced process manager for production Node.js applications. Load balancer, logs facility, startup script, micro…pm2.keymetrics.io
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:
Cluster Mode · PM2
The cluster mode allows networked Node.js applications (http(s)/tcp/udp server) to be scaled accross all CPUs…pm2.keymetrics.io
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.
Article Photo by Jonathan Gallegos