Features of Dockerizing Applications Written in Next.js
Nov 28, 2024
4 min read
6 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

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

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