In this article we will learn how to run NodeJS applications on docker. We will take two examples of node applications, one a simple node app returning just ‘Hello from docker!!’ message as response, and another a full fledged node app with MongoDB that performs basic user registration, login and authentication.

Checkout full source code for the implementation here for first application, and here for second one.

What is Docker ?

In simple words, Docker is a way to package an application so it can run on any hardware.

So basically docker is a solution to an age old problem – ‘But it runs on my machine!!

Docker solves problem like this by reproducing environments. To implement docker, there are three things we should know about, docker file, docker image and containers.

Docker File

A docker file is a set of instructions or a blueprint we define to build a environment to run the application. We can then use this file to rebuild the environment on any machine, which is saved as a docker image.

Docker Image

A docker image is immutable snapshot of the application or a template for running docker container. Images can be uploaded to cloud just like any code both in public and private repositories, which can later be pulled to create a docker container.

Docker Container

Docker container is nothing but a running process or a application. Single docker image can be used to spawn same docker container multiple times in multiple places.

Prerequisites

Before we start, make sure to install the following:

Dockerizing a simple NodeJS application

Let’s start with a simple NodeJS application which contains the following code in app.js file.

const express = require('express');
require('dotenv').config();
const port = process.env.PORT || "8000";

const app = express();

app.use('/', (req,res) => {
    res.status(200).send('Hello from Docker!!')
});

app.listen(port, () => {
    console.log(`Listening to requests on http://localhost:${port}`);
  });

Here we have a express application which just returns a message when we navigate to localhost port 8000. Below are the rest of files in the root folder

files-docker
Creating Docker File

To start dockerizing the application, let’s first make sure to install the docker extension available in vs code. Now let’s create a dockerfile which will contain the following code.

FROM node:alpine
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
ENV PORT=8000
EXPOSE 8000
CMD [ "npm" , "start" ]

Here each line instruction is considered a step or a command executed in sequence. Let’s break down each step to understand what exactly we are doing here

Line 1: First step is to choose a runtime like ubuntu or a base image, in our case we are using a node alpine base image as it is ideal for running node and is of smaller size (~5MB).

Line 2: Here we are creating a work directory in docker, from here on now any file operation we do, will be done inside this directory.

Line 3: here we are copying package.json file from our root project directory to docker app/ directory.

Line 4: Now using the package.json file, here we are installing all the dependencies required to run the application.

Line 5: copying rest of the files in our project folder to docker app/ directory. But here we have to take care of the files and folder we do not want to copy, for example node_modules folder and .env file. So to take care of that, just like .gitignore file we have .dockerignore file.

Line 6: we are setting environment variable for port as we have done the same in our code.

Line 7: Here we exposing port 8000 of the container to listen to incoming requests

Line 8: here we define the command to run the node application, just make sure to add the start script in package.json file.

This completes our docker file definition. Below are the files in our root folder now, with .dockerignore content

docker project files 2
Building Docker Image

Now using the docker file we can build docker image with the following docker build command

docker build -t azzan93/dockerdemo:1.0 .

Here -t is used to give the image a name tag. azzan93 is my dockerhub username, you can create one for free on dockerhub, then we gave the name tag as dockerdemo and version 1.0 and ‘.’ is the path of the docker file. Let’s run the command in terminal and see the result.

docker build cmd

Here we see the step by step execution of the build step we defined in dockerfile. You will notice some steps are cached here as i am not running this command for the first time.

Now if we open docker desktop and navigate to images we should see the image there.

docker desktop 1

we can see our docker image here with name, tag and image id. To use this image we usually push this to a container repository like docker hub or any cloud provider and the command we use for that is docker push and to retrieve it docker pull just like git command. But here we just want to run it locally, so let’s do that.

Running Docker Container

To run the container we use the following command

docker run e46b336cc910

where we have to give the image id or tag name to specify which image we are using, lets hit it and see the result

docker container 1

Here we see that, the command is executed successfully and we get the console message that its listening to requests on localhost:8000.

