DEV Community

Ajeet Singh Raina for Docker

Posted on • Updated on

Dockerizing a NodeJS , Express, Redis with Nginx Proxy using Docker Compose

With over 14,700 stars, 2,000 forks, awesome-compose is a popular Docker repository that provides a starting point for how to integrate different services using a Compose file and to manage their deployment with Docker Compose. This project is maintained by Docker, Inc and open for the community to contribute and submit their compose file.

Image23

Available Categories

The setups provided currently in the awesome-compose repository fit mainly in two categories:

Application skeletons

It is useful for kicking off project development. We can find different application skeletons with multiple services already wired together and ready to be launched with docker-compose;

Open-source software stacks

These setups are not meant for production. They are intended mostly for personal/home use or simply for developers to get familiar with them within their local dev environment.

Here's a quick glimpse of the awesome-compose repository:

Image41

In this tutorial, you will see how to containerise a NodeJS web application with Redis database and Nginx as a reverse proxy in front of NodeJS app using Docker. We are going to build the following project structure:

Project structure

.
├── docker-compose.yml
├── nginx
│   ├── Dockerfile
│   └── nginx.conf
├── web
│   ├── Dockerfile
│   ├── package.json
│   └── server.js
├── web1
│   ├── Dockerfile
│   ├── package.json
│   └── server.js
└── web2
    ├── Dockerfile
    ├── package.json
    └── server.js

4 directories, 12 files
Enter fullscreen mode Exit fullscreen mode

Prerequisite:

– Install Docker Desktop

Visit https://docs.docker.com/desktop/mac/install/ to setup Docker Desktop for Mac or Windows on your local system.

Image1

ℹ️ INFO

Docker Desktop comes with Docker compose installed by default, hence you don't need to install it separately.

Step 1. Create a Docker compose file

Create an empty file with the below content and save it by name - "docker-compose.yml"

version: '3.9'
services:
  redis:
    image: 'redis:alpine'
    ports:
      - '6379:6379'
  web1:
    restart: on-failure
    build: ./web1
    ports:
      - '81:5000'
  web2:
    restart: on-failure
    build: ./web2
    ports:
      - '82:5000'
  nginx:
    build: ./nginx
    ports:
    - '80:80'
    depends_on:
    - web1
    - web2
Enter fullscreen mode Exit fullscreen mode

The compose file defines an application with four services redis, web, web1 and web2.
When deploying the application, docker-compose maps port 80 of the web service container to port 80 of the host as specified in the file.

ℹ️ INFO

Redis runs on port 6379 by default. Make sure you don't run another instance of Redis on your system or port 6379 on the host is not being used by another container, otherwise the port should be changed.

Step 2. Create an nginx directory and add the below files:

File: nginx/nginx.conf


upstream loadbalancer {
  server web1:5000;
  server web2:5000;
}

server {
  listen 80;
  server_name localhost;
  location / {
    proxy_pass http://loadbalancer;
  }
}
Enter fullscreen mode Exit fullscreen mode

File: Dockerfile

FROM nginx:1.21.6
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
Enter fullscreen mode Exit fullscreen mode

Step 3. Create a web directory and add the below files:

File: web/Dockerfile

FROM node:14.17.3-alpine3.14

WORKDIR /usr/src/app

COPY ./package.json ./
RUN npm install
COPY ./server.js ./

CMD ["npm","start"]
Enter fullscreen mode Exit fullscreen mode

File: web/package.json

"name": "web",
  "version": "1.0.0",
  "description": "Running Node.js and Express.js on Docker",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.17.2",
    "redis": "3.1.2"
  },
  "author": "",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode

File: web/server.js

const express = require('express');
const redis = require('redis');
const app = express();
const redisClient = redis.createClient({
  host: 'redis',
  port: 6379
});

app.get('/', function(req, res) {
    redisClient.get('numVisits', function(err, numVisits) {
        numVisitsToDisplay = parseInt(numVisits) + 1;
        if (isNaN(numVisitsToDisplay)) {
            numVisitsToDisplay = 1;
        }
        res.send('Number of visits is: ' + numVisitsToDisplay);
        numVisits++;
        redisClient.set('numVisits', numVisits);
    });
});

app.listen(5000, function() {
    console.log('Web application is listening on port 5000');
});
Enter fullscreen mode Exit fullscreen mode

File: server.js

const express = require('express');
const redis = require('redis');
const app = express();
const redisClient = redis.createClient({
  host: 'redis',
  port: 6379
});


app.get('/', function(req, res) {
    redisClient.get('numVisits', function(err, numVisits) {
        numVisitsToDisplay = parseInt(numVisits) + 1;
        if (isNaN(numVisitsToDisplay)) {
            numVisitsToDisplay = 1;
        }
        res.send('web1: Total number of visits is: ' + numVisitsToDisplay);
        numVisits++;
        redisClient.set('numVisits', numVisits);
    });
});

