Docker · Kubernetes · Grafana · ArgoCD · AWS

⚙️ How Docker Works

Docker Architecture Docker CLI ──────────────────────────► Docker Daemon (dockerd) (your terminal) (background service) │ ┌────────────┼────────────┐ ▼ ▼ ▼ Containers Images Volumes (running processes) (blueprints) (persistent data) Image Layers (read-only, cached, shared): ┌──────────────────────────────┐ │ Layer 4: COPY app.py /app │ ← your code │ Layer 3: RUN pip install … │ ← dependencies │ Layer 2: RUN apt-get … │ ← system packages │ Layer 1: FROM python:3.12 │ ← base image └──────────────────────────────┘ + ┌──────────────────────────────┐ │ Writable container layer │ ← data written at runtime (lost on rm) └──────────────────────────────┘

📦 Images

An image is a read-only blueprint. Pull official images from Docker Hub, or build your own.

# Pull an image
docker pull nginx:1.25
docker pull python:3.12-slim
docker pull postgres:16-alpine

# List local images
docker images

# Inspect image layers
docker image history python:3.12-slim

# Remove image
docker rmi nginx:1.25

# Remove all unused images
docker image prune -a

# Tag image (required before pushing)
docker tag myapp:latest myregistry.com/myteam/myapp:1.0

# Push to registry
docker push myregistry.com/myteam/myapp:1.0
Base ImageSizeBest For
alpine:3.19~7 MBSmallest possible — shell scripts, tiny tools
debian:bookworm-slim~75 MBGood default, familiar package manager
python:3.12-slim~130 MBPython apps — much smaller than the full image
node:20-alpine~140 MBNode.js apps
golang:1.22-alpine~240 MBGo build stage only — use multi-stage builds
nginx:alpine~45 MBStatic file serving, reverse proxy

🏃 Containers

# Run in background (-d), map port (-p), give a name (--name)
docker run -d -p 8080:80 --name web nginx

# Run interactively and delete when done
docker run -it --rm ubuntu bash

# Pass environment variables
docker run -d \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=mydb \
  -p 5432:5432 \
  --name db postgres:16

# List running containers
docker ps

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

# Stream logs
docker logs -f web

# Open a shell inside a running container
docker exec -it web bash
docker exec -it web sh   # alpine images don't have bash

# Copy files between host and container
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
docker cp ./nginx.conf web:/etc/nginx/nginx.conf

# Live resource stats
docker stats

# Stop and remove
docker stop web && docker rm web
docker rm -f web       # force-stop and remove in one command
FlagMeaningExample
-dDetach (run in background)docker run -d nginx
-itInteractive + terminaldocker run -it ubuntu bash
-p host:containerPublish port-p 8080:80
--nameContainer name--name myapp
--rmAuto-delete on stop--rm
-e KEY=VALUEEnvironment variable-e DB_HOST=db
-vVolume / bind mount-v /data:/app/data
--networkAttach to network--network mynet
--restartRestart policy--restart unless-stopped
--memoryMemory limit--memory 512m
--cpusCPU limit--cpus 1.5

📝 Dockerfile

A Dockerfile is the recipe for building your image. Each instruction adds a layer.

# ── Stage 1: Build ──────────────────────────────────────
FROM python:3.12-slim

# Set working directory inside the container
WORKDIR /app

# Copy requirements FIRST so pip install is cached
# This layer only rebuilds when requirements.txt changes
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Now copy the rest of the code
COPY . .

# Create a non-root user (security best practice)
RUN useradd -r -u 1001 appuser && chown -R appuser /app
USER appuser

# Document which port the app uses
EXPOSE 8080

# Health check — Docker marks container unhealthy if this fails
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

# Default command (exec form — correct signal handling)
CMD ["python", "app.py"]

Multi-Stage Build (keep images tiny)

# Stage 1: compile Go binary
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server .

# Stage 2: tiny final image — no Go toolchain included
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/server /usr/local/bin/server
EXPOSE 8080
CMD ["server"]
# Result: ~12 MB instead of ~350 MB
.git
node_modules
__pycache__
*.pyc
.env
*.log
dist/
coverage/
💡
Cache tip

Put instructions that change rarely (FROM, RUN apt install) at the top. Put code COPY at the bottom. Changing one layer invalidates all layers below it.

💾 Volumes

# Named volume (Docker manages location)
docker volume create pgdata
docker run -d -v pgdata:/var/lib/postgresql/data postgres:16

# Bind mount (your folder → container path)
docker run -d -v /home/alice/data:/app/data myapp
docker run -d -v $(pwd):/app myapp    # current directory

# Read-only bind mount
docker run -d -v ./config:/app/config:ro myapp

# Volume commands
docker volume ls
docker volume inspect pgdata
docker volume rm pgdata
docker volume prune           # remove all unused volumes

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

# Restore
docker run --rm \
  -v pgdata:/data -v $(pwd):/backup \
  alpine tar xzf /backup/pgdata.tar.gz -C /data

🌐 Networking

# Create a custom network (enables DNS by container name)
docker network create mynet

# Both containers can reach each other by name
docker run -d --name db      --network mynet postgres:16
docker run -d --name api     --network mynet myapi:latest
docker run -d --name cache   --network mynet redis:7-alpine

# Inside "api" container, DNS works:
#   curl http://db:5432   →  reaches postgres
#   ping cache            →  reaches redis

# Network commands
docker network ls
docker network inspect mynet
docker network connect mynet existing-container
docker network rm mynet

📋 Docker Compose

Define your entire multi-container application in one YAML file.

# docker-compose.yml
services:
  web:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    volumes:
      - .:/app                    # live code in development
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

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

volumes:
  pg-data:
  redis-data:
docker compose up -d           # start all services
docker compose down            # stop + remove containers
docker compose down -v         # also remove volumes
docker compose logs -f         # stream all logs
docker compose logs -f web     # stream one service
docker compose ps              # list services
docker compose exec web bash   # shell into service
docker compose build --no-cache  # force rebuild
docker compose pull            # pull latest images

📌 Command Cheat Sheet

TaskCommand
Start container in backgrounddocker run -d -p 8080:80 nginx
Shell into running containerdocker exec -it NAME bash
Stream logsdocker logs -f NAME
Stop & remove containerdocker rm -f NAME
Remove all stopped containersdocker container prune
Build imagedocker build -t myapp:1.0 .
Push to registrydocker push user/myapp:1.0
Show disk usagedocker system df
Clean everything unuseddocker system prune -a --volumes