Chapter 8 — Containers & Orchestration

Containers package your application with all its dependencies into a portable, reproducible unit. This solves "works on my machine" permanently.

Why Containers

Dockerfile Best Practices

# Multi-stage build — keeps final image small
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2: Production image
FROM node:20-alpine
WORKDIR /app

# Don't run as root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Copy only what's needed
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
Key Dockerfile principles:
• Use specific base image tags (node:20-alpine, not node:latest)
• Multi-stage builds to minimize image size
• Copy package.json first (layer caching for dependencies)
• Run as non-root user
• Add HEALTHCHECK for orchestrators
• Use .dockerignore to exclude node_modules, .git, etc.

Docker Compose (Multi-Service Apps)

# docker-compose.yml — typical web app stack
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
      interval: 5s
      timeout: 3s
      retries: 5

  cache:
    image: redis:7-alpine
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app

volumes:
  pgdata:
# Commands
docker compose up -d          # Start all services
docker compose logs -f app    # Follow app logs
docker compose ps             # Status of all services
docker compose down           # Stop and remove
docker compose up -d --build  # Rebuild and restart

Container Registries

RegistryFree TierBest For
Docker Hub1 private repoPublic images, open source
GitHub Container Registry (ghcr.io)Generous freeGitHub-based projects
AWS ECR500MB freeAWS deployments
Google Artifact Registry500MB freeGCP deployments

Kubernetes — When You Need Orchestration

Kubernetes (K8s) manages containers at scale: scheduling, scaling, self-healing, service discovery, rolling updates.

You probably don't need Kubernetes. Docker Compose on a single VPS handles most workloads. K8s is for: multiple services, multiple teams, auto-scaling requirements, or when you need zero-downtime deployments with automated rollbacks. The operational overhead is significant.

Core Kubernetes Concepts

# Pod — smallest deployable unit (one or more containers)
# Deployment — manages replica sets, rolling updates
# Service — stable network endpoint for pods
# Ingress — HTTP routing from outside the cluster

# Example: Deployment + Service + Ingress
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: ghcr.io/you/myapp:v1.2.3
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 10
          periodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-svc
            port:
              number: 80

Managed Kubernetes Services

ServiceProviderStarting CostNotes
EKSAWS$0.10/hr control plane + nodesMost popular, complex
GKEGoogle1 free zonal cluster + nodesBest K8s experience (Google made K8s)
AKSAzureFree control plane + nodesGood for Microsoft shops
DOKSDigitalOceanFree control plane + $12/nodeSimplest managed K8s