11 min read

Dockerizing a Node.js Application

Dockerizing a node.js application

A guide on how to Deploy Node Application in a Docker container

In this article, we’ll be exploring one of the most popular tools used to containerize and deploy applications over the internet, i.e., Docker. We will look into what Docker is, why we need it, and how to run a basic Node application in a Docker container. 

So, let’s dive in.

What is a Docker?

Docker is a containerization platform that enables developers to seamlessly build, share, and run any application, anywhere. However, the question arises if you’re new to deployment, why do we need containers? Containers are a method of operating system virtualization that allows you to run an application and its dependencies in resource-isolated processes. Being resource-isolated, there are many benefits in containerization using Docker,

  • Improve application life-cycle management through capabilities such as continuous integration and continuous delivery (CI/CD). 
  • Increased portability gives consistent operations because applications in containers will run the same, regardless of where they are deployed, the solution to “Works on my machine” syndrome. 
Figure 1.0 | Source : reddit
  • There are fewer overheads because of isolation at the OS level; containers share the system kernel with other containers, making them faster. (Figure 1.1)
  • Improves security by isolating applications from the host system and each other.
  • Since containers are encapsulated and loosely coupled, it allows you to replace or upgrade one container without disrupting others.
  • Scalable with the help of orchestration services like Kubernetes, Docker Swarm, or Apache Mesos.
Figure 1.1 | Containers and Virtual Machines | Source: Docker.com

Build Once, Run Anywhere.

However, there is a disadvantage in tokenization: applications with different OS requirements cannot be hosted together on the same Docker host. 

Docker collaborates with the open-source ecosystem. Anyone can contribute to Docker and extend it to meet their requirements if they need additional features that aren’t available out of the box. So why try to reinvent the wheel, right?

What are the terminologies associated with Docker?

Let us take a quick look at some of the terminology associated with Docker before looking into a practical example.

Dockerfile: A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Docker image is created using a Docker file.

Docker Images: An image is a read-only template with instructions for creating a Docker container. Often, an image is based on another image, with some additional customization. 

Docker Containers: A container is a runnable instance of an image. You can create, start, stop, move, or delete a container using the Docker API or CLI. In addition, you can connect a container to one or more networks, attach storage to it, or even create a new image based on its current state.

While there are other terminologies that you should become familiar with, like Docker engine, Docker hub, and Docker architecture. You can find detailed clarifications here in the Docker documentation.

Deploying a Node Application

Once you are done developing your application, you need to ensure that the application is executing correctly on the assigned port. In my case, I am using port 8080. If the application is working as expected, you can proceed with deployment. You wrote it, so it should work, right?

Creating a Dockerfile

Create a simple Dockerfile in the app root directory as Dockerfile (case sensitive), with no extension.

The first thing we need to do is define what image we want to build from. After that, you can choose what you need with the application requirements. You can find base images on Docker Hub.

You can also specify multiple FROM instructions which are called a multi-stage build, but for this article, let’s keep things simple.

FROM node:14.15.1

Then we need to define the working directory using WORKDIR.

WORKDIR /usr/src/app

Now we need to install our node application dependencies using the npm or yarn. We are only copying the package.json file using a wildcard. Since Docker creates a layer for COPY command, this allows us to take advantage of cached Docker layers.

Then we install using RUN. This instruction does what it says; it runs commands. Or, more specifically, it executes a specific command. 

RUN npm install

Next, we bundle the app source using COPY.

COPY … 

Then we EXPOSE, the EXPOSE instruction informs Docker that the container listens on a specific network port at runtime. However, this is optional.

EXPOSE 8080 

We define our runtime using CMD; CMD sets a default command, which will be executed when you run the Docker image without specifying a command. 

CMD [ “node”, “server.js” ]

You final Dockerfile should be something like this:

<script src=”https://gist.github.com/lashewi/94837c19483f750c6ab53f35e6108725.js”></script>

Time to leverage .dockerignore. This allows you to exclude files from the context like a .gitignore file will enable you to exclude files from your git repository. This is more of an optimization approach. In the root directory, create a .dockerignore file and add the following lines.

node_modules

npm-debug.log

Building your image

Now that we are done with Dockerfile, let’s build. First, in the root directory, run:

$ docker build -t <your-username>/<repository-name> .

When you have many images, it becomes difficult to know which image is what. So Docker provides a way to tag your images with the -t flag.

Running a Docker image

You can now run the Docker image by using the docker run API. The command is as follows:

$ docker run -p 80:8080 -d <your-username>/<repository-name>

 -d flag is added to run the container in detached mode, leaving the container running in the background, and the -p flag redirects a public port to a private port inside the container. 

docker ps and docker logs <container id> command will help you to list containers and get container logs details, respectively. Now we are at the end of the journey, get container-id using docker ps and get the port of your app that Docker mapped. 

 curl -i localhost:<port>

Conclusion

In this article, we mapped the basics of containerization, what Docker is, why we need it, and how to dockerize a simple node application. While we only scratched the surface of the Docker world, there is a lot more to learn and get hands-on. It needs to be said, combined with mechanics like Docker Compose, volumes, and container orchestration solutions, you can deploy your applications in a seamless transition between your development environment and the live environment. You and your organization can benefit immensely by continuous integration and continuous deployment of application life-cycles. 

Additional Resources:

Here are some valuable resources mentioned throughout the article; it might be helpful to check them out.

  1. Docker Documentation, Docker overview 

Retrieved from: https://docs.docker.com/get-started/overview/

  1. Docker Documentation, Best practices for writing Dockerfiles 

Retrieved from: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

  1. Hub.docker.com, Docker Hub

Retrieved from: https://docs.docker.com/get-started/overview/