How to use Docker with Node.js: A Step-by-Step Tutorial
By: Geshan Manandhar
Tags: Nodejs Tutorial, JavaScript, Docker Compose, Docker, Nodejs

Both Docker and Node.js have risen in popularity in the past 5 years. Running Node.js on docker containers with docker-compose for local development is a great experience.

In this step-by-step tutorial, we will look at how Node.js docker and docker-compose with multi-stage docker build work in sync. Time to get cracking.

Docker the new norm

Docker has changed the way we software engineers work in the past 5–7 years.

Containers have made it easier to ship the whole stack including the OS, not just the code.

There is more than one reason to use docker everywhere, especially in the development environment. Docker was the second most loved platform in the Stack Overflow Survey 2020 same as 2019. The same survey 2020 edition also quotes

“We also see some year over year growth in the popularity of container technologies such as Docker and Kubernetes.”

Docker is used by 35% of all respondents and 39.2% of professional developers. It is safe to say 1/3rd or more software engineers are using docker. In 2020 and beyond, if you are not using docker as a developer you are missing out for sure.

Node.js on Docker with high scalability

Node.js was initially released in 2009. It has been used for high traffic web applications by big companies like Paypal, Netflix, eBay, and LinkedIn to name a few. It has surely been battle-tested in the past 10 years and has proven its mettle. It also works well with a big team where Spotify is an example. It was used by 90 teams and 600 developers at Spotify. As per W3tech, 1% of websites use Node.js.

1% of the websites tracked by W3tech might seem a small number, but Node.js is popular in websites with high traffic as mentioned above.

With high scalability in the picture, it becomes a lot easier to scale the application horizontally with Docker and Kubernetes. Using Docker with Node.js enables high scalability.

Assumptions for Docker with Node.js

You have some familiarity with using Node.js (express js or any other framework) You have some experience using Docker (local development, production environments preferred) I am using Docker version 19.03.13 and docker-compose version 1.27.4, I hope you have similar versions. For the first part of this tutorial, we will use Node.js 8 + with npm and npx installed.

Steps

Let’s dive deeper into the step-by-step details of this tutorial on how to run a Node.js express demo application on docker with docker-compose.

1. Setup express with express-generator

As the first step, it is time to set up a bare-bones Node.js express application. To generate the express js application we will use the express application generator.

1.1 Use express-generator to scaffold the app

To generate your demo Node.js express application for docker with Node.js, execute the following commands:

Notice that we are generating a web app not an API and using pug as the templating engine for the views. It should give you an output like below:

Now, let’s run the app to see how it looks on the browser, no Node.js on docker yet. To start the Node.js express application, please run the following commands:

After that, you should see something like below:

Hit http://localhost:3000 on a browser like Chrome to check if the app is running correctly. You should see something similar to this:

When you hit the homepage on the browser, you will also see some logs on how long the request took to respond on the command line.

At this point, I have added it to git, if you want to view the code it is in this pull request.

1.2 Edit the index to see the changes

You can edit the page to say something different like Express on Docker and Let's get started or something of that sort. To do this we will need to edit 2 files, /routes/index.js and views/index.pug , like below. You can get the file changes in this pull request:

It shows up on the browser like below:

For a better developer experience, it is best to reload the server when a file changes. To achieve this we use nodemon. There are 2 ways to use Nodemon. The first way is to install it as a global node module resulting in a global nodemon command. The second one is to have it as a dev dependency local to the project. We will be using the first way with the following command:

After you run your application index ( bin/www in this case) with nodemon it will restart the server on each file save. Below is an example of how it looks on server restarts on code change:

At this stage, you have the generated Node.js express app running. It can also be run with nodemon to restart the Node.js server on every file save. The next stage is to use Node.js on docker.

2. Use Node.js on Docker

To use Node.js on Docker, we will start with a Dockerfile . As per Docker's official website:

A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.

We will start with a simple dockerfile and move on to a multi-stage one. We will use a multi-stage build dockerfile so that we have one stage for development and another one for production. In the development stage, we will have Nodemon. There will be some optimization for the production docker container, one of them being the absence of not needed Nodemon.

Below is the simple Node Dockerfile for the express application:

The dockerfile is simple. It is:

using node:14-alpine as the starting point. We are using alpine because it is a small and secure base image for docker containers.

as the starting point. We are using alpine because it is a small and secure base image for docker containers. We first copy package.json and lock file to the WORKDIR /src to exploit docker's build caching

to exploit docker's build caching Then we run npm install --production to get only the needed application dependencies from npm

to get only the needed application dependencies from npm After that our application code is copied to /src

Consequently, the port is exposed, and the command to start the server is executed

With good use of Docker caching and BUILDKIT you can get faster docker builds. To build the above dockerfile to a Node.js docker image execute the following:

It will take some time. After it is done you should see an output like below:

Time to run the docker image and see the output for Node.js with Docker on the browser. To do this run the following command:

In the above command:

--rm is used to remove the container when it is stopped

is used to remove the container when it is stopped --name is used to name the container running Node.js on docker, it will be used later to see logs and stop the container

is used to name the container running Node.js on docker, it will be used later to see logs and stop the container -d is used to detach the container process sending it in the background

is used to detach the container process sending it in the background -p 3000:3000 means the local post 3000 is mapped to container port 3000

Now to know the container is running, run the following command:

You should see something like below:

You can view the logs from the container with the following command:

This will attach the command line (bash) to the container logs. Then hit the URL http://localhost:3000 on a browser. You will see some logs. Hit Ctrl+C to stop viewing logs. Now you can stop the container with the following command:

Below is a recap of running the docker container, viewing logs, and stopping it:

The above simple dockerfile is also available as a pull request for your convenience. At this juncture, we can proceed to make the Node.js dockerfile even better with a multi-stage docker build.

2.2 Multi-stage docker file to support Nodejs docker in production

We will create 3 stages from the above simple dockerfile. The stages will be as follows:

Base: This stage will have things common for docker with Node.js Production: This stage will have components useful for the production environment for Node.js on docker. It also uses npm ci in place of npm install. Dev: This stage will have nodemon which is only useful for developing Node.js on docker

Below is the modified dockerfile:

You can build the above Node.js dockerfile to run Node.js on docker with the following command:

The addition here in this docker build command compared to the above one is the inclusion of --target=dev . It tells docker to build the dev stage not production . If you want to build this multi-stage docker file for Node.js on docker use --target=production and it will create a docker image optimized for production.

To run the Node.js Docker image and attach it to its logs, you can run the following command:

The main difference here from the above docker run command for Node.js with Docker is -v "$(pwd)":/src . As we want the server to restart on every file change the current directory is mounted on the docker container's work dir. With this on each change, the sever will restart for Node.js on docker.

The multi-stage dockerfile for docker with Node.js can be found in this pull request. Below is a quick recap of the commands for Node.js Docker multi-stage build:

3. Node.js Docker made better with docker-compose

As seen, we had to run long commands like below:

It was not easy, to say the least. Stopping the running container also needed another docker stop command. The solution to these issues is using docker-compose with Node.js on docker. Docker-compose can be used effectively to sew up multiple services like a database with the application Node.js docker container.

With docker-compose, you can get the application running with just as single commands, docker compose up . It will build the containers if they are not built and run them for you. Next, we will see how to do it.

3.1 Build the Node.js docker-compose with dev target

To being with, below is the docker-compose.yml file that can run the application on docker with Node.js using docker-compose:

The docker-compose file has some parts to understand:

The version is latest at 3.8

In services, the web service has target:dev being sent so that we build only for the dev stage, not production

being sent so that we build only for the dev stage, not production The current directory . is mounted to the docker container at /src so the changes will be reflected in the container too.

is mounted to the docker container at so the changes will be reflected in the container too. We changed the npm start command in the Docker with Node.js to use nodemon as we will use docker-compose only for development.

command in the Docker with Node.js to use as we will use docker-compose only for development. We pass in only one environment variable NODE_ENV as dev other environment variables for instance database credentials can also be passed in as environment variables.

We will be using BUILDKIT to build docker containers with docker-compose too. To use BUILKIT with docker-compose while building the docker container we can execute the command below:

Here you see the output of the docker-compose build for docker with Node.js with BUILKIT in action:

After the containers are built it can be easily run with docker-compose up . After the Node.js Docker containers are built, it can be run with docker-compose up like below:

The changes for docker-compose addition is in this pull request. This is how you can run Node.js on Docker which works very well for Developing as well as putting the containers in the production environment.

TLDR; Give me a quick rundown

All the code is in a public Github repository. You can run the following commands to get started quickly:

Given you have git set up correctly, clone the repo with: git clone git@github.com:geshan/express-app.git Then execute cd express-app After that run COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose build , wait for the build to finish. Consequently, execute: docker-compose up , wait for some time to see nodemon starting... on your console. Following that, hit http://localhost:3000 on a browser You should see the following output on your browser:

Enjoy! Now you can reverse engineer the Dockerfile and docker-compose.yml file. If you have any questions, search for a specific thing like say target in this post.

There are some considerations you should be aware of:

In my experience, containers on production are run with an orchestrator like Kubernetes. I believe Docker Swarm (and docker-compose) in production have lost the race by now. It is best to use Docker build caching and BUILDKIT for faster builds. Docker-compose makes it easier to use multiple dependencies in the development environment. For example, if your application depends on MySQL and Redis it can be easily put together in the docker-compose,yml file.

Using Node.js on Docker is a rewarding experience. If you want to upgrade Node.js, it is as simple as changing the version on the Docker file, rebuilding it, and using it. Node.js is also great for microservices.