๐ณ Docker โ Complete Guide โ
A practical, visual, and example-driven guide to mastering Docker from zero to production.
Table of Contents โ
- What is Docker?
- Why Use Docker?
- Core Concepts
- Docker Architecture
- Installation
- Essential Commands
- Dockerfile Deep Dive
- Docker Compose
- Real-World Examples
- Networking
- Volumes & Storage
- When to Use Docker
- Best Practices
- Common Pitfalls
- 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.
| Feature | Virtual Machine | Docker Container |
|---|---|---|
| Boot Time | Minutes | Seconds |
| Size | GBs | MBs |
| OS | Full Guest OS | Shared Host Kernel |
| Isolation | Strong | Process-level |
| Portability | Limited | Excellent |
Why Use Docker? โ
The "It works on my machine" Problem โ
With Docker โ
Core Concepts โ
Key Terms โ
| Term | Description | Analogy |
|---|---|---|
| Image | Read-only blueprint | Recipe / Class definition |
| Container | Running instance of an image | Cooked dish / Object instance |
| Dockerfile | Instructions to build an image | Recipe steps |
| Registry | Storage for images (Docker Hub) | App Store |
| Volume | Persistent storage for containers | External hard drive |
| Network | Communication layer between containers | LAN network |
| Compose | Tool to run multi-container apps | Orchestrator |
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) โ
# 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-worldEssential Commands โ
Image Commands โ
# 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.0Container Commands โ
# 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 statsContainer Lifecycle โ
Cleanup Commands โ
# 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 --volumesDockerfile Deep Dive โ
A Dockerfile is a text file with instructions to build a Docker image.
Instruction Reference โ
# 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 โ
| Instruction | When to Use |
|---|---|
COPY | Simple file copying (preferred) |
ADD | Copying + auto-extract tar files (use sparingly) |
RUN | Execute commands during build |
CMD | Default command at container start (overridable) |
ENTRYPOINT | Fixed command; CMD becomes its arguments |
CMD vs ENTRYPOINT โ
# 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.jsMulti-Stage Build (Production Pattern) โ
# ============ 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 โ
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
*.md
.env
dist
coverage
.DS_StoreDocker Compose โ
Docker Compose lets you define and run multi-container applications using a single YAML file.
Full-Stack Architecture โ
docker-compose.yml Example โ
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: bridgeCompose Commands โ
# 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 psReal-World Examples โ
Example 1: Node.js REST API โ
Project structure:
my-api/
โโโ Dockerfile
โโโ .dockerignore
โโโ package.json
โโโ src/
โโโ server.jsFROM 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"]docker build -t my-api:latest .
docker run -d \
--name my-api \
-p 3000:3000 \
-e NODE_ENV=production \
--restart unless-stopped \
my-api:latestExample 2: Python Flask App with Redis โ
# 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
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]# 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 โ
# 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.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 โ
| Type | Description | Use Case |
|---|---|---|
bridge | Default. Isolated virtual network | Most apps |
host | Container uses host network directly | High performance |
none | No network | Completely isolated |
overlay | Multi-host networking | Docker Swarm |
macvlan | Container gets its own MAC address | Legacy apps |
# 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-networkVolumes & Storage โ
# 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 /dataWhen to Use Docker โ
Ideal Use Cases โ
| Scenario | Why Docker Helps |
|---|---|
| ๐ Microservices | Isolate and deploy each service independently |
| ๐ CI/CD Pipelines | Reproducible build & test environments |
| ๐ Multi-environment | Dev โ Staging โ Prod with identical containers |
| ๐งช Testing | Spin up fresh DB instances per test suite |
| ๐ฆ Legacy migration | Containerize old apps without changing them |
| ๐ง Local dev setup | docker compose up replaces installing 10 tools |
| โ๏ธ Cloud deployment | Native support in AWS ECS, GKE, Azure ACI |
When NOT to Use Docker โ
| Scenario | Reason |
|---|---|
| Desktop GUI apps | Complex X11 forwarding required |
| Tiny one-person scripts | Overhead not worth it |
| Embedded / IoT systems | Resource constrained |
Best Practices โ
Dockerfile Best Practices โ
# โ
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 THISSecurity Best Practices โ
# 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-appCommon Pitfalls โ
| Pitfall | Symptom | Fix |
|---|---|---|
Using :latest tag | Unpredictable builds | Pin to specific version: node:18.17.0 |
| Running as root | Security risk | Add USER node in Dockerfile |
| Large image size | Slow pulls & deploys | Use Alpine base + multi-stage builds |
| Hardcoded config | Can't change without rebuild | Use environment variables |
Data loss on rm | DB data disappears | Mount named volumes for stateful data |
| Wrong layer order | Slow rebuilds | Order: deps first, source last |
| Secrets in Dockerfile | Credentials in git history | Use .env files + add to .gitignore |
| PID 1 signal problem | Container won't stop cleanly | Use dumb-init or tini as entrypoint |
โ ๏ธ Warning:
EXPOSEdoes NOT publish ports โ it's documentation only. Use-p 8080:80orports: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 โ
# ===== 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 unusedGuide covers Docker 24.x / Compose v2.x
