Docker Guide 2026 — Containerize Node.js, Python, and Next.js Apps
Advertisement
Introduction
Why This Matters
Docker is the standard unit of deployment in 2026. Containerizing your app guarantees identical behavior across development, CI, and production. Multi-stage builds keep images small and secure. Docker Compose replaces complex local setup scripts.
Production Dockerfile for Node.js
Multi-stage builds reduce image size by 60–80%:
# Dockerfile
FROM node:22-alpine AS base
WORKDIR /app
# Install dependencies only when package.json changes
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Build stage
FROM base AS builder
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage — minimal image
FROM base AS runner
ENV NODE_ENV=production
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]Next.js Dockerfile
FROM node:22-alpine AS base
WORKDIR /app
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
FROM base AS runner
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup -S nodejs && adduser -S nextjs -G nodejs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]// next.config.ts — enable standalone output
const config = {
output: 'standalone',
}
export default configDocker Compose for Local Development
# docker-compose.yml
services:
app:
build:
context: .
target: builder # Use build stage for hot reload
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
command: npm run dev
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
volumes:
postgres_data:
redis_data:Useful Docker Commands
# Build and tag
docker build -t my-app:1.0.0 .
docker build -t my-app:latest --target runner .
# Run with env file
docker run -p 3000:3000 --env-file .env my-app:latest
# Compose commands
docker compose up -d # Start detached
docker compose logs -f app # Follow app logs
docker compose exec app sh # Shell into container
docker compose down -v # Stop and remove volumes
# Inspect image layers
docker history my-app:latest
docker image inspect my-app:latest
# Clean up
docker system prune -af
docker volume prune -f.dockerignore
node_modules
.next
.git
.env
.env.local
*.md
coverage
dist
.turboCommon Mistakes
- Not using a
.dockerignore— copiesnode_modulesinto the image, bloating it by hundreds of MB - Running the container as root — use a non-root user with
USERdirective - Using
npm installinstead ofnpm ci—ciinstalls exact versions frompackage-lock.json - Not using multi-stage builds — dev dependencies end up in the production image
- Using
latesttags for base images in production — pin to a specific version for reproducibility
Best Practices
- Pin base image versions (
node:22-alpine, notnode:alpine) for reproducible builds - Use Alpine variants to keep images small — typically 5–50 MB vs 500+ MB for Debian
- Add
HEALTHCHECKinstructions so orchestrators know when a container is truly ready - Store secrets in environment variables or a secrets manager, never
COPY-ed into the image - Use Docker BuildKit (
DOCKER_BUILDKIT=1) for parallel build stages and better cache control
Key Takeaways
- Multi-stage builds separate dev dependencies from the production image, reducing size by 60–80%
- Non-root users in Docker prevent privilege escalation if the container is compromised
npm ciinstalls exact locked dependencies — always prefer it overnpm installin CI and Dockeroutput: 'standalone'in Next.js generates a self-contained production build with no extranode_modulescopy neededHEALTHCHECKlets Docker and Kubernetes determine when the container is accepting traffic- Docker Compose replaces complex README setup instructions for local development
.dockerignoreis as important as.gitignore— always excludenode_modules,.git, and.env- Pin image digest (
node:22-alpine@sha256:...) in production for fully reproducible builds
Advertisement
Related reading
Kubernetes Guide 2026 — Deploy, Scale, and Manage Containers in Production5 min readContainer Security — From Dockerfile to Runtime Protection8 min readAI Tools for DevOps — Generate Dockerfiles, CI/CD Pipelines, and Kubernetes Manifests5 min readCI/CD Pipeline Design Guide 2026 — From Commit to Production in Under 15 Minutes6 min readPM2 Complete Guide 2026 — Node.js Process Manager for Production Servers7 min readDevOps Complete Roadmap 2025 — From Zero to Production Engineer6 min read