Docker for Backend Engineers: Beyond the Basics

4 min read
devopsdockerinfrastructure

Docker for Backend Engineers: Beyond the Basics

Docker is ubiquitous in backend engineering. But most of us stop at basic commands. Here's what you should know to use Docker effectively in production.

Multi-Stage Builds

The difference between a 1GB image and a 50MB image often comes down to multi-stage builds.

Before: Single-Stage Build

FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]

Problem: Includes dev dependencies, source files, and build tools in the final image.

After: Multi-Stage Build

# 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
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

Result: Smaller images, faster deployments, reduced attack surface.

Layer Caching Strategy

Docker builds layers sequentially. Leverage this for faster builds.

Optimization Rules

  1. Copy package files first - Install dependencies before copying source code
  2. Separate static assets - Cache what doesn't change
  3. Order matters - Most stable layers first

Example: Optimized Python Build

FROM python:3.11-slim

# Install system dependencies (rarely changes)
RUN apt-get update && apt-get install -y \\
    gcc \\
    && rm -rf /var/lib/apt/lists/*

# Install Python dependencies (changes occasionally)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code (changes frequently)
COPY . /app
WORKDIR /app

CMD ["python", "main.py"]

Health Checks

Always include health checks in production containers.

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

This enables:

  • Automatic container restarts
  • Load balancer integration
  • Monitoring and alerting

.dockerignore is Critical

Treat it like .gitignore but for containers.

node_modules
npm-debug.log
.git
.env
*.md
.vscode
coverage
.DS_Store

Why? Faster builds, smaller contexts, no accidentally leaking secrets.

Environment-Specific Images

Use build arguments for environment-specific builds.

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

RUN if [ "$NODE_ENV" = "development" ]; then \\
      npm install; \\
    else \\
      npm ci --only=production; \\
    fi

Build for different environments:

# Production
docker build --build-arg NODE_ENV=production -t app:prod .

# Development
docker build --build-arg NODE_ENV=development -t app:dev .

Security Best Practices

1. Use Official Base Images

FROM python:3.11-slim  # Official Python image
FROM node:18-alpine    # Minimal Node image

2. Run as Non-Root User

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

3. Scan for Vulnerabilities

docker scan my-image:latest

Or use tools like:

  • Trivy
  • Snyk
  • Anchore

Docker Compose for Local Development

docker-compose.yml for consistent dev environments:

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
    depends_on:
      - db
    volumes:
      - ./src:/app/src  # Hot reload

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Production Deployment Checklist

Before deploying:

  • [ ] Multi-stage build implemented
  • [ ] Image size optimized (< 200MB for most apps)
  • [ ] Health checks configured
  • [ ] Non-root user specified
  • [ ] Secrets managed externally (not in image)
  • [ ] Logs sent to stdout/stderr
  • [ ] Resource limits defined (CPU, memory)
  • [ ] Vulnerability scan passed

Common Pitfalls

1. Building from source in production

Don't: Install dev tools in production images
Do: Use multi-stage builds to separate build and runtime

2. Hardcoded secrets

Don't: ENV API_KEY=secret123
Do: Use environment variables or secret management

3. Running as root

Don't: Leave default user
Do: Create and use a non-root user

Conclusion

Docker is more than a deployment tool—it's a way to ensure consistency across environments. Master these patterns and your deployments become predictable, secure, and fast.

Key Takeaways:

  • Multi-stage builds reduce image size by 80%+
  • Layer caching speeds up builds dramatically
  • Security must be built in, not bolted on
  • Health checks enable self-healing systems

The difference between good and great Docker usage is attention to these details.