Let's Dockerize a Node.js App 🐋

Let's Dockerize a Node.js App 🐋

Cover image by Ian Taylor on unsplash.com

In this article, we’ll discuss how to dockerize a Node.js application.

What is Docker and What is the purpose of dockerizing my Node.js App? 🤔

You might have heard about Docker everywhere. You see it in every job requirement. Everyone is talking about it.

So, what is Docker and why is everyone using it these days?

Docker is a platform for building, running, and shipping applications in a consistent way. It basically solves the common problem in software development - It works on my machine😬. If you have been developing software for a while you've probably come across this situation where your application works on your development machine but doesn't somewhere else. It could be in your co-worker machine or production machine. What might be the possible reasons for this issue? Here are some of them:

#1 Missing files or project dependencies

It might happen if one or more files or dependencies of your project are not included as a part of your deployment. So your application is not completely deployed. It's missing something.

#2 Software version conflict

This can also happen if the target machine is running a different version of software like databases or runtimes that your application requires. Let's imagine a scenario where your application requires Node version 16 but your target machine is running Node version 10. This is where mishap occurs.

#3 Configuration settings mismatch

If the configuration settings like environment variables are different across your machines, this issue is sure to happen.

This is where Docker comes to the rescue.🤗 With Docker we can build and ship our applications in such a way that if your application works on your development machine it can run and function the same way on other machines. So if your application needs a given version of Node and MongoDB, all of these will be included in your application package. Now, you can ship this package anywhere and run it on any machine that runs Docker. So if it works on your development machine it's definitely going to work on your test and production machines. There's one more advantage to using Docker. If someone joins your team, they don't have to spend the whole day or week setting up their machine to run the application. They don't have to install and configure these dependencies and required tools like MongoDB, Redis, RabbitMQ, and so on. They can simply instruct Docker to bring up your application and Docker will take the responsibility to download and run these dependencies inside an isolated environment called a container. This is the beauty of Docker. ❤

Now what is Container?

A container is an isolated environment for running an application. An isolated environment allows multiple applications to use different versions of some software side by side. So one application may use node version 16. Another application may use node version 17. Both these applications can run side by side on the same machine without messing with each other. So this is how Docker allows us to consistently run an application on different machines using containers.

Now there is one more benefit here when we're done with this application and don't want to work on it anymore we can remove the application and all its dependencies in one go. Without Docker, as we work on different projects our development machine gets cluttered with so many libraries and tools that are used by different applications, and then after a while, we don't know if we can remove one or more of these tools because we're always afraid that we would mess up with some application. With Docker, we don't have to worry about this because each application runs with its dependencies inside an isolated environment. We can safely remove an application with all its dependencies to clean up our machine. Isn't that great?

So in a nutshell Docker helps us consistently build, run and ship our applications and that's why a lot of employers are looking for people with darker skills these days. So if someone is pursuing a job as software or DevOps engineer, he/she should learn Docker and learn it well.

Enough theory, Let's get to the action. ⚔️

Docker in Action

Now, let's see Docker in action and see a basic Docker workflow by containerizing a simple Node.js application.

  1. Let's make a directory "hello-docker" with the mkdir command and get into the directory with the cd command. Now, let's open the directory in VS Code.
mkdir hello-docker
cd hello-docker
code .

dock.png Now, let's add a file called app.js in this directory and add the following code in the app.js file.

console.log("Hello Docker");

dock1.png

So, with this, we are going to print a message "Hello Docker" on the terminal. Let's say this is an application and we want to dockerize this application. So, we wanna build, run and ship it using Docker. So typically without Docker if you want to ship this application or more accurately this program to a different computer, on that computer we need to install node and then we can go to the terminal and type :

node app.js

So we get the output: ss.png

So, here are the instructions for deploying this program:

  1. Start with an OS (Windows, Mac, Linux)
  2. Install Node
  3. Copy app files
  4. Run node app.js

We have to follow these four steps just for a simple program😢. What if you were working with a really complex application? You would end up with a complex release document that had to be precisely followed. Now, this is where Docker comes to the rescue. We can write these instructions inside a Dockerile and let Docker package up our application.

