Nodejs App Deployment with Docker Compose

This blog post demonstrates how to deploy a Node.js web application, which uses the Redis server using Docker compose. The Redis server is used to store the number of visitors to our Node.js  application.

Pre-requisites 

As we are deploying the app using Docker and Docker compose we need to install this software first. Here am using Ubuntu 18.04 as my hosting server.

Docker installation

To install Docker on the Ubuntu machine execute the below commands.

  1. Setup the repository to install additional packages.
$ sudo apt-get update

$ sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

2. Add Docker’s official GPG key:

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

3. Set up the stable repository.

$ echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

4. Install Docker engine.

 $ sudo apt-get update
 $ sudo apt-get install docker-ce docker-ce-cli containerd.io

5. Check the installed Docker version.

$ sudo docker --version
Docker version 20.10.11, build dea9396

Docker 20.10.11 has been installed successfully.

6. Install Docker compose

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

7. Apply execution permission to the binary.

sudo chmod +x /usr/local/bin/docker-compose

8. Create a symlink to the /usr/bin directory.

sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

9. Confirm the installed docker-compose version with the below command

docker-compose --version docker-compose version 1.29.2, build 5becea4c

Create the application

The Docker  and docker-compose installation has been completed, next, we have to code our application, for this I will create a directory called “mywebapp”. Inside this folder create a file called package.json, this file contains the configurations for our application. Copy and paste the below code to the package.json file.

{

  “dependencies”: {

    “express”: “*”,

    “redis”: “2.8.0”

  },

  “scripts”: {

    “start”: “node index.js”

  }

}

In the above code snippet, I have mentioned the dependencies are express ( * means any version of it) and Redis (version 2.8.0). Under the script, I mentioned  “start index.js” this is command which start our node.js application.

 

Next, create another file called index.js in the same folder, which is the index file of the node.js application, this file we mentioned in the package.json file.

const express = require('express');
const redis = require('redis');

const app = express();
const client = redis.createClient({
   host: 'redis-server',
   port: 6379,
});
client.set('visits', 0);

app.get('/', (req, res) => {
client.get('visits', (err, visits) => {
res.send('Number of visits ' + visits);
client.set('visits', parseInt(visits) + 1);
   });
});

app.listen(8081, () => {
console.log('listening on port 8443');
});

The above code creates a website and displays the number of hits to the site.  In the code, we have used the arguments host and port to connect to the Redis server. We have created our website and the next step is to build the Docker image. This image will be used to created a container for our node.js application.

Build Docker image

To build a docker image, create a file called Dockerfile in the same folder and copy-paste the below code.

FROM node:alpine

WORKDIR '/app'

COPY package.json .
RUN npm install
COPY . .

CMD ["npm","start"]

This docker file combines the index.js and package.json files together. The Docker file is using a node base image (alpine version) and creates a working directory called “/app”. It copies the package.json file from the “mywebapp” folder to the image. “RUN npm install” install all the dependencies mentioned and the COPY command will copy all files (index.js) to the container image and the last command CMD will start the node.js server as a container.

As I mentioned first, our application uses a Redis server to store the number of hits to the application, for this we need a Redis server. We will not build a custom Redis server instead we use the existing Redis image which is present in the Docker Hub.

Now, we have two Docker images and we can run two containers (Node app and Redis server) from these images but how we can connect the Node app and Redis server? to establish this we will use docker-compose. Docker-compose will spin up the container in the same network (docker network) and open the mentioned ports as well. It helps to connect both containers

Create Docker-compose file

Create another file named docker-compose.yaml inside the same directory and copy-paste the below code.

version: '3'
services:
  redis-server:
    image: 'redis'
  node-app:
    build: .
    ports:
      - '8443:8080'
The docker-compose file explanation is given below.
Version: The Docker compose version.
Service: The containers which we are going to use, first one is redis and the second one we will build using the docker file. The “.” represent the location of Dockerfile.
Ports: 8443 port is used to open in the server and 8080 is the port inside the container.
The docker compose file has been created. Next build the container using this file, for this execute the below command.

$ docker-compose up --build

The command will automatically  download base images, build images with Dockerfile, create network and run the container. Adding sample output for reference.

azureuser@docker-machine:~/mywebapp$ sudo docker-compose up --build

Creating network "mywebapp_default" with the default driver

Pulling redis-server (redis:)...

latest: Pulling from library/redis

e5ae68f74026: Pull complete

37c4354629da: Pull complete

Status: Downloaded newer image for redis:latest

Building node-app

Sending build context to Docker daemon 5.12kB

Step 1/6 : FROM node:alpine

alpine: Pulling from library/node

Status: Downloaded newer image for node:alpine

---> bb1fcdaff936

Step 2/6 : WORKDIR '/app'

---> Running in cf8db57a40b5

Removing intermediate container cf8db57a40b5

---> 4bf1d37e2859

Step 3/6 : COPY package.json .

---> 5f85cdec800a

Step 4/6 : RUN npm install

---> Running in 468a9c7d4271

Creating mywebapp_redis-server_1 ... done

Creating mywebapp_node-app_1 ... done

Attaching to mywebapp_node-app_1, mywebapp_redis-server_1

redis-server_1| 1:M 10 Dec 2021 10:16:52.479 * Ready to accept connections

node-app_1|

node-app_1| > start

node-app_1| > node index.js

node-app_1|

node-app_1| listening on port 8443

From the output it is clear that the docker-compose has created two containers (mywebapp_redis_server_1 and mywebapp_node_app_1) and created a network called “mywebapp_default”.

Go to the web browser and access our application with the public IP and the port (8443), http://<public_ip>:8443/ and you can see first time show the number of visits as o

Access the application multiple time and you can see the number of visits is increasing.

We have created a multi-node docker container with the help of docker-compose. To understand more about how the containers connected execute below command and list all networks in the system.

azureuser@docker-machine:~/mywebapp$ sudo docker network ls
NETWORK ID   NAME             DRIVERSCOPE
bba20c7fe1bf bridge           bridgelocal
ceb75a72bcca host             hostlocal
a44f1f58cd07 mywebapp_default bridgelocal
1ab04fdceec0 none             nulllocal

Here you can see a network named mywebapp_default is created, now execute below command to get more detail about this network. (output is trimmed)

$ sudo docker network inspect mywebapp_default
"Containers": {
            "155c0e11068f48bdcd84e07c4b8bbbae851705c6319171b6f369267c4e04e471": {
                "Name": "mywebapp_redis-server_1",
                "EndpointID": "74f5597331942acede83efa6a01e7b4cedcfe9a4ecc808a7fae557b7ad02024c",
                "MacAddress": "02:42:ac:16:00:03",
                "IPv4Address": "172.22.0.3/16",
                "IPv6Address": ""
            },
            "557f8f0d936744e84a085d19e7bed6b3e976b8d5aeb46d105302973fd986e0ea": {
                "Name": "mywebapp_node-app_1",
                "EndpointID": "341721094df29832e18bfcd03614c81e0cfeaf07e1c78a1feaf5b25fc9b6dafb",
                "MacAddress": "02:42:ac:16:00:02",
                "IPv4Address": "172.22.0.2/16",
                "IPv6Address": ""
            }

Look closely at the command output and you can see both containers mywebapp_redis-server_1 (172.22.0.3/16) and mywebapp_node-app_1 (172.22.0.2/16) have the same network range address. 

Leave a Reply

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