docker
Dockerfile Examples: Best Practices and Patterns
Beginner
14 minutes
October 2026
CloudOpsGuide Team
Dockerfile Examples: Best Practices and Patterns
Production-ready Dockerfile examples for Node.js, Python, Go, and Java with multi-stage builds and security best practices.
Table of Contents
- Dockerfile Basics
- Node.js Dockerfile
- Python Dockerfile
- Go Dockerfile
- Multi-Stage Builds
- Security Best Practices
- Common Pitfalls
Dockerfile Basics
A Dockerfile is a text file with instructions to build a Docker image. Every instruction creates a layer.
Essential Instructions
| Instruction | Purpose |
|---|---|
FROM | Base image |
WORKDIR | Set working directory |
COPY | Copy files into image |
RUN | Execute commands at build time |
EXPOSE | Document the port |
CMD | Default command at runtime |
ENTRYPOINT | Fixed entrypoint |
ENV | Environment variables |
USER | Run as non-root user |
Node.js Dockerfile
Production-Ready Node.js
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
WORKDIR /app
# Install dependencies first (layer caching)
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Build stage
FROM base AS build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production image
FROM base AS runner
ENV NODE_ENV=production
# Non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 appuser
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
Simple Node.js (Development)
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Python Dockerfile
Production Python (Flask/FastAPI)
FROM python:3.12-slim AS base
# Prevent Python from writing .pyc files and enable unbuffered logs
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
# Install dependencies
FROM base AS deps
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Final image
FROM base AS runner
COPY --from=deps /install /usr/local
COPY . .
# Non-root user
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
Python with Poetry
FROM python:3.12-slim AS base
ENV POETRY_VERSION=1.8.2 \
POETRY_VIRTUALENVS_CREATE=false \
PYTHONUNBUFFERED=1
RUN pip install "poetry==$POETRY_VERSION"
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry install --no-root --only main
COPY . .
USER nobody
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Go Dockerfile
Optimized Go Build
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Download dependencies first (caching)
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Static binary, no CGO
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
# Minimal runtime image
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
This produces an image under 20MB — no shell, no package manager, minimal attack surface.
Multi-Stage Builds
Multi-stage builds keep final images small by discarding build tools.
Pattern
# Stage 1: build
FROM node:20 AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# Stage 2: runtime (only what's needed)
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
Why It Matters
| Approach | Image Size |
|---|---|
| Single stage (node:20) | ~1.1 GB |
| Multi-stage (alpine) | ~200 MB |
| Multi-stage (distroless) | ~50 MB |
Security Best Practices
1. Always Use Specific Tags
# Bad — unpredictable
FROM node:latest
# Good — reproducible
FROM node:20.18.0-alpine3.20
2. Run as Non-Root
RUN addgroup -S app && adduser -S app -G app
USER app
3. Use .dockerignore
node_modules
.git
.env
*.md
dist
coverage
4. Scan Images
# Scan for vulnerabilities
docker scout cves my-image:latest
# Or with Trivy
trivy image my-image:latest
5. Minimize Layers
# Bad — 3 layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# Good — 1 layer
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
Common Pitfalls
Pitfall 1: Copying Everything First
# Bad — cache invalidates on every code change
COPY . .
RUN npm install
# Good — dependencies cached separately
COPY package*.json ./
RUN npm install
COPY . .
Pitfall 2: Secrets in Layers
# NEVER do this — secret stays in image history
RUN echo "password123" > /app/secret.txt && rm /app/secret.txt
# Use build secrets instead
RUN --mount=type=secret,id=npm_token \
npm ci --userconfig /run/secrets/npm_token
Pitfall 3: Wrong Signal Handling
# Bad — signals not forwarded to the process
CMD npm start
# Good — exec form runs process directly as PID 1
CMD ["node", "server.js"]
Pitfall 4: Missing Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
Useful Commands
# Build image
docker build -t my-app:1.0 .
# Build with specific Dockerfile
docker build -f Dockerfile.prod -t my-app:1.0 .
# Build without cache
docker build --no-cache -t my-app:1.0 .
# Check image size
docker images my-app
# Inspect layers
docker history my-app:1.0
# Dive into image layers
dive my-app:1.0
Related Articles
- Docker Build Errors: Common Issues and Fixes
- Docker Security: Scanning and Hardening
- Kubernetes YAML Examples
Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Beginner
Estimated Reading Time: 14 minutes