|$ curl https://forge-ai.dev/api/markdown?path=docs/ci/docker
$cat docs/docker-&-containers.md
updated Recently·35 min read·published

Docker & Containers

DockerDevOpsIntermediate
Introduction

Docker containers package applications with their dependencies into isolated, reproducible units. Containers solve the "works on my machine" problem by bundling the runtime, libraries, and configuration into a single image that runs identically everywhere.

For web applications, Docker provides consistency across development, CI, and production environments, simplifies scaling, and enables modern deployment patterns like orchestrators (Kubernetes, ECS) and serverless containers.

Dockerfile Fundamentals

A Dockerfile is a text document containing instructions to build a Docker image. Each instruction creates a layer in the image, and layers are cached to speed up subsequent builds.

Dockerfile
DOCKERFILE
1# Base image — pin to specific version, never use "latest"
2FROM node:20-slim AS base
3
4# Set working directory
5WORKDIR /app
6
7# Copy package files first (layer caching)
8COPY package.json package-lock.json ./
9
10# Install production dependencies only
11RUN npm ci --only=production
12
13# Copy application source
14COPY . .
15
16# Build the application
17RUN npm run build
18
19# Expose the port
20EXPOSE 3000
21
22# Set the runtime user (security best practice)
23USER node
24
25# Start the application
26CMD ["node", "dist/server.js"]

info

Order Dockerfile instructions from least to most frequently changing. Package dependencies change less often than application code, so copying package.json before COPY . maximizes layer cache hits and speeds up builds by 10-50x.
Multi-Stage Builds

Multi-stage builds use multiple FROM statements to separate build-time dependencies from the runtime image. The result is dramatically smaller production images — a Next.js app can go from 2GB to under 200MB.

Dockerfile.multistage
DOCKERFILE
1# Stage 1: Install dependencies
2FROM node:20-slim AS deps
3WORKDIR /app
4COPY package.json package-lock.json ./
5RUN npm ci
6
7# Stage 2: Build the application
8FROM node:20-slim AS builder
9WORKDIR /app
10COPY --from=deps /app/node_modules ./node_modules
11COPY . .
12RUN npm run build
13
14# Stage 3: Production runtime (minimal)
15FROM node:20-slim AS runner
16WORKDIR /app
17
18ENV NODE_ENV=production
19
20# Copy only production artifacts
21COPY --from=builder /app/public ./public
22COPY --from=builder /app/.next/standalone ./
23COPY --from=builder /app/.next/static ./.next/static
24
25EXPOSE 3000
26USER node
27CMD ["node", "server.js"]

Image size comparison for a typical Next.js app:

ApproachImage SizeBuild Time
Single-stage (dev deps included)~2.1 GB~60s
Single-stage (production deps only)~800 MB~50s
Multi-stage (runner only)~150 MB~60s

best practice

Use node:XX-slim or node:XX-alpine as the base image for production. Avoid full node:XX (includes build tools and system packages) and never use node:latest — pin to a specific major version like node:20-slim.
.dockerignore

The .dockerignore file excludes files and directories from the Docker build context, reducing image size and preventing secrets from leaking into layers.

.dockerignore
DOCKERIGNORE
1# Git
2.git
3.gitignore
4
5# Dependencies (reinstalled in Docker)
6node_modules
7
8# Next.js
9.next
10
11# Environment files (may contain secrets)
12.env
13.env.local
14.env.*.local
15
16# IDE
17.idea
18.vscode
19*.swp
20
21# OS files
22.DS_Store
23Thumbs.db
24
25# Logs
26npm-debug.log*
27yarn-debug.log*
28yarn-error.log*
29
30# Testing
31coverage
32__tests__
33*.test.js
34*.spec.js

danger

Never include .env files in Docker images. Environment variables containing secrets will be embedded in image layers and visible to anyone with image access. Use Docker secrets or runtime environment variables instead.
Docker Compose for Development

Docker Compose defines multi-container applications using a YAML file. It is ideal for local development environments that require databases, caches, or message queues alongside your application.

