DEV Community

Nguyen Dinh Khai
Nguyen Dinh Khai

Posted on

Load Balancing with Docker Compose + Nginx + Nestjs

Hello everyone.
It's possible that you have encountered cases where your server is overloaded, laggy, and unable to process incoming requests.
And when you face that case, there are several ways to address this issue, such as:

  • Option 1: Identify the root cause of the code causing the error.
  • Option 2: Increase server capacity.
  • Option 3: Use Docker + Nginx to create load balancing to distribute incoming requests.
  • etc,...

Today, I will introduce how to implement Option 3. The technologies I am using:

  • NestJS
  • Docker: Nginx, PM2

1. Create a simple NestJs source code

Prerequisites
Please make sure that Node.js (version >= 16) is installed on your operating system.

Setup
Setting up a new project is quite simple with the Nest CLI. With npm installed, you can create a new Nest project with the following commands in your OS terminal:

npm i -g @nestjs/cli
nest new load-balance-with-docker
Enter fullscreen mode Exit fullscreen mode

Image description

Image description

Then go to the source code folder:

cd load-balance-with-docker
Enter fullscreen mode Exit fullscreen mode

And you have a structure folder like the picture:

Image description

2. Docker configuration **
**Prerequisites

Install docker for your device: https://docs.docker.com/engine/install/

We need to configure 3 parts:

  • Nginx
  • Server
  • Pm2

2.1 Nginx
Create a root directory, create a new folder named nginx:

Image description

Create a file named Dockerfile in the nginx directory as follows:

# nginx/Dockerfile
FROM nginx:stable-alpine
COPY default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

The above configuration snippet is a Dockerfile to build a Docker image based on the nginx:stable-alpine image.

  • FROM nginx:stable-alpine: This line identifies the base image that the new image will be based on, in this case nginx:stable-alpine. Image nginx:stable-alpine is a stable version of Nginx on Alpine Linux.

  • COPY default.conf /etc/nginx/conf.d/default.conf: This line copies the default.conf file to the /etc/nginx/conf.d/default.conf path in the image. The default.conf file contains the Nginx configuration that will be used when the container is launched.

  • EXPOSE 80: This line specifies that the container will listen on port 80. However, using EXPOSE does not open the port on the host machine, it is just an instruction telling others which port the container will listen on.

  • CMD ["nginx", "-g", "daemon off;"]: This line defines the command that the container will execute when launched. In this case, the container will run the command nginx -g 'daemon off;' to start Nginx in non-daemon mode so that the container does not exit immediately after starting Nginx.

Then create a default.conf file in the nginx directory

# nginx/default.conf
upstream my-loadbalancer {
    server my-loadbalancer:3000;    
}

server {
    listen 80;

    location / {
        proxy_pass http://my-loadbalancer/;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • upstream my-loadbalancer { server my-loadbalancer:3000; }: This snippet defines a group of servers (upstream) named "my-loadbalancer". In this case, there is only one back-end server running on port 3000.

  • server { listen 80; ... }: This is the configuration for an Nginx server. It listens on port 80 to handle incoming HTTP requests.

  • location / { proxy_pass http://my-loadbalancer/; }: This section defines a default location for all requests. It uses proxy_pass to redirect all requests to the upstream "backend". This allows Nginx to act as a proxy and redirect requests to the back-end server running on port 3000.

2.2 Server
In the main directory, create a Dockerfile file for the server as follows:

# Use node:18 image
FROM node:18.0.0

# Set working directory
WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm install

# Copy source code
COPY . .

# Build app
RUN npm run build

# Install PM2 globally
RUN npm install pm2 -g

# Set environment variables
ENV PORT 3000

# Expose port
EXPOSE $PORT

# Start app
CMD ["pm2-runtime", "start", "ecosystem.config.js"]


Enter fullscreen mode Exit fullscreen mode

Then create a docker-compose.yml file:

version: '1'

services:
  nginx: 
    build: ./nginx
    ports:
      - "80:80"
    depends_on: 
      - my-loadbalancer
  my-loadbalancer:
    build: .
    environment:
      - PORT=3000 
    deploy:
      replicas: 4
Enter fullscreen mode Exit fullscreen mode

The above configuration is a docker-compose file used to define and run services in a Docker environment.

  • It first defines two services: nginx and my-loadbalancer.
  • The nginx service is built from the ./nginx directory and exposes port 80 on the host and container.
  • The nginx service also depends on the my-loadbalancer service before starting.
  • The my-loadbalancer service is built from the current directory (the directory containing the docker-compose file) and sets the environment variable PORT=3000.
  • Finally, the my-loadbalancer service is deployed with 4 replicas to create a load-balanced environment. You can change the number of replicas với sự tương thích của máy chủ server

2.3 Pm2
Create a file ecosystem.config.js in the root directory:

module.exports = {
    apps: [{
      name: 'my-loadbalancer',
      script: 'dist/main.js',
      instances: 'max',
      autorestart: true,
      watch: false,
      max_memory_restart: '1G',
    }]
  };

Enter fullscreen mode Exit fullscreen mode

3. Done
Finally, to run the server, you can use the command:

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Image description

Then you can check the containers by docker ps:

Image description

Finally you can access the server at the url: http://localhost:80

Image description

You can check the stats of containers with the docker stats command

Image description

You can find the source code here.

Thank you for reading.

Top comments (0)