Skip to content

๐Ÿณ Docker โ€” Complete Guide โ€‹

A practical, visual, and example-driven guide to mastering Docker from zero to production.


Table of Contents โ€‹

  1. What is Docker?
  2. Why Use Docker?
  3. Core Concepts
  4. Docker Architecture
  5. Installation
  6. Essential Commands
  7. Dockerfile Deep Dive
  8. Docker Compose
  9. Real-World Examples
  10. Networking
  11. Volumes & Storage
  12. When to Use Docker
  13. Best Practices
  14. Common Pitfalls
  15. Quick Reference Cheat Sheet

What is Docker? โ€‹

Docker is a containerization platform that packages your application and all its dependencies into a lightweight, portable unit called a container. Unlike Virtual Machines (VMs), containers share the host OS kernel โ€” making them fast and resource-efficient.

FeatureVirtual MachineDocker Container
Boot TimeMinutesSeconds
SizeGBsMBs
OSFull Guest OSShared Host Kernel
IsolationStrongProcess-level
PortabilityLimitedExcellent

Why Use Docker? โ€‹

The "It works on my machine" Problem โ€‹

With Docker โ€‹


Core Concepts โ€‹

Key Terms โ€‹

TermDescriptionAnalogy
ImageRead-only blueprintRecipe / Class definition
ContainerRunning instance of an imageCooked dish / Object instance
DockerfileInstructions to build an imageRecipe steps
RegistryStorage for images (Docker Hub)App Store
VolumePersistent storage for containersExternal hard drive
NetworkCommunication layer between containersLAN network
ComposeTool to run multi-container appsOrchestrator

Docker Architecture โ€‹

Image Layers โ€‹

๐Ÿ’ก Tip: Layers are cached. If Layer 3 doesn't change, Docker reuses the cached version for layers 1โ€“3, making builds fast.


Installation โ€‹

Windows / macOS โ€‹

Download Docker Desktop

Linux (Ubuntu/Debian) โ€‹

bash
# 1. Update packages
sudo apt-get update

# 2. Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# 3. Add user to docker group (no sudo needed)
sudo usermod -aG docker $USER
newgrp docker

# 4. Verify
docker --version
docker run hello-world

Essential Commands โ€‹

Image Commands โ€‹

bash
# Pull an image from Docker Hub
docker pull node:18-alpine

# List local images
docker images

# Remove an image
docker rmi node:18-alpine

# Build an image from Dockerfile
docker build -t my-app:1.0 .

# Tag an image
docker tag my-app:1.0 myusername/my-app:1.0

# Push to Docker Hub
docker push myusername/my-app:1.0

Container Commands โ€‹

bash
# Run a container (creates + starts)
docker run nginx

# Run in background (detached mode)
docker run -d nginx

# Run with port mapping (host:container)
docker run -d -p 8080:80 nginx

# Run with name
docker run -d --name my-nginx -p 8080:80 nginx

# Run with environment variables
docker run -d -e NODE_ENV=production my-app

# Run with volume mount
docker run -d -v /host/path:/container/path nginx

# Run interactively (bash session)
docker run -it ubuntu bash

# Run and auto-remove when stopped
docker run --rm ubuntu echo "Hello!"

# List running containers
docker ps

# List all containers (including stopped)
docker ps -a

# Stop a container
docker stop my-nginx

# Start a stopped container
docker start my-nginx

# Remove a container
docker rm my-nginx

# View logs
docker logs my-nginx
docker logs -f my-nginx      # follow/stream logs

# Execute command in running container
docker exec -it my-nginx bash

# Inspect container details
docker inspect my-nginx

# Resource usage stats
docker stats

Container Lifecycle โ€‹

Cleanup Commands โ€‹

bash
# Remove all stopped containers
docker container prune

# Remove unused images
docker image prune

# Remove unused volumes
docker volume prune

# Nuclear option: remove EVERYTHING unused
docker system prune -a --volumes

Dockerfile Deep Dive โ€‹

A Dockerfile is a text file with instructions to build a Docker image.

Instruction Reference โ€‹

dockerfile
# Base image (always first)
FROM node:18-alpine

# Set maintainer metadata
LABEL maintainer="you@example.com"

# Set working directory (creates it if not exists)
WORKDIR /app

# Set environment variable
ENV NODE_ENV=production
ENV PORT=3000

# Copy files: COPY <src> <dest>
COPY package*.json ./

# Run command during BUILD time
RUN npm install --only=production

# Copy the rest of source code
COPY . .

# Expose port (documentation only, doesn't publish)
EXPOSE 3000

# Volume mount point
VOLUME ["/app/data"]

# Set default user (never run as root in production!)
USER node

# Health check
HEALTHCHECK --interval=30s --timeout=5s \
  CMD wget -qO- http://localhost:3000/health || exit 1

# Command to run when container STARTS
CMD ["node", "server.js"]

COPY vs ADD vs RUN โ€‹

InstructionWhen to Use
COPYSimple file copying (preferred)
ADDCopying + auto-extract tar files (use sparingly)
RUNExecute commands during build
CMDDefault command at container start (overridable)
ENTRYPOINTFixed command; CMD becomes its arguments

CMD vs ENTRYPOINT โ€‹

dockerfile
# CMD only โ€” fully overridable
CMD ["node", "server.js"]
# docker run my-app python script.py   โ† overrides CMD

# ENTRYPOINT + CMD โ€” ENTRYPOINT is fixed, CMD are default args
ENTRYPOINT ["node"]
CMD ["server.js"]
# docker run my-app other.js   โ† runs: node other.js

Multi-Stage Build (Production Pattern) โ€‹

dockerfile
# ============ Stage 1: Builder ============
FROM node:18 AS builder

WORKDIR /app
COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build   # produces /app/dist

# ============ Stage 2: Production ============
FROM node:18-alpine AS production

WORKDIR /app

# Only copy what we need from builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .

ENV NODE_ENV=production
USER node

EXPOSE 3000
CMD ["node", "dist/server.js"]

โš ๏ธ Important: Multi-stage builds drastically reduce final image size. A typical Node.js app goes from ~900MB โ†’ ~80MB.

Layer Caching Strategy โ€‹

.dockerignore File โ€‹

text
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
*.md
.env
dist
coverage
.DS_Store

Docker Compose โ€‹

Docker Compose lets you define and run multi-container applications using a single YAML file.

Full-Stack Architecture โ€‹

docker-compose.yml Example โ€‹

yaml
version: "3.9"

services:
  # ---- Frontend ----
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - REACT_APP_API_URL=http://localhost:5000
    depends_on:
      - backend
    networks:
      - app-network

  # ---- Backend ----
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
    environment:
      - NODE_ENV=development
      - DB_HOST=db
      - DB_PORT=5432
      - DB_NAME=myapp
      - DB_USER=postgres
      - DB_PASSWORD=${DB_PASSWORD} # from .env file
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy # wait for DB to be ready
      cache:
        condition: service_started
    volumes:
      - ./backend:/app # bind mount for hot reload
      - /app/node_modules # anonymous volume (don't overwrite)
    networks:
      - app-network

  # ---- Database ----
  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  # ---- Cache ----
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    networks:
      - app-network

  # ---- Optional: Database Admin UI ----
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    depends_on:
      - db
    networks:
      - app-network
    profiles:
      - tools # only starts with: docker compose --profile tools up

# Named volumes (persisted by Docker)
volumes:
  postgres_data:
  redis_data:

# Custom network
networks:
  app-network:
    driver: bridge

Compose Commands โ€‹

bash
# Start all services (builds if needed)
docker compose up

# Start in background
docker compose up -d

# Build images before starting
docker compose up --build

# Start specific service only
docker compose up backend

# Stop all services
docker compose down

# Stop AND remove volumes (โš ๏ธ deletes data!)
docker compose down -v

# View logs for all services
docker compose logs

# View logs for specific service (follow)
docker compose logs -f backend

# Scale a service
docker compose up -d --scale backend=3

# Run one-off command in a service
docker compose exec backend bash
docker compose run --rm backend npm run migrate

# List running services
docker compose ps

Real-World Examples โ€‹

Example 1: Node.js REST API โ€‹

Project structure:

text
my-api/
โ”œโ”€โ”€ Dockerfile
โ”œโ”€โ”€ .dockerignore
โ”œโ”€โ”€ package.json
โ””โ”€โ”€ src/
    โ””โ”€โ”€ server.js
dockerfile
FROM node:18-alpine

RUN apk add --no-cache dumb-init

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY src/ ./src/

USER node
EXPOSE 3000

ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "src/server.js"]
bash
docker build -t my-api:latest .
docker run -d \
  --name my-api \
  -p 3000:3000 \
  -e NODE_ENV=production \
  --restart unless-stopped \
  my-api:latest

Example 2: Python Flask App with Redis โ€‹

yaml
# docker-compose.yml
version: "3.9"

services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      - REDIS_HOST=redis
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
dockerfile
# Dockerfile
FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
CMD ["python", "app.py"]
python
# app.py
from flask import Flask
import redis, os

app = Flask(__name__)
cache = redis.Redis(host=os.environ.get('REDIS_HOST', 'localhost'))

@app.route('/')
def hello():
    count = cache.incr('hits')
    return f'Hello! Page views: {count}'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Example 3: Nginx as Reverse Proxy โ€‹

yaml
# docker-compose.yml
version: "3.9"

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app1
      - app2

  app1:
    build: ./app1
    expose:
      - "3000" # internal only, not published to host

  app2:
    build: ./app2
    expose:
      - "4000"
nginx
# nginx.conf
events {}
http {
  server {
    listen 80;

    location /api/v1/ {
      proxy_pass http://app1:3000/;
    }

    location /api/v2/ {
      proxy_pass http://app2:4000/;
    }
  }
}

Networking โ€‹

Network Types โ€‹

TypeDescriptionUse Case
bridgeDefault. Isolated virtual networkMost apps
hostContainer uses host network directlyHigh performance
noneNo networkCompletely isolated
overlayMulti-host networkingDocker Swarm
macvlanContainer gets its own MAC addressLegacy apps
bash
# Create a custom network
docker network create my-network

# Containers on same network reach each other by service name
docker run -d --network my-network --name db postgres
docker run -d --network my-network --name web my-app
# web โ†’ connects to db at: postgres://db:5432/...

# List and inspect
docker network ls
docker network inspect my-network

Volumes & Storage โ€‹

bash
# Named volume (managed by Docker โ€” preferred for databases)
docker run -v postgres_data:/var/lib/postgresql/data postgres

# Bind mount (development โ€” hot reload)
docker run -v ./src:/app/src my-app

# Read-only bind mount (config files)
docker run -v /host/config:/app/config:ro my-app

# tmpfs mount (sensitive data โ€” in memory only)
docker run --tmpfs /app/tmp my-app

# Manage volumes
docker volume ls
docker volume create my-data
docker volume inspect my-data
docker volume rm my-data

# Backup a named volume
docker run --rm \
  -v my-data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/backup.tar.gz /data

When to Use Docker โ€‹

Ideal Use Cases โ€‹

ScenarioWhy Docker Helps
๐Ÿš€ MicroservicesIsolate and deploy each service independently
๐Ÿ”„ CI/CD PipelinesReproducible build & test environments
๐ŸŒ Multi-environmentDev โ†’ Staging โ†’ Prod with identical containers
๐Ÿงช TestingSpin up fresh DB instances per test suite
๐Ÿ“ฆ Legacy migrationContainerize old apps without changing them
๐Ÿ”ง Local dev setupdocker compose up replaces installing 10 tools
โ˜๏ธ Cloud deploymentNative support in AWS ECS, GKE, Azure ACI

When NOT to Use Docker โ€‹

ScenarioReason
Desktop GUI appsComplex X11 forwarding required
Tiny one-person scriptsOverhead not worth it
Embedded / IoT systemsResource constrained

Best Practices โ€‹

Dockerfile Best Practices โ€‹

dockerfile
# โœ… Use specific versions (not :latest)
FROM node:18.17.0-alpine3.18

# โœ… Combine RUN commands to reduce layers
RUN apt-get update && \
    apt-get install -y curl git && \
    rm -rf /var/lib/apt/lists/*

# โœ… Copy dependencies FIRST for layer caching
COPY package*.json ./
RUN npm ci

# Then copy source (changes more often)
COPY src/ ./src/

# โœ… Use non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# โœ… Add health check
HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:3000/health || exit 1

# โŒ NEVER store secrets in images
# ENV SECRET_KEY=abc123   โ† DO NOT DO THIS

Security Best Practices โ€‹

bash
# Scan image for vulnerabilities
docker scout cves my-app:latest

# Run with limited capabilities
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-app

# Run as read-only filesystem
docker run --read-only my-app

# Limit memory and CPU
docker run \
  --memory="512m" \
  --cpus="1.5" \
  my-app

Common Pitfalls โ€‹

PitfallSymptomFix
Using :latest tagUnpredictable buildsPin to specific version: node:18.17.0
Running as rootSecurity riskAdd USER node in Dockerfile
Large image sizeSlow pulls & deploysUse Alpine base + multi-stage builds
Hardcoded configCan't change without rebuildUse environment variables
Data loss on rmDB data disappearsMount named volumes for stateful data
Wrong layer orderSlow rebuildsOrder: deps first, source last
Secrets in DockerfileCredentials in git historyUse .env files + add to .gitignore
PID 1 signal problemContainer won't stop cleanlyUse dumb-init or tini as entrypoint

โš ๏ธ Warning: EXPOSE does NOT publish ports โ€” it's documentation only. Use -p 8080:80 or ports: in compose to actually publish.

โš ๏ธ Warning: Data is lost when a container is removed unless you use volumes. Always use named volumes for databases.


Quick Reference Cheat Sheet โ€‹

bash
# ===== IMAGES =====
docker pull <image>                          # Download image
docker build -t <name>:<tag> .               # Build from Dockerfile
docker images                                # List images
docker rmi <image>                           # Remove image
docker push <name>:<tag>                     # Push to registry

# ===== CONTAINERS =====
docker run -d -p <h>:<c> --name <n> <image> # Run detached with port
docker run -it <image> bash                  # Interactive shell
docker ps                                    # List running
docker ps -a                                 # List all
docker stop / start / rm <name>             # Lifecycle
docker logs -f <name>                        # Follow logs
docker exec -it <name> bash                  # Shell into running
docker stats                                 # Resource usage

# ===== COMPOSE =====
docker compose up -d                         # Start all (detached)
docker compose up -d --build                 # Rebuild and start
docker compose down                          # Stop and remove
docker compose down -v                       # Also remove volumes
docker compose logs -f <service>             # Follow service logs
docker compose exec <service> bash           # Shell into service
docker compose ps                            # List services

# ===== CLEANUP =====
docker container prune                       # Remove stopped containers
docker image prune -a                        # Remove unused images
docker volume prune                          # Remove unused volumes
docker system prune -a --volumes             # Remove everything unused

Guide covers Docker 24.x / Compose v2.x

Released under the ISC License.