But if we try to navigate to localhost:8000 we cannot see anything there. why?

In our docker file we exposed port 8000, this port is a docker port not a localhost port, which is not accessible to the outside world . So to access the docker container port we will have to implement something called port-forwarding, which is when we map a port from our local machine to a port on docker container. Let’s understand it by using the below command

docker run -p 4200:8000 e46b336cc910

Here we used -p flag to indicate port forwarding, 4200 is the port on our local machine which is being mapped to port 8000 on container. Now we have to hit localhost:4200 in browser to check if the container is running.

container run

As you can see that our container is up and running.

One thing to take care of is, our container will still be running when we close the terminal. To manage the container we can use docker desktop.

container-desktop

Here we see our container listed, with options to manage it, like stop, start, delete etc.

Dockerizing NodeJS and MongoDB application using Docker Compose

Now let’s dive deeper, and try to dockerize a full fledged Node application having MongoDB as database.

We will take an example of a node application we built in User Authentication in NodeJS using Passport.js (local strategy) article.

You can also find the complete source for the application here.

Below is the project structure and files in this application.

folder structure

Here also we will start by creating a docker file with the following steps

FROM node:alpine
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
CMD [ "npm", "start" ]

Here we have most of the same steps as we defined in the previous application and only steps missing are the ones defining environment variables and exposing the port. Which we will see later. Also make sure to add node_modules in .dockerignore file.

Now this application is not just plane NodeJS but requires MongoDB as well which runs on another port. This is where docker compose comes into play, it helps in running multiple docker containers at the same time.

To use docker compose we need to define a docker-compose.yml file with following instructions

version: '3.9'

services:
  #mongo database container
  mongodb:
    container_name: 'database_container'
    image: mongo:latest
    restart: always
    volumes:
      - mongoDB:/data/db
  
  #nodeJS service
  node:
    build: .
    ports:
      - 8000:3000
    environment:
      PORT: 3000
      DB_URI: mongodb://mongodb:27017
      SESSION_SECRET: expressSessionSecret

volumes:
  mongoDB: {}

Line 1: Here we give the version of docker compose, 3.9 is currently the latest

Line 3: under services we need to define all the containers we will be using.

Line 5-7: First we are defining the mongo container by giving container name and importing mongo image, just like we did for node in dockerfile.

Line 8: Here we setting mongo container to always restart in case of any failures.

Line 9: When we shut down the containers, we lose the state of the application. To avoid this, we can make use of volumes. Here we defined a volume and attached it to mongodb container and gave the default path of mongo storage to the volume.

Line 13-14: Here we have defined our node container. First step is building the image by providing the path of dockerfile we created.

Line 15-16: Here using port forwarding to map port 8000 of local machine to port 3000 of docker node container.

Line 17-20: Setting the environment variables. Here you will notice that in DB_URI, instead of giving localhost:27017 we gave mongodb:27017, this is because docker does not have localhosts, here we are connecting to port 27017 of mongodb container we created above.

Line 22-23: Just like services we need to define if we are using any volumes.

Now our project should contain the following files, with .dockerignore content as shown

files in project

Now to use docker compose we need to use the below command

docker compose up

Now based on our instructions, this command will build and run both the containers.

docker compose command

As you can see we successfully executed docker compose command. Let’s see in docker desktop if our containers are up.

docker desktop

Here we see that both mongo and NodeJS container are up and running.

Now if we navigate to localhost:8000 in browser we should see the entire application in action as shown below.

Debugging in Docker Container

We can make use of Docker desktop to check logs of our application as seen below

container logs

Here we have logs of our node container, there is even a search option to search through logs. We also have inspect option as shown below

container inspect

Here we can see all environment variables defined for our application as wall as ports both the container’s and localhost. Also there is stats option as shown below

container inspect

Here we have data to monitor the container.

This almost covers our tutorial on running NodeJS applications on docker.

Also checkout this article to learn how to implement JWT Authentication in NodeJS.

Leave a Reply

Your email address will not be published. Required fields are marked *