Load Balancing with Docker Compose + Nginx + Nestjs
Tags: docker, loadbalance, nginx, nestjs
Published: 2024-04-25 09:31:43

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
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qrslmlnm3xzi5jerjcrx.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6q9gxfyzq4vpq1t5dd5p.png)

Then go to the source code folder: 

```
cd load-balance-with-docker
```
And you have a structure folder like the picture:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jz716xyn5lxhlmjojsta.png)


**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0j80n9ai614ab3yp94r0.png)

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;"]
```
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/;
    }
}
```
- `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"]


```

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
```
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',
    }]
  };
  
```


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

```
docker-compose up -d
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxcnlv0690y4si2pzaj0.png)

Then you can check the containers by `docker ps`:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ku4w5eb9ycwv7itwngxx.png)

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2b2jr2kpfjml9jw2gcam.png)

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihbyq9o7j1yhpvoyun8x.png)


You can find the source code [here](https://github.com/DinhKhai0201/load-balancing-docker-nginx-nestjs-simple).

Thank you for reading.