CloudOpsGuide
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

A Dockerfile is a text file with instructions to build a Docker image. Every instruction creates a layer.

Essential Instructions

InstructionPurpose
FROMBase image
WORKDIRSet working directory
COPYCopy files into image
RUNExecute commands at build time
EXPOSEDocument the port
CMDDefault command at runtime
ENTRYPOINTFixed entrypoint
ENVEnvironment variables
USERRun 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

ApproachImage 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


Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Beginner
Estimated Reading Time: 14 minutes