DevOpsLesson
DevOpsLesson

Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.

Stay Updated

Get notified about new tutorials and features.

Tutorials

  • What is DevOps?
  • Docker Tutorial
  • Terraform Tutorial
  • CI/CD Pipeline
  • All Tutorials

Roadmaps

  • DevOps Engineer
  • Cloud Engineer
  • SRE Path
  • All Roadmaps

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 DevOpsLesson. All rights reserved.

DOCKERKUBERNETESTERRAFORMAWSCI/CDLINUXGITDEVOPS ROADMAPCLOUD ROADMAPSRE ROADMAPGIT CHEATSHEETDOCKER CHEATSHEETK8S CHEATSHEETTF CHEATSHEETLINUX CHEATSHEETDOCKERFILE LINTERYAML VALIDATORCRON PARSERREGEX TESTER

Docker Tutorial

Introduction to docker
Why Use Docker?
Docker vs Virtual Machines
Installing Docker
Key Docker Concepts
Docker Images
Docker Containers
Writing Dockerfiles
Docker Volumes

Real-World Examples

PreviousPrev
Next

Complete, production-ready Dockerfiles for Node.js, Python, Go, React, and more - annotated with the reasoning behind every decision.

Real-World Examples

Theory is useful, but seeing a complete, production-ready Dockerfile - and understanding every decision - is what makes the knowledge stick. Each example below is annotated and ready to adapt.


Node.js API (TypeScript)

A TypeScript API compiled to JavaScript, running in production with only the built output and production dependencies.

# ── Build Stage ───────────────────────────────────────────────
FROM node:20-alpine AS builder

WORKDIR /app

# Copy dependency manifests first (cache layer)
COPY package*.json tsconfig.json ./

# Install ALL deps including devDependencies (needed for tsc)
RUN npm ci

# Copy source and compile
COPY src ./src
RUN npm run build        # Outputs to /app/dist


# ── Production Stage ─────────────────────────────────────────
FROM node:20-alpine

WORKDIR /app

# Only production dependencies
COPY package*.json ./
RUN npm ci --omit=dev && \
    npm cache clean --force

# Copy compiled output from builder
COPY --from=builder /app/dist ./dist

# OCI metadata
LABEL org.opencontainers.image.source="https://github.com/org/api"

# Non-root user (built into node image)
RUN chown -R node:node /app
USER node

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "dist/server.js"]

What this achieves:

  • Build tools (TypeScript compiler, dev deps) stay in the builder stage only
  • Final image has: node runtime + production deps + compiled JS only
  • npm cache clean removes the npm cache from the production layer
  • Non-root node user for runtime security

Python FastAPI

A Python web API with compiled extension packages and a non-root user.

# ── Build Stage (compiles C extensions) ──────────────────────
FROM python:3.12-slim AS builder

# Install build tools
RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc libpq-dev && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Build wheels for all dependencies
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt


# ── Production Stage ─────────────────────────────────────────
FROM python:3.12-slim

# Runtime system library only (no build tools)
RUN apt-get update && \
    apt-get install -y --no-install-recommends libpq5 && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Create non-root user
RUN useradd -m -r appuser && chown appuser /app