So back to vs code. We're going to add another file to this project called "Dockerfile". In vscode, I have an extension installed for Docker which is recommended for developing applications inside Vscode with Docker. This extension can be installed from the extensions marketplace.

So back to this Dockerfile. The following code is a simple example of Dockerfile. We're going to discuss these commands in detail.

FROM node:alpine
COPY .  /app
WORKDIR /app
CMD node app.js

Here we write instructions for packaging our application. So typically we start from a base image. We can use the FROM instruction to specify the base image for our application. This base image has a bunch of files. We're going to take these files to add additional files to it.

So what is this base image?

Here the base image is Node. We are starting with Node image built on top of Alpine Linux.

FROM node

Alternatively, we can start from a Linux image and install Node on top of it

FROM linux

This node image is already built on top of Linux. These images are officially published on Docker Hub. So, if you go to hub.docker.com, and search for node we can see the official node image. So Docker Hub is a registry for docker images. It's like Git to Github.

Now back to our Dockerfile. So we start from a node image. Now if you look at the Docker Hub, you will see that there are multiple node images. These node images are built on top of different distributions of Linux. So Linux has different distributions or different flavors used for different purposes. Now here we can specify a tag using a column to specify which Linux distribution we want to use for this demo. We're going to use alpine which is a very small Linux distribution. So the size of the image that we're going to download and build on top of is going to be very small. Okay, so we start from that image.

FROM node:alpine

Next, we need to copy our application or program files. For that, we use the COPY command. We're going to copy all the files in the current directory into the app directory into that image. We represent the current directory with a period (.). So that image has a file system and in that file system, we're going to create a directory called app.

COPY . /app

Okay, now finally we're going to use the command instruction CMD to execute a command. What command should we execute here?

 node app.js

But this file is inside the app directory so we have to prefix it with the directory name.

CMD node /app/app.js

The better approach to this is to set the current working directory with the WORKDIR instruction to /app. Then we don't need to prefix app.js with the directory name. So when we use this instruction all the following instructions assume that we're currently inside the app directory.

WORKDIR /app

Okay, so these instructions clearly document our deployment process. Now we go to the terminal and tell Docker to package up our application. We use the following command to build an image from the Dockerfile:

cmd.png

Let's break down this command. Here we needed to give our image a tag. A tag is used to identify an image. With the -t flag, we specify a name like hello-docker, and then we need to specify where Docker can find a Dockerfile. So we're currently inside the hello-docker directory and our Dockerfile is right here. So we use a period (.) to reference the current directory. Let's go with that. Now you might be expecting an image file inside the current directory. Let's verify that with the ls command.

file.png

Look there is nothing here because the image is not stored here and in fact, an image is not a single file. How Docker stores this image is very complex and we don't have to worry about it. So, back to the terminal. To see all the images on the computer, we type docker images or docker image ls which is short for the list.

imgs.png

So take a look at this machine. We have a repository: REPOSITORY called hello-docker. In this repository we have an image with this tag: TAG - latest. So Docker added this by default. We use these tags for versioning our images. So, each image can contain a different version of our application. Each image also has a unique identifier: IMAGE ID. Next, we can see when the image was created: CREATED and the size: SIZE of this image here is 112MB. Here, we used a Node from Alpine Linux, so we ended up with 112 megabytes of data in this image. So this image contains an alpine Linux node and our application files and the total size is 112 megabytes. Now if we used a different node image that was based on a different distribution of Linux like Ubuntu or Debian, we would end up with a larger image, and then when deploying that image we would have to transfer that image from one computer to another. So that's why we use node alpine because this is a very small image.

Okay so we have built this image now we can run this image on any computer running Docker. So on this machine which is my development machine I can say docker run and then type the image name hello-docker and it doesn't matter which directory I'm in because this image contains all the files for running our application. It is accessible globally in the system.

docker-run.png

Now look we see the message on the terminal. Now we can go ahead and publish this image to the docker hub so that anyone can use this image. Then I can go on another machine like a test or a production machine and pull and run this image.

This is Docker in action. 🚀

Thank you so much for reading this article, I hope you enjoyed reading it. Let me know about your feedback in the comments section.

Thank you. Happy learning! ❤