Docker 🐋
Why Docker & How to Dockerize a NextJS App
Why Use Docker?
When you work as a software engineer, having a reliable development environment is crucial. Engineers should be able to work without worrying about their Node.js version or manually installing dependencies. This is where Docker comes in. Docker is a powerful tool that simplifies software deployment by containerizing applications and their dependencies.
Here’s why Docker is essential:
-
Consistency Across Environments: Docker ensures your application behaves the same way across different systems, eliminating the "it works on my machine" issue.
-
Simplified Dependency Management: Docker encapsulates dependencies, avoiding manual setup.
-
Efficient Resource Utilization: Docker containers are lightweight and share the host OS kernel, unlike traditional virtual machines.
-
Scalability and Deployment: Docker enables horizontal scaling with tools like Docker Swarm and Kubernetes.
-
Portability: Containers can seamlessly move between development, staging, and production environments.
How to Dockerize a Next.js App
Here’s a step-by-step guide to containerizing a Next.js app using Docker.
1. Create a Next.js App
If you don’t already have one:
1npx create-next-app@latest my-next-app 2cd my-next-app
2. Create a Dockerfile
In your project root:
1# --- Base Image --- 2FROM node:20.18.0-bookworm AS base 3 4# --- Set Environment Variables --- 5ENV NODE_ENV=development \ 6 SHELL=/bin/bash \ 7 TMP_DIR=/mnt/tmp \ 8 WORKDIR=/app 9 10WORKDIR ${WORKDIR} 11 12# --- System Dependencies --- 13RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/* 14 15# --- Install pnpm --- 16RUN npm install -g pnpm@9.14.1 17 18# --- Set Up pnpm Cache --- 19ENV npm_config_cache="${TMP_DIR}/npm-cache" \ 20 npm_config_store_dir="${TMP_DIR}/pnpm-store" 21 22# Copy dependency files and install packages 23COPY package.json pnpm-lock.yaml ./ 24RUN pnpm install --frozen-lockfile 25 26# Copy app source code 27COPY . . 28 29# --- Development Stage --- 30FROM base AS dev 31CMD ["pnpm", "dev"] 32 33# --- Production Stage --- 34FROM base AS prod 35ENV NODE_ENV=production 36RUN pnpm run build 37CMD ["pnpm", "start"] 38 39# Use tini for process management 40ENTRYPOINT ["/usr/bin/tini", "-sg", "--"] 41 42EXPOSE 3000
3. Create .dockerignore
To reduce build context:
1node_modules 2build 3.dockerignore 4Dockerfile 5docker-compose.yml 6.git
4. Create docker-compose.yml
1services: 2 next-app: 3 container_name: blog-portfolio 4 build: 5 context: . 6 dockerfile: Dockerfile 7 target: dev 8 ports: 9 - "3000:3000" 10 volumes: 11 - .:/app:cached 12 - /app/node_modules 13 working_dir: /app 14 stdin_open: true 15 tty: true 16 networks: 17 - react-network 18 environment: 19 - CHOKIDAR_USEPOLLING=true 20 - WATCHPACK_POLLING=true 21 - NEXTJS_IGNORE_ESLINT=1 22 command: ["pnpm", "dev", "--turbo"] 23 24networks: 25 react-network: 26 driver: bridge
Why This Setup?
-
Multi-Stage Builds: Separate stages for base, development, and production.
-
Tini for Signal Handling: Prevents zombie processes.
-
Docker Cache Optimization: Layer caching speeds up builds.
-
Docker Compose: Simplifies orchestration.
5. Build the Image
1docker build -t my-next-app .
6. Run the Container
1docker run -p 3000:3000 my-next-app
7. Use Docker Compose
1 2# Uses BuildKit for faster, optimized builds 3export DOCKER_BUILDKIT=1 4export COMPOSE_DOCKER_CLI_BUILD=1 5 6docker compose up --build
8. Create Helper Scripts
up
1#!/bin/bash 2docker compose up
down
1#!/bin/bash 2docker compose down
build
1#!/bin/bash 2docker compose build
Now you can run your docker commands like the following:
1./up 2./down 3./build
Troubleshooting
Here are some common issues and fixes:
-
Container immediately exits: Ensure
CMD
andENTRYPOINT
are correctly defined. Usetty: true
andstdin_open: true
in Compose for interactive shells. -
File changes not reflected: Double-check volume mounting syntax and ensure you’re not accidentally overriding with
node_modules
. -
Permission errors: Files created inside containers might be owned by root. Use user-specific UID/GID settings if needed.
-
App not accessible: Ensure port mapping (
-p 3000:3000
) is correct and app is listening on0.0.0.0
. -
Memory issues: Docker may throttle memory. Check Docker Desktop settings or adjust
mem_limit
indocker-compose.yml
.
Production Tips
-
Use
NODE_ENV=production
: This minimizes dependencies and ensures performance optimizations. -
Use multi-stage builds: As shown above, separating build and run stages reduces image size.
-
Don’t mount volumes: In production, avoid
volumes:
to ensure a read-only, consistent image. -
Use a process manager: If needed, consider using
pm2
ortini
(as shown) to manage signals and child processes. -
Enable health checks: Add a
healthcheck
directive to ensure your container is running correctly. -
Security: Scan images for vulnerabilities with tools like
docker scan
orTrivy
.
Additional Resources
If you’re just getting started with Docker and prefer visual learning, this video is a great introduction:
Prefer reading documentation? The official Docker docs are comprehensive and beginner-friendly: Official Docker Documentation
Final Thoughts
Docker is a game changer for consistent, scalable development. This guide shows how to Dockerize a Next.js app from start to finish. Once set up, your dev and production workflows become more reliable and repeatable.
Get started today and level up your Next.js workflow with Docker! 🐳