Docker Containers DevOps

Docker Complete Guide: Images, Containers, Volumes & Compose

MT
Mike Thompson
Platform Engineer @ FastDeploy
Mar 15, 2025
25 min read

What You'll Learn

A hands-on introduction to Docker: images, containers, Dockerfile, volumes, networks, and docker-compose — everything you need to containerize and run any application.

Why Docker? The Problem It Solves

Before Docker, the classic developer excuse was "it works on my machine". Different OS versions, library conflicts, and environment inconsistencies meant that code working on a developer's laptop would fail in staging or production.

Docker solves this by packaging your application and all its dependencies into a container — a lightweight, portable, self-sufficient unit that runs the same way everywhere.

Isolation

Each container runs in its own isolated environment

Portability

Build once, run anywhere — dev, staging, production

Speed

Starts in milliseconds vs minutes for VMs

Docker Architecture

Docker uses a client-server architecture:

  • Docker Client — The CLI you type commands into (docker build, docker run)
  • Docker Daemon (dockerd) — The background service that builds, runs, and manages containers
  • Docker Registry — Where images are stored (Docker Hub, AWS ECR, GCR)

Essential Docker Commands

bash
# === IMAGES ===
docker pull nginx:latest           # Download image from Docker Hub
docker images                      # List local images
docker image inspect nginx         # Detailed image info
docker rmi nginx:latest            # Remove image
docker build -t myapp:1.0 .        # Build image from Dockerfile
docker tag myapp:1.0 myrepo/myapp:1.0  # Tag for pushing

# === CONTAINERS ===
docker run nginx                   # Run container (foreground)
docker run -d nginx                # Run detached (background)
docker run -d -p 8080:80 nginx     # Map port host:container
docker run -d --name my-nginx nginx  # Named container
docker run --rm nginx              # Auto-remove when stopped

docker ps                          # Running containers
docker ps -a                       # All containers (including stopped)
docker stop my-nginx               # Graceful stop
docker start my-nginx              # Start stopped container
docker restart my-nginx            # Restart
docker rm my-nginx                 # Remove container
docker rm -f my-nginx              # Force remove (even if running)

# === INTERACT WITH CONTAINER ===
docker exec -it my-nginx bash      # Open bash shell in container
docker exec -it my-nginx sh        # Use sh if bash not available
docker logs my-nginx               # View container logs
docker logs -f my-nginx            # Follow logs in real-time
docker inspect my-nginx            # Detailed container config/state
docker stats                       # Live resource usage stats
docker cp my-nginx:/etc/nginx.conf ./nginx.conf  # Copy file from container

# === CLEANUP ===
docker system prune                # Remove all unused data
docker system prune -a             # Remove all unused images too
docker volume prune                # Remove unused volumes

Writing a Dockerfile

A Dockerfile is a text file with instructions for building a Docker image. Each instruction creates a new layer in the image.

Dockerfile — Node.js Application
# Stage 1: Build
FROM node:20-alpine AS builder

# Set working directory
WORKDIR /app

# Copy dependency files first (cache optimization!)
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application source
COPY . .

# Build the app
RUN npm run build

# ─────────────────────────────────────────────
# Stage 2: Production (smaller final image)
FROM node:20-alpine AS production

WORKDIR /app

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

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

# Set correct ownership
RUN chown -R appuser:appgroup /app

# Switch to non-root user
USER appuser

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
  CMD node healthcheck.js || exit 1

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

Dockerfile Best Practices

  • ✅ Use .dockerignore to exclude node_modules, .git
  • ✅ Copy package.json BEFORE source code (layer cache)
  • ✅ Use multi-stage builds to reduce final image size
  • ✅ Run as non-root user in production
  • ✅ Pin specific image tags (not :latest)
  • ✅ Add HEALTHCHECK instruction
  • ✅ One process per container

Common Mistakes

  • ❌ Running as root (security risk)
  • ❌ Storing secrets in Dockerfile (use ENV vars or secrets)
  • ❌ Using :latest tag (non-deterministic)
  • ❌ Not using .dockerignore (bloated context)
  • ❌ Combining RUN commands wastefully
  • ❌ Installing dev dependencies in production
  • ❌ Not using multi-stage builds

Docker Volumes — Persisting Data

bash
# Named volumes (managed by Docker)
docker volume create mydata
docker run -v mydata:/var/lib/postgresql/data postgres

# Bind mounts (host path → container path)
docker run -v /host/path:/container/path nginx
docker run -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro nginx  # Read-only

# Anonymous volumes (avoid — hard to manage)
docker run -v /var/lib/mysql mysql

# Volume commands
docker volume ls                   # List volumes
docker volume inspect mydata       # Volume details
docker volume rm mydata            # Remove volume
docker volume prune                # Remove unused volumes

Docker Compose — Multi-Container Apps

Docker Compose lets you define and run multi-container applications using a YAML file. Essential for local development.

docker-compose.yml — Full Stack App
version: '3.8'

services:
  # Nginx reverse proxy
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certbot/conf:/etc/letsencrypt
    depends_on:
      - app
    restart: unless-stopped

  # Node.js application
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:pass@postgres:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  # PostgreSQL database
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis cache
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

networks:
  default:
    name: myapp-network
Compose Commands
docker-compose up -d               # Start all services (detached)
docker-compose up --build          # Rebuild images before starting
docker-compose down                # Stop and remove containers
docker-compose down -v             # Also remove volumes
docker-compose logs -f app         # Follow logs for app service
docker-compose exec app sh         # Shell into app container
docker-compose ps                  # Status of all services
docker-compose restart nginx       # Restart specific service
docker-compose scale app=3         # Scale app to 3 instances

Production Tips

  • Use Docker secrets — Never put passwords in docker-compose.yml. Use Docker Secrets or environment files.
  • Resource limits — Always set memory/CPU limits: mem_limit: 512m
  • Restart policies — Use restart: unless-stopped for production services.
  • Health checks — Add healthchecks so Compose knows when a service is truly ready.
  • Multi-stage builds — A typical Node.js image can go from 1.2GB to under 150MB with multi-stage builds.

Knowledge Check

Test what you've learned in this article.

Keep Reading

D
DevOps

Docker Networking Demystified: Bridge, Host & Overlay

8 min read Read More
C
Cloud

AWS IAM Roles vs Users vs Policies

10 min read Read More
P
Programming

Understanding Python's GIL & Multiprocessing

14 min read Read More