Features of Dockerizing Applications Written in Next.js
Nov 28, 2024
4 min read
35 views
Share

By admin

In this article, we describe the challenges faced by the Onix team while creating a Docker environment for a project written in Next.js.
Background: A small project written in Next.js, consisting of a backend, frontend, and a database, needed to be deployed in a cloud environment with minimal costs (up to $50 per month). The decision was made to set up a minimal database server and a small Ubuntu 24.04 server to deploy the dockerized project there.

We created a standard Dockerfile and docker-compose.yml that were used for building the projects.

Dockerfile

FROM node:18.7
ENV PORT 3000
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
#add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install app dependencies
COPY package*.json /usr/src/app/
RUN npm install --legacy-peer-deps
#add app
COPY . /usr/src/app
RUN npm run build
EXPOSE 3000
# start app
СMD ["npm", "start"]

The docker-compose.yml

services:
backend-service:
build:
context: ./backend
dockerfile: Dockerfile
image: backend:latest
container_name: backend-container
env_file:
- ./backend/.env
restart: always
frontend-service:
build:
context: ./frontend
dockerfile: Dockerfile
image: frontend:latest
restart: always
container_name: frontend-container
ports:
- 80:3000
env_file:
- ./frontend/.env
depends_on:
- backend-service

After deploying the project on the server, we encountered the following issues:

1) Docker Compose build Instability

The docker compose build command warked unstable – sometimes the container built successfully, while other times it failed with error code 137. Locally, everything worked without any issues.

Cause: Upon investigation, we discovered that the application was running out of memory and being terminated with a SIGKILL error.

Solution:

We resolved the issue by adding SWAP memory to the server.
Additionally, it is recommended to:
Limit the memory available to Node.js by using the parameter NODE_OPTIONS=–max-old-space-size=2048 .

Limiting Docker container  memory usage with docker-compose option mem_limit.

 

2) Frontend container connection iIssues

The frontend container displayed a strange error when attempting to connect to the backend. The error was strange, as we were certain that nothing in our setup was attempting to connect to 127.0.0.1, especially using dynamic ports.

 

Error: connect ECONNREFUSED 127.0.0.1:36817 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1247:16) {errno: -111, code: 'ECONNREFUSED', syscall: 'connect',
 address: '127.0.0.1', port: 36817}

Investigating this issue, we discovered that certain versions of docker images with Next.js exhibited the same behavior.

Cause: The issue appears because Next.js Docker containers, without warnings, switch to IPv6. When they attempt to resolve the hostname of the backend container, they look for its address in IPv6 format. Since Docker does not use IPv6 addressing by default, this results in connectivity problems.

Solutions

There are a couple of ways to resolve this issue:

1.Force IPv4 Usage in Next.js:

Add the following environment variable to ensure Next.js prioritizes IPv4:

NODE_OPTIONS=--dns-result-order=ipv4first

2.Enable IPv6 for the Docker Network:

Configure your Docker network to support IPv6 addressing. This allows containers to resolve hostnames using IPv6.

Result: After applying these changes, the issue was resolved.

Final Dockerized Project Setup

The final Dockerization setup for the project looked like this:

Dockerfile

FROM node:18.7
ARG NODE_OPTIONS="max-old-space-size=3072 --dns-result-order=ipv4first"
ENV NODE_OPTIONS=$NODE_OPTIONS
ENV PORT=3000
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install app dependencies
COPY package*.json /usr/src/app/
RUN npm install --legacy-peer-deps
# add app
COPY . /usr/src/app
RUN npm run build
EXPOSE 3000
# start app
CMD ["npm", "start"]

 

The docker-compose.yml

 

services:
backend-service:
build:
context: ./backend
dockerfile: Dockerfile
image: backend:latest
container_name: backend-container
mem_limit: 2g
env_file:
- ./backend/.env
restart: always
networks:
default:
frontend-service:
build:
context: ./frontend
dockerfile: Dockerfile
network: host
image: frontend:latest
restart: always
container_name: frontend-container
mem_limit: 2g
ports:
- 80:3000
env_file:
- ./frontend/.env
depends_on:
- backend-service
networks:
default:
networks:
default:
name: project-network
driver: bridge
enable_ipv6: true
ipam:
config:
- subnet: 2001:db8::/64