Docker, NodeJS/Express, PM2 and the 12 Factor App

Last week I wrote an article on how to use Angular and Docker to keep your configuration outside of your code. Keeping you configuration outside of your code and keeping your environments as similar as possible are 2 very important parts of keeping your app 12 Factor App compliant. So today I will look at doing the same but with NodeJS and docker.

Why Docker?

Node can be flaky and managing its versions and the effects of them can be a tricky task. By pulling a specific version via a docker image can limit the amount of issues. I have seen it in previous projects that 2 developers on the same project were running different versions of node and getting slightly different results because of that.

By committing to a version of node and having the docker image manage that we can now have confidence that our app will work exactly how we want it to wherever we deploy it to.

This works the same way for dependencies as well. package-lock.json has helped a lot in regards to limiting the differences of package versions installed when building. But I don't like leaving things to chance. So if we build this docker image, we know nothing will change apart from the configuration files we pass into it.

Why PM2?

Express recommends using a process manager when running in production:

In development, you started your app simply from the command line with node server.js or something similar. But doing this in production is a recipe for disaster. If the app crashes, it will be offline until you restart it. To ensure your app restarts if it crashes, use a process manager. A process manager is a “container” for applications that facilitates deployment, provides high availability, and enables you to manage the application at runtime. Source

I use and recommend PM2. Firstly, the configurations are great. It also has a specific container command pm2-runtime.

Using PM2 as a layer between the container and the application brings PM2 powerful features like application declaration file, customizable log system and other great features to manage your Node.js application in production environment. Source

I will hopefully explore some more PM2 features in future articles.

A Very Simple Example

First, lets create a nodeJS script that simply takes an environment variable and prints it to the screen. If no message is present then we will simply print 'No Message'.

Install express npm install express --save.

Create a file called index.js and add the following code.

const express = require('express');

const app = express();

app.get('/', (req, res) => {
  const message = process.env.MESSAGE ? process.env.MESSAGE : 'No message';


const server = app.listen(3000, () => {
  console.log('App listening on port', server.address().port);

If we run node index.js and navigate to localhost:3000 you will see we have a simple html page with the h1 tag saying 'No message'.

The simplest way of now pushing an environment variable/header message into node would be to run MESSAGE='Hello World' node index.js.

For any project that is above hobby status, due to the reasons mentioned above, this isn't good enough... And making it more stable is really easy. So let's add Docker and PM2.

Let's install PM2 npm install pm2 --save

Create a file called Dockerfile and add the following:

FROM node:12.7-slim
WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install --production

COPY index.js .
CMD ["pm2-runtime", "index.js"]

This container is lightweight and to the point. We get the node image (FROM node:12.7-slim), copy across our package.json (COPY package*.json ./) so we know what dependencies to install, we install them (RUN npm install --production) and then copy out script and declare what command (CMD ["pm2-runtime", "index.js"]) we want to run it with.

Now we can build docker docker build -t nodejs-12-factor .

Run docker docker run -e MESSAGE="12 Factor Rocks" --publish 8080:3000 nodejs-12-factor:latest

Now navigate to http://localhost:8080/ and you will see the marvelous "12 Factor Rocks" title.


Its as simple as that, we can now move forward and manage our db strings, passwords and other sensitive configuration information without it being baked into the code.

We know our code and dependecies will always be the same across all environments, its just the configuration that changes... Meaning no surprises.

We also have a great process manager that will keep node in control for us.

I will delve deeper into these subjects soon :-)

Full demo:

Comments (1)

Farhana Yasmin's photo

Thank you for this article it was really helpful.