← Back to Blog
DevOps2026-01-2012 min read

Docker for Production Deployments

DockerDevOpsDeployment

Docker has become essential for modern software deployment. Learn how to containerize applications and deploy them reliably to production environments.

What is Docker?

Docker is a containerization platform that packages your application, dependencies, and configuration into a single unit called a container. This ensures your application runs consistently across different environments.

Why Docker?

  • Consistency - "It works on my machine" problem solved
  • Isolation - Each container runs independently
  • Scalability - Easy to run multiple instances
  • Efficiency - Lightweight compared to virtual machines

Docker Fundamentals

Images vs Containers

Docker Image: A blueprint containing your application code and dependencies Docker Container: A running instance of an image

Think of it like a class (image) and an object (container).

Basic Dockerfile

# Start from a base image
FROM node:18-alpine

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application code
COPY . .

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
  CMD node healthcheck.js

# Run application
CMD ["node", "server.js"]

Production Best Practices

1. Multi-Stage Builds

Reduce image size by using multiple build stages:

# Build stage
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]

This reduces final image size from 1GB to ~200MB.

2. Security Considerations

Use Non-Root User

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
USER nodejs

Scan for Vulnerabilities

# Scan image for security issues
docker scan myapp:latest

# Use Trivy for detailed scanning
trivy image myapp:latest

Keep Base Images Updated

# Check for updates regularly
docker pull node:18-alpine

3. Optimize Image Size

Techniques:

  • Use slim/alpine base images
  • Remove unnecessary files
  • Combine RUN commands to reduce layers
  • Use .dockerignore file
node_modules
npm-debug.log
.git
.env
.vscode
__tests__
coverage

Docker Compose for Local Development

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/myapp
      - NODE_ENV=development
    depends_on:
      - db
    volumes:
      - .:/app
      - /app/node_modules

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Deploying to Production

Option 1: Docker Swarm

# Initialize swarm
docker swarm init

# Deploy stack
docker stack deploy -c docker-compose.prod.yml myapp

Option 2: Kubernetes

# deployment.yaml
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: myregistry/myapp:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

Option 3: Docker Hub + CI/CD

# GitHub Actions workflow
name: Deploy to Docker Hub

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: myregistry/myapp:latest
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

Monitoring and Logging

Health Checks

# Check container health
docker inspect --format='{{.State.Health.Status}}' myapp

View Logs

# Follow logs in real-time
docker logs -f myapp

# Get last 100 lines
docker logs --tail=100 myapp

Monitoring Tools

  • Prometheus - Metrics collection
  • Grafana - Visualization
  • ELK Stack - Logging
  • DataDog - Full observability

Common Pitfalls to Avoid

IssueSolution
Running as rootCreate non-root user
Large imagesUse multi-stage builds
Hardcoded secretsUse environment variables
No health checksImplement HEALTHCHECK
Outdated base imagesUpdate regularly
No resource limitsSet CPU/memory limits

Performance Tips

# Build with buildkit for better caching
export DOCKER_BUILDKIT=1
docker build -t myapp:latest .

# Use --cache-from to speed up builds
docker build --cache-from myapp:latest -t myapp:latest .

# Limit build context with .dockerignore

Conclusion

Docker is powerful but requires careful attention to best practices. Follow these guidelines for secure, efficient, scalable deployments.

Key Takeaways:

  • Use multi-stage builds to reduce image size
  • Never run containers as root
  • Implement health checks
  • Use .dockerignore to reduce context
  • Monitor and log everything
  • Keep base images updated

Ready to containerize your application?