Container Documentation

Docker Deployment Guide

This guide covers containerizing applications with Docker: writing Dockerfiles, building images, running containers, and production deployment patterns.

Prerequisites

  • Docker Engine 20.10+ installed
  • Docker Compose v2 (optional, for multi-container setups)
  • Basic familiarity with command-line operations

Quick Start

Pull and run the official image:

# Pull the latest image
docker pull example/app:latest

# Run with required environment variables
docker run -d \
  --name my-app \
  -p 8080:8080 \
  -e DATABASE_URL="postgres://..." \
  -e API_KEY="your-key" \
  example/app:latest

Verify the container is running:

docker ps
docker logs my-app

Writing a Dockerfile

A well-structured Dockerfile for a Node.js application:

# Use specific version for reproducibility
FROM node:20-alpine AS builder

# Set working directory
WORKDIR /app

# Copy dependency files first (better layer caching)
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application code
COPY . .

# Build the application
RUN npm run build

# Production stage - smaller final image
FROM node:20-alpine AS production

WORKDIR /app

# Create non-root user for security
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

# Copy only production artifacts
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# Switch to non-root user
USER appuser

# Expose the application port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

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

Dockerfile Best Practices

  • Use specific base image tags — Avoid :latest; pin to :20-alpine or a SHA digest.
  • Multi-stage builds — Keep build tools out of production images.
  • Layer caching — Copy dependency files before source code.
  • Non-root user — Never run as root in production.
  • Health checks — Enable orchestrators to detect unhealthy containers.
  • .dockerignore — Exclude node_modules, .git, and test files.

Building Images

# Build with a tag
docker build -t my-app:1.0.0 .

# Build with build arguments
docker build \
  --build-arg NODE_ENV=production \
  --build-arg VERSION=1.0.0 \
  -t my-app:1.0.0 .

# Build for multiple platforms
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t my-app:1.0.0 \
  --push .

Running Containers

Basic Run

docker run -d \
  --name my-app \
  -p 8080:8080 \
  my-app:1.0.0

With Environment Variables

# From command line
docker run -d \
  -e DATABASE_URL="postgres://user:pass@host:5432/db" \
  -e LOG_LEVEL="info" \
  my-app:1.0.0

# From env file
docker run -d \
  --env-file .env.production \
  my-app:1.0.0

With Volumes

# Named volume (persistent data)
docker run -d \
  -v app-data:/app/data \
  my-app:1.0.0

# Bind mount (development)
docker run -d \
  -v $(pwd)/config:/app/config:ro \
  my-app:1.0.0

Resource Limits

docker run -d \
  --memory="512m" \
  --cpus="1.0" \
  --restart=unless-stopped \
  my-app:1.0.0

Docker Compose

For multi-container applications:

# docker-compose.yml
version: "3.8"

services:
  app:
    image: my-app:1.0.0
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://postgres:password@db:5432/app
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3

  db:
    image: postgres:15-alpine
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=app
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    volumes:
      - redis-data:/data

volumes:
  postgres-data:
  redis-data:

Compose Commands

# Start all services
docker compose up -d

# View logs
docker compose logs -f app

# Stop all services
docker compose down

# Stop and remove volumes
docker compose down -v

Troubleshooting

Container won't start

# Check logs
docker logs my-app

# Check recent events
docker events --since 5m --filter container=my-app

# Inspect container config
docker inspect my-app

Container exits immediately

  • Check the exit code: docker inspect my-app --format='{{.State.ExitCode}}'
  • Exit code 1: Application error — check logs
  • Exit code 137: OOM killed — increase memory limit
  • Exit code 143: SIGTERM — graceful shutdown

Network issues

# List networks
docker network ls

# Inspect network
docker network inspect bridge

# Test connectivity from container
docker exec my-app ping -c 3 other-service

Disk space issues

# Check disk usage
docker system df

# Remove unused resources
docker system prune -a --volumes

Security Checklist

  • Never run as root — Use a non-root USER in Dockerfile
  • Scan images for vulnerabilities — Use docker scout or Trivy
  • Use read-only filesystem — Add --read-only where possible
  • Limit capabilities — Use --cap-drop=ALL and add only what's needed
  • Don't store secrets in images — Use environment variables or secrets management
  • Keep base images updated — Rebuild regularly with patched bases

Related Samples

This is a sample article to demonstrate how I write.