app.listen(5000, function() {
    console.log('Web app is listening on port 5000');
});
Enter fullscreen mode Exit fullscreen mode

File: package.json

{
  "name": "web1",
  "version": "1.0.0",
  "description": "Running Node.js and Express.js on Docker",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.17.2",
    "redis": "3.1.2"
  },
  "author": "",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode

Step 5. Deploy the application

Let us deploy the full-fledged app using docker-compose

$ docker-compose up -d
Enter fullscreen mode Exit fullscreen mode
Creating nginx-nodejs-redis_redis_1 ... done
Creating nginx-nodejs-redis_web1_1  ... done
Creating nginx-nodejs-redis_web2_1  ... done
Creating nginx-nodejs-redis_nginx_1 ... done
Enter fullscreen mode Exit fullscreen mode

Expected result

Listing containers must show three containers running and the port mapping as below:

docker-compose ps
           Name                        Command              State           Ports         
------------------------------------------------------------------------------------------
nginx-nodejs-redis_nginx_1   /docker-entrypoint.sh ngin     Up      0.0.0.0:80->80/tcp    
                             ...                                                          
nginx-nodejs-redis_redis_1   docker-entrypoint.sh redis     Up      0.0.0.0:6379->6379/tcp
                             ...                                                          
nginx-nodejs-redis_web1_1    docker-entrypoint.sh npm       Up      0.0.0.0:81->5000/tcp  
                             start                                                        
nginx-nodejs-redis_web2_1    docker-entrypoint.sh npm       Up      0.0.0.0:82->5000/tcp  
                             start   
Enter fullscreen mode Exit fullscreen mode

Step 6. Testing the app

After the application starts, navigate to http://localhost:80 in your web browser or run:

curl localhost:80
curl localhost:80
web1: Total number of visits is: 1
Enter fullscreen mode Exit fullscreen mode
curl localhost:80
web1: Total number of visits is: 2
Enter fullscreen mode Exit fullscreen mode
$ curl localhost:80
web2: Total number of visits is: 3
Enter fullscreen mode Exit fullscreen mode
$ curl localhost:80
web2: Total number of visits is: 4
Enter fullscreen mode Exit fullscreen mode

Stop and remove the containers

$ docker-compose down
Enter fullscreen mode Exit fullscreen mode

Step 7. Monitoring Redis keys

If you want to monitor the Redis keys, you can use monitor command. Install redis-client in your Mac system using

brew install redis

and then directly connect to Redis container by issuing the below command:

% redis-cli
127.0.0.1:6379> monitor
OK
1646485507.290868 [0 172.24.0.2:34330] "get" "numVisits"
1646485507.309070 [0 172.24.0.2:34330] "set" "numVisits" "5"
1646485509.228084 [0 172.24.0.2:34330] "get" "numVisits"
1646485509.241762 [0 172.24.0.2:34330] "set" "numVisits" "6"
1646485509.619369 [0 172.24.0.4:52082] "get" "numVisits"
1646485509.629739 [0 172.24.0.4:52082] "set" "numVisits" "7"
1646485509.990926 [0 172.24.0.2:34330] "get" "numVisits"
1646485509.999947 [0 172.24.0.2:34330] "set" "numVisits" "8"
1646485510.270934 [0 172.24.0.4:52082] "get" "numVisits"
1646485510.286785 [0 172.24.0.4:52082] "set" "numVisits" "9"
1646485510.469613 [0 172.24.0.2:34330] "get" "numVisits"
1646485510.480849 [0 172.24.0.2:34330] "set" "numVisits" "10"
1646485510.622615 [0 172.24.0.4:52082] "get" "numVisits"
1646485510.632720 [0 172.24.0.4:52082] "set" "numVisits" "11"
Enter fullscreen mode Exit fullscreen mode

Further References

Top comments (5)

Collapse
 
jonahtzuchi profile image
Jonah Junior

I can't thank you enough for your work. Took me a few days to figure out how to achieve the nginx(reverse-proxy) + expressjs(webserver) bundle.

Thank you very much!

Collapse
 
ajeetraina profile image
Ajeet Singh Raina

Glad that the article helped you with your work.

Collapse
 
vasilisa119 profile image
Vasilisa119

Thanks for the article! But unfortunately instead of the nginx/nginx.conf file, the Dockerfile is specified. In other words, the nginx.conf file is missing and the Dockerfile is duplicated.

Collapse
 
ajeetraina profile image
Ajeet Singh Raina

I have fixed it. Thanks for the feedback.

Collapse
 
vasilisa119 profile image
Vasilisa119

Many thanks!