How to Build a Node.js Application with Docker
By: Jordan Moore
Tags: Nodejs, Docker, Software Engineering, Expressjs, JavaScript

How to Build a Node.js Application with Docker

If you’ve found this article, that means that you have been tasked to setup an application and run it inside of a Docker container. By the end of this article, you will be able to do exactly that.

Before diving into the necessary code, it’s important to understand the underlying problem that containers and Docker solve. Luckily, this problem is simple to understand, and will make our code easier to comprehend.

For readers who are already familiar with Docker and its use case, scroll past the next section.

What is the problem that Containers solve?

Most developers can relate to the following scenario that illustrates the purpose of containers well. A co-worker asks you to clone their GitHub repository in order to help them debug an issue. After you’ve cloned it and installed the necessary dependencies, you find that the application will not run. It’s throwing all sorts of errors, none of which are the problem that your co-worker requested help with.

The problem that you two have just ran into is that of running an application on a different machine with a different environment. No two developers have the same machine. Just because one developer’s application works on their machine doesn’t mean that it will work on your machine, since your machine may have many different attributes in their respective environments, like:

Operating system;

Software version;

Installed dependencies;

The list of differences can go on and on! What is the solution to this problem?

This is where containers are a solution. By packaging code and all of its dependencies into an isolated container, a developer can run that container on any machine and have predictable results, since everything the application needs to run is included in the container.

Once an application is containerized, it can be shared and run on any machine. So, the aforementioned problem of incompatible development environments is solved.

We’ve arrived at the definition of a container: a container is a software unit that packages up code and all of the code’s dependencies so that it can run on any machine.

Let’s translate this definition into a Node.js application; what would the process of packaging up the code and its dependencies be like?

First, the host that will run the application will need the Node.js runtime, so that should be included. Next, we’ll need the actual code that comprises the application, like index.js , and all of the other files that make up the application. We will also need the package.json , as that file defines the necessary dependencies for the application.

Additionally, we will need to define the command(s) necessary to actually run the application.

Now that we’ve seen the problem that Docker containers solve, let’s actually create one for a Node.js application.

Defining the Dockerfile

Go to a new directory and run npm init . This will create a new package.json file for defining an application and its dependencies.

Let’s add a dependency: run npm i express to install express as a dependency. We will use this package to create a server.

Now, create the entry point file for your application. It’s name will be whatever you defined it as during the init setup. On my machine, it’s index.js , so I’ll run touch index.js .

Open index.js and paste the following code:

index.js is just specifying a host and port combination to listen on for requests. When we visit the root, / , we will receive the response on line 11 from the code.

This is enough code to write and test out Docker containers.

In order to spin up a Docker container, we first need to write a Dockerfile which defines the application, its dependencies, and any commands that need to be executed for the application to run. Run the command touch Dockerfile and open it in a text editor.

First, we need to define the base image that will be used to build this container upon.

An image is used to build a Docker container. The image provides the necessary environment for running a particular application or software inside of it as a container. In our case, we are running a Node.js application, so we will need to build from the Node.js image to provide our application with the Node.js runtime. To do that, mimic the following Dockerfile:

We have selected a certain image provided by Node.js. There are many different options; in this case, we opted for a small image of it.

Next, let’s define the working directory inside of the container. This directory will house our application’s code.

Up next is copying the package.json file from the current directory on our machine into the image, and also running npm install to install the dependencies defined in package.json . Since we’ve used the base image of Node.js, NPM is already installed and can be run inside the container.

For this, we’ll use the commands COPY and RUN.

The COPY package.json . will copy the package.json in our current directory to the working directory, /usr/src/app/package.json .

We’re almost done — now, we need to copy the rest of our application’s source code; in this case, it’s just index.js . Define another COPY command for the current directory.

We’ll need to use a new command now, EXPOSE. This command tells the Docker container which port to listen to at runtime. In our case, our application listens on 3000, so expose the same port in the container. In addition to exposing the container’s 3000 port, run the final command to start the application: node index.js .

Before actually building this image and running the container, create a .dockerignore file to define which files from the current directory should be ignored when building the image. This file should exist in the same directory as the Dockerfile.

As it stands right now, the entire directory is getting copied over to the working directory in the image, including the node modules.

This directory can be massive. We don’t want to include node_modules , so add it to .dockerignore .

Building the Image

The Dockerfile is defined, but we still haven’t built the image. In order to run this application in a container, we will need to run the image representing the application. So, let’s build our image from the Dockerfile, and then run the image as a container.

In the current directory, run docker build -t <your_name>/node_app . . The . at the end specifies to check the current directory for a Dockerfile, while the -t <your_name>/node_app gives the image a tag that you can succinctly specify to run the image.

After it has been successfully built, it’s time to run the image as a container. Run the following command: docker run -p 5000:3000 -d <your_name>/node_app .

-p specifies a public port on your machine to map to an internal port in the Docker container. In this case, we map to the port 3000 so that requests to our machine at 5000 will be routed to the Docker container’s 3000 port. -d instructs Docker to run this container as a background process so you can keep operating in the terminal.

Visit localhost:5000 in a browser, and the message ‘Running on Docker!’ should be present.

Conclusion

As one can see, Docker containers give developers immense power by ensuring that we can run any application on any machine.

In this article, we’ve only been exposed to a tiny sliver of Docker’s power. For any readers who want additional work, try the following exercise.

Create an application that uses a Redis instance in some way. Then, define a docker-compose file that can seamlessly run both the application and Redis and allow them to communicate over a shared network.

Good luck and happy coding!