# Install pre-built wheels (no compiler needed)
COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/* && rm -rf /wheels

# Copy application source
COPY --chown=appuser:appuser . .

USER appuser

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

What this achieves:

  • gcc and libpq-dev only in builder - not in final image
  • Pre-built wheels install without a compiler at runtime
  • libpq5 is the runtime-only PostgreSQL client library
  • --workers 4 runs multiple uvicorn worker processes

Go (Static Binary)

Go compiles to a single static binary, enabling the smallest possible runtime image.

# ── Build Stage ───────────────────────────────────────────────
FROM golang:1.22-alpine AS builder

# Install git for module fetching (if needed)
RUN apk add --no-cache git ca-certificates tzdata

WORKDIR /app

# Download dependencies first (cached)
COPY go.mod go.sum ./
RUN go mod download

# Build static binary
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-w -s" -o server .

# Verify the binary
RUN ./server --version


# ── Production Stage ─────────────────────────────────────────
FROM scratch

# Copy SSL certs (needed for HTTPS calls)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy timezone data
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

WORKDIR /app
COPY --from=builder /app/server .

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD ["/app/server", "healthcheck"]

ENTRYPOINT ["/app/server"]

What this achieves:

  • CGO_ENABLED=0 creates a fully static binary with no libc dependency
  • -ldflags="-w -s" strips debug info - reduces binary size ~30%
  • Final image: FROM scratch - just the binary, ~10MB total
  • CA certificates and timezone data copied from the builder (commonly needed)

React Frontend (Nginx)

Build the React app with Node.js, serve the static output with Nginx.

# ── Build Stage ───────────────────────────────────────────────
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

# Build args for environment-specific config
ARG VITE_API_URL=https://api.example.com
ARG VITE_APP_ENV=production
ENV VITE_API_URL=${VITE_API_URL}
ENV VITE_APP_ENV=${VITE_APP_ENV}

RUN npm run build        # Outputs to /app/dist


# ── Production Stage ─────────────────────────────────────────
FROM nginx:1.25-alpine

# Remove default nginx config
RUN rm /etc/nginx/conf.d/default.conf

# Add custom nginx config
COPY nginx.conf /etc/nginx/conf.d/app.conf

# Copy built static files
COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget -qO- http://localhost/health || exit 1

CMD ["nginx", "-g", "daemon off;"]

A minimal nginx.conf for a React SPA (handles client-side routing):

server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;

    # All routes fall back to index.html (SPA routing)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Health check endpoint
    location /health {
        return 200 'OK';
        add_header Content-Type text/plain;
    }

    # Cache static assets aggressively
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

What this achieves:

  • Build args inject API URL at build time (baked into the JS bundle)
  • Final image: Nginx + static files only - no Node.js, no source code
  • SPA routing handled by Nginx fallback to index.html

Django + Gunicorn

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1

RUN apt-get update && \
    apt-get install -y --no-install-recommends libpq5 && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

RUN useradd -m -r django && chown django /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY --chown=django:django . .

# Collect static files at build time
RUN python manage.py collectstatic --noinput

USER django

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health/')"

CMD ["gunicorn", "myproject.wsgi:application", \
     "--bind", "0.0.0.0:8000", \
     "--workers", "4", \
     "--timeout", "120"]

Key env vars for Python:

  • PYTHONDONTWRITEBYTECODE=1 - no .pyc files (saves space in the image)
  • PYTHONUNBUFFERED=1 - stdout/stderr flushed immediately (critical for logs)
  • PIP_NO_CACHE_DIR=1 - no pip cache (saves space)

Using These Examples

Each example follows the same pattern:

  1. Builder stage - everything needed to compile or bundle
  2. Production stage - the smallest base that can run the output
  3. Cache layers - manifests before source code
  4. Non-root user - security hardening
  5. HEALTHCHECK - observable health status
  6. Exec form CMD - correct signal handling

Adapt these to your project by:

  • Swapping the base image version to your current pinned version
  • Adjusting the CMD to your application's start command
  • Adding environment-specific ARG values
  • Customising the HEALTHCHECK endpoint to match your app's health route
PreviousPrev
Next

Continue Learning

ENV & ARG

Inject variables into your builds and containers. Learn the critical difference between ENV (runtime) and ARG (build-time only), and how to use both safely.

7 min·Easy

WORKDIR & EXPOSE

Set working directories cleanly and document ports correctly. Plus LABEL, USER, VOLUME, and HEALTHCHECK - the supporting instructions every production Dockerfile needs.

7 min·Easy

Best Practices

The definitive checklist for production-grade Dockerfiles - security, caching, size, linting, and the habits that separate good images from great ones.

10 min·Easy

Explore Related Topics

Ku

Kubernetes Tutorials

Orchestrate your Docker containers at scale

CI

CI/CD Tutorials

Automate Docker builds and deployments in pipelines

Try the Tool

Dockerfile Linter

Instantly lint your Dockerfile for best-practice violations and security issues.

On This Page

Real-World ExamplesNode.js API (TypeScript)Python FastAPIGo (Static Binary)React Frontend (Nginx)Django + GunicornUsing These Examples