Docker for Backend Developers: A Practical Guide

Docker has revolutionized how we develop, ship, and run applications. As a backend developer, understanding Docker is essential. Let’s explore how to containerize your backend apps.

Why Docker?#

“It works on my machine” - We’ve all heard this. Docker solves this by packaging your application with all its dependencies.

┌─────────────────────────────────────┐
│         Docker Container            │
├─────────────────────────────────────┤
│  Your Application                   │
│  Runtime (Node.js, Python, Java)    │
│  Dependencies                       │
│  System Libraries                   │
│  OS (Linux)                         │
└─────────────────────────────────────┘

Core Concepts#

Images vs Containers#

Image     → Blueprint (like a class)
Container → Running instance (like an object)

Dockerfile#

A Dockerfile is your recipe for building an image:

# 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 the port
EXPOSE 3000

# Start the application
CMD ["node", "server.js"]

Building and Running#

Build an Image#

docker build -t my-backend-app:1.0 .

Run a Container#

docker run -d -p 3000:3000 --name my-app my-backend-app:1.0

Essential Commands#

# List running containers
docker ps

# View logs
docker logs my-app

# Stop container
docker stop my-app

# Remove container
docker rm my-app

# List images
docker images

Docker Compose#

For multi-container applications, use Docker Compose:

# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
    depends_on:
      - db
      - redis

  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

volumes:
  postgres_data:

Run with:

docker-compose up -d

Best Practices#

1. Use Multi-Stage Builds#

Reduce image size by separating build and runtime:

# 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 --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

2. Use .dockerignore#

node_modules
.git
.env
*.log
Dockerfile
docker-compose.yml

3. Don’t Run as Root#

RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -D appuser
USER appuser

4. Use Specific Tags#

# ❌ Bad - unpredictable
FROM node:latest

# ✅ Good - predictable
FROM node:18.19.0-alpine

Environment Variables#

Handle configuration with environment variables:

docker run -d \
  -e DATABASE_URL=postgres://... \
  -e JWT_SECRET=mysecret \
  my-backend-app

Or use an env file:

docker run -d --env-file .env my-backend-app

Health Checks#

Add health checks to your containers:

HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -q --spider http://localhost:3000/health || exit 1

Conclusion#

Docker is a game-changer for backend development. It ensures consistency across environments, simplifies deployment, and makes your applications portable.

Start containerizing your apps today! 🐳