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
CMD [“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
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.
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