Docker Best Practices for Production Environments
Essential Docker practices to optimize security, performance, and maintainability in production deployments
Docker has revolutionized application deployment, but running containers in production requires careful consideration of security, performance, and maintainability. Let's explore the essential best practices.
Image Optimization
Use Multi-Stage Builds
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
Choose Minimal Base Images
Use Alpine or distroless images to minimize attack surface:
# Instead of
FROM ubuntu:latest
# Use
FROM node:18-alpine
# or
FROM gcr.io/distroless/nodejs18-debian11
Optimize Layer Caching
Order Dockerfile instructions from least to most frequently changing:
FROM node:18-alpine
# Dependencies (changes rarely)
COPY package*.json ./
RUN npm ci --only=production
# Application code (changes often)
COPY . .
# Build (depends on code)
RUN npm run build
Security Best Practices
Run as Non-Root User
Never run containers as root in production:
FROM node:18-alpine
# Create app user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
COPY --chown=nodejs:nodejs . .
# Switch to non-root user
USER nodejs
EXPOSE 3000
CMD ["node", "server.js"]
Scan Images for Vulnerabilities
docker run aquasec/trivy image myapp:latest
snyk container test myapp:latest
Use .dockerignore
Prevent sensitive files from being added to images:
# .dockerignore
node_modules
npm-debug.log
.git
.env
.env.local
*.md
.DS_Store
Sign and Verify Images
export DOCKER_CONTENT_TRUST=1
docker push myregistry/myapp:latest
docker trust inspect myregistry/myapp:latest
Performance Optimization
Minimize Image Layers
Combine related RUN commands:
# Instead of
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
# Use
RUN apt-get update && \
apt-get install -y package1 package2 && \
rm -rf /var/lib/apt/lists/*
Use Build Cache Effectively
# Copy only dependency files first
COPY package*.json ./
RUN npm ci --only=production
# Then copy application code
COPY . .
Implement Health Checks
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js || exit 1
Resource Management
Set Resource Limits
docker run -d \
--memory="512m" \
--cpus="0.5" \
--restart=unless-stopped \
myapp:latest
Use Docker Compose for Multi-Container Apps
version: "3.8"
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M
reservations:
cpus: "0.25"
memory: 256M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
Logging and Monitoring
Structured Logging
Configure applications to log to stdout/stderr:
console.log(
JSON.stringify({
timestamp: new Date().toISOString(),
level: "INFO",
message: "User logged in",
userId: user.id,
})
);
Centralized Log Management
docker run -d \
--log-driver=fluentd \
--log-opt fluentd-address=localhost:24224 \
myapp:latest
CI/CD Integration
Automated Image Building
name: Docker Build
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Container Orchestration
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
containers:
- name: myapp
image: myregistry/myapp:latest
ports:
- containerPort: 3000
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
Conclusion
Following these Docker best practices ensures your containers are:
- Secure: Running with minimal privileges and scanned for vulnerabilities
- Efficient: Optimized for size and performance
- Reliable: With proper health checks and resource limits
- Maintainable: With clear structure and documentation
Start implementing these practices incrementally in your projects to see immediate improvements in your container workflows.
Additional Resources