docker-compose.yml
YAML
1version: "3.9"
2
3services:
4 app:
5 build:
6 context: .
7 dockerfile: Dockerfile.dev
8 ports:
9 - "3000:3000"
10 volumes:
11 - .:/app
12 - /app/node_modules # Anonymous volume for node_modules
13 environment:
14 - NODE_ENV=development
15 - DATABASE_URL=postgres://user:pass@db:5432/app
16 - REDIS_URL=redis://cache:6379
17 depends_on:
18 - db
19 - cache
20
21 db:
22 image: postgres:16-alpine
23 volumes:
24 - pgdata:/var/lib/postgresql/data
25 environment:
26 POSTGRES_USER: user
27 POSTGRES_PASSWORD: pass
28 POSTGRES_DB: app
29 ports:
30 - "5432:5432"
31
32 cache:
33 image: redis:7-alpine
34 ports:
35 - "6379:6379"
36
37volumes:
38 pgdata:

Start the development environment with:

compose-commands.sh
Bash
1# Build and start all services
2docker compose up --build
3
4# Run in detached mode
5docker compose up -d
6
7# View logs
8docker compose logs -f app
9
10# Execute commands in the app container
11docker compose exec app npm run migrate
12
13# Stop and remove all containers and volumes
14docker compose down -v
Container Registries

Container registries store and distribute Docker images. Push your built images to a registry so they can be pulled by CI/CD systems and deployment targets.

RegistryPublic/PrivateIntegration
Docker HubBothDefault, largest community
GitHub Container Registry (GHCR)BothNative GitHub Actions integration
AWS ECRPrivateAWS native, IAM auth
Google Artifact RegistryPrivateGCP native
registry-commands.sh
Bash
1# Build and tag an image
2docker build -t my-app:latest .
3docker tag my-app:latest ghcr.io/myorg/my-app:v1.0.0
4
5# Login to GitHub Container Registry
6echo $PAT | docker login ghcr.io -u myorg --password-stdin
7
8# Push to registry
9docker push ghcr.io/myorg/my-app:v1.0.0
10
11# Pull and run
12docker pull ghcr.io/myorg/my-app:v1.0.0
13docker run -p 3000:3000 ghcr.io/myorg/my-app:v1.0.0
🔥

pro tip

Tag images semantically: v1.2.3 for releases, sha-abc123 for every commit, and latest for the current stable. This enables reproducible deployments, rollbacks to any specific commit, and clear version tracking.
Dockerizing Next.js Applications

Next.js applications benefit from specific Docker configurations. The official Next.js Docker example uses output file tracing to copy only required files into the production image.

Dockerfile.nextjs
DOCKERFILE
1FROM node:20-slim AS base
2WORKDIR /app
3
4# Dependencies
5FROM base AS deps
6COPY package.json package-lock.json ./
7RUN npm ci
8
9# Builder
10FROM base AS builder
11COPY --from=deps /app/node_modules ./node_modules
12COPY . .
13RUN npm run build
14
15# Runner
16FROM base AS runner
17ENV NODE_ENV=production
18ENV NEXT_TELEMETRY_DISABLED=1
19
20RUN addgroup --system --gid 1001 nodejs
21RUN adduser --system --uid 1001 nextjs
22
23# Copy standalone output (Next.js output: "standalone")
24COPY --from=builder /app/.next/standalone ./
25COPY --from=builder /app/public ./public
26COPY --from=builder /app/.next/static ./.next/static
27
28USER nextjs
29EXPOSE 3000
30CMD ["node", "server.js"]

Enable standalone output in next.config.js:

next.config.js
JavaScript
1// next.config.js
2/** @type {import('next').NextConfig} */
3const nextConfig = {
4 output: "standalone",
5 // ... other config
6};
7
8module.exports = nextConfig;
Docker Best Practices
Use specific base image tags (node:20-slim) — never use :latest which changes unpredictably.
Combine RUN commands with && to reduce layers: RUN apt-get update && apt-get install -y pkg && rm -rf /var/lib/apt/lists/*.
Run as a non-root user (USER node or USER 1001) for security.
Use multi-stage builds to minimize production image size.
Add HEALTHCHECK instruction for container orchestration health probes.
Use .dockerignore to exclude unnecessary files from the build context.
Use COPY --chown to set correct ownership instead of RUN chown.
Pin base images with digest SHA: node:20-slim@sha256:abc123... for repeatable builds.
Scan images for vulnerabilities before pushing: docker scout quickstart, trivy image, or snyk.
Label images with metadata: maintainer, version, build-date, commit-sha.
$Blueprint — Engineering Documentation·Section ID: DOCKER-01·Revision: 1.0