08 - Docker Production Best Practices

Docker Production Best Practices

In earlier tutorials, we covered Docker basics: concepts, commands, image building, data management, networking, and Docker Compose. These are enough for development, but production deployments require more: security, reliability, performance, and maintainability. This article shares Docker best practices for production.

Security best practices

1. Use minimal base images

Use minimal images like Alpine or Distroless to reduce the attack surface.

# Use Alpine as a base image
FROM alpine:3.14

# Or use an official lightweight image
FROM node:16-alpine

2. Do not run containers as root

Create a non-root user in the Dockerfile and run the app as that user.

# Create a non-root user
RUN addgroup -g 1000 appuser && \
    adduser -u 1000 -G appuser -s /bin/sh -D appuser

# Switch to that user
USER appuser

# Run the app
CMD ["node", "app.js"]

3. Scan images for vulnerabilities

Use tools like Trivy, Clair, or Docker Scout to scan images.

# Scan an image with Trivy
trivy image myapp:1.0

4. Use content trust and image signing

Enable Docker Content Trust to ensure only signed images run.

# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1

# Push a signed image
docker push myapp:1.0

5. Limit container resources

Restrict CPU, memory, and other resources to prevent DoS impact.

docker run -d \
  --name myapp \
  --memory="512m" \
  --memory-swap="512m" \
  --cpu-shares=512 \
  myapp:1.0

6. Use read-only filesystems

Set the container filesystem to read-only and mount volumes only where writes are needed.

docker run -d \
  --name myapp \
  --read-only \
  --tmpfs /tmp \
  -v logs:/app/logs \
  myapp:1.0

Monitoring and logging

1. Centralized log management

Use log drivers to send container logs to centralized systems like ELK, Graylog, or Fluentd.

# Use json-file with log rotation
docker run -d \
  --name myapp \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp:1.0

# Or use the syslog driver
docker run -d \
  --name myapp \
  --log-driver syslog \
  --log-opt syslog-address=udp://logserver:514 \
  myapp:1.0

2. Container health checks

Add HEALTHCHECK in the Dockerfile or configure at runtime.

# HEALTHCHECK in Dockerfile
HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost/health || exit 1
# HEALTHCHECK at runtime
docker run -d \
  --name myapp \
  --health-cmd="curl -f http://localhost/health || exit 1" \
  --health-interval=30s \
  --health-retries=3 \
  --health-timeout=3s \
  myapp:1.0

3. Use monitoring tools

Use Prometheus, Grafana, cAdvisor, etc., to monitor resource usage and performance.

# docker-compose.yml example with Prometheus and Grafana
version: '3'

services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    depends_on:
      - prometheus

Performance optimization

1. Optimize image size

  • Use multi-stage builds
  • Combine RUN instructions to reduce layers
  • Clean unnecessary files and caches
# Multi-stage build example
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

2. Use volumes to improve I/O performance

For I/O-heavy apps, use volumes to boost performance.

docker run -d \
  --name db \
  -v db-data:/var/lib/postgresql/data \
  postgres:13

3. Set resource limits appropriately

Set CPU and memory limits according to app needs to avoid contention.

docker run -d \
  --name myapp \
  --cpus=2 \
  --memory=2g \
  myapp:1.0

High availability and scalability

1. Use an orchestrator

In production, use Kubernetes, Docker Swarm, or Nomad to manage containers.

# Initialize Docker Swarm
docker swarm init

# Deploy a service
docker service create \
  --name myapp \
  --replicas 3 \
  --publish 8080:80 \
  myapp:1.0

2. Service discovery and load balancing

Leverage built-in discovery and load balancing from orchestrators, or tools like Traefik/Nginx.

# docker-compose.yml example using Traefik as reverse proxy
version: '3'

services:
  traefik:
    image: traefik:v2.5
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro

  webapp:
    image: myapp:1.0
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.webapp.rule=Host(`example.com`)"
      - "traefik.http.routers.webapp.entrypoints=web"

3. Auto scaling containers

Adjust replica counts based on load.

# Auto scaling in Docker Swarm
docker service update \
  --replicas-max 10 \
  --replicas-min 2 \
  myapp

Backup and disaster recovery

1. Regularly back up data volumes

# Back up a data volume
docker run --rm \
  -v db-data:/source \
  -v $(pwd):/backup \
  alpine \
  tar -czf /backup/db-backup-$(date +%Y%m%d).tar.gz -C /source .

2. Auto-restart containers

docker run -d \
  --name myapp \
  --restart unless-stopped \
  myapp:1.0

3. Multi-region deployments

Deploy across regions for high availability and disaster recovery.

CI/CD best practices

1. Automate build and test

Use CI/CD tools (Jenkins, GitHub Actions, GitLab CI) to build and test images automatically.

# GitHub Actions workflow example
name: Build and Test

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Build the Docker image
      run: docker build -t myapp:${{ github.sha }} .
    - name: Test the Docker image
      run: docker run --rm myapp:${{ github.sha }} npm test

2. Blue-green or canary releases

Use blue-green or canary strategies to reduce deployment risk.

# Blue-green deployment example (Docker Swarm)
# Deploy new version (green)
docker service create \
  --name myapp-green \
  --replicas 3 \
  myapp:2.0

# After validation, update routing
docker service update \
  --publish-add 8080:80 \
  myapp-green

# Remove old version (blue)
docker service rm myapp-blue

Summary

We covered Docker production best practices spanning security, monitoring, performance, high availability, and CI/CD. By following these guidelines you can build a more stable, secure, and efficient Docker environment. Choose strategies and tools based on your scenario. In the next tutorial, we’ll look at common Docker issues and troubleshooting techniques.

Found an issue with this content?

Tell us what could be improved.