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