🐳 Latest Edition
📖 Beginner to Advanced
⏱️ 50 min read
🎯 20+ Sections
⏱️ Estimated reading time: 45-50 minutes
📋 Quick Summary: Docker is the #1 containerization platform used by 90% of companies. It lets you package any application with its dependencies into a lightweight, portable container that runs everywhere — from your laptop to production servers to the cloud. Docker Swarm adds native clustering and orchestration. By the end of this course, you will containerize applications, build custom images, deploy with Docker Compose, and orchestrate clusters with Docker Swarm.
🐳 What Is Docker?
Docker is a platform for developing, shipping, and running applications inside containers. A container is a lightweight, standalone, executable package that includes everything needed to run an application: code, runtime, system tools, libraries, and settings.
Think of containers as lightweight VMs — they isolate applications from each other and from the host system, but they share the host OS kernel, making them much faster and smaller than virtual machines.
Docker vs Virtual Machines
| Feature | Docker Container | Virtual Machine |
|---|---|---|
| Startup Time | Milliseconds | Minutes |
| Size | MBs | GBs |
| Performance | Near-native | Some overhead |
| OS Kernel | Shared with host | Separate per VM |
| Isolation | Process-level | Hypervisor-level |
| Scalability | 1000s per host | 10s per host |
🔧 Installation
Install Docker Engine
# Linux (Ubuntu/Debian) — recommended curl -fsSL https://get.docker.com | sh # Add your user to docker group (avoid sudo) sudo usermod -aG docker $USER # Log out and back in, or: newgrp docker # macOS — install Docker Desktop brew install --cask docker # Windows — install Docker Desktop winget install Docker.DockerDesktop # Verify installation docker --version # Docker version 27.x docker compose version # Docker Compose v2 docker run hello-world # Test container
Architecture Overview
# Docker uses a client-server architecture: # Client: docker CLI (commands you type) # Server: dockerd (Docker daemon — runs in background) # Registry: Docker Hub (stores images — like GitHub for containers) # Key components: # Image: Read-only template (e.g., python:3.12) # Container: Running instance of an image # Volume: Persistent storage outside container # Network: Communication between containers
📦 Docker Basics
First Commands
# Run a container docker run nginx:latest # Download the nginx image (if not cached) and run it # Run in detached mode (-d) docker run -d --name my-nginx -p 8080:80 nginx:latest # -d: detached (background) # --name: give it a name # -p 8080:80: map host port 8080 to container port 80 # List containers docker ps # Running containers docker ps -a # All containers (including stopped) # Stop and remove docker stop my-nginx docker rm my-nginx # View logs docker logs my-nginx docker logs -f my-nginx # Follow (tail -f style) # Execute commands in running container docker exec -it my-nginx bash # Pull an image without running docker pull python:3.12-slim
Lifecycle Commands
docker start container_name # Start stopped container docker stop container_name # Graceful stop (SIGTERM) docker kill container_name # Force stop (SIGKILL) docker restart container_name # Stop + start docker pause container_name # Freeze processes docker unpause container_name # Unfreeze docker rm container_name # Remove container docker rm $(docker ps -aq) # Remove ALL containers docker container prune # Remove all stopped containers
🖼️ Docker Images
Images are the blueprints for containers. Understanding images is the most important Docker skill.
Image Commands
# List images docker images # Search images on Docker Hub docker search nginx # Pull an image docker pull ubuntu:24.04 docker pull python:3.12-slim # Slim = minimal footprint docker pull alpine:latest # Alpine = ~5MB (ultra small) # Image information docker inspect nginx:latest # Full metadata (JSON) docker history nginx:latest # Layer history # Remove images docker rmi nginx:latest docker image prune # Remove dangling images docker system prune # Clean everything unused
Image Layers
# Docker images are built in LAYERS. Each instruction in a Dockerfile # creates a new layer. Layers are cached and reused. # Example: python:3.12-slim layers: # 1. Base: debian:bookworm-slim (~80MB) # 2. System: ca-certificates, apt packages (~30MB) # 3. Python: Python 3.12.3 (~150MB) # = Total: ~260MB (shared across all Python images!) # Why layers matter: # - Faster builds (cached layers don't rebuild) # - Smaller downloads (only new/different layers) # - Efficient storage (shared between images)
📝 Dockerfile — Build Your Own Images
The Dockerfile is a recipe for building images. Master this and you can containerize anything.
Python App Example
# Dockerfile FROM python:3.12-slim WORKDIR /app # Install dependencies (separate from code — leverages cache) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . # Expose port EXPOSE 8000 # Default command CMD ["python", "app.py"]
# Build and run docker build -t my-python-app . docker run -d -p 8000:8000 my-python-app
Node.js App Example
# Dockerfile (Multi-stage build — production optimized) # Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . # Stage 2: Production FROM node:20-alpine AS production WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/package.json ./ EXPOSE 3000 CMD ["node", "dist/server.js"] # Build: docker build -t my-node-app . # Result: Small production image (no build tools, no source code)
NGINX + Static Site
# Dockerfile FROM nginx:alpine COPY ./site /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 # Build and run docker build -t my-site . docker run -d -p 80:80 my-site
Dockerfile Instructions Reference
| Instruction | What It Does | Example |
|---|---|---|
FROM |
Base image to build from | FROM python:3.12-slim |
WORKDIR |
Set working directory | WORKDIR /app |
COPY |
Copy files from host | COPY . . |
RUN |
Execute commands during build | RUN pip install -r req.txt |
ENV |
Set environment variables | ENV NODE_ENV=production |
EXPOSE |
Document port (docs only) | EXPOSE 8000 |
CMD |
Default command (can override) | CMD ["python", "app.py"] |
ENTRYPOINT |
Main command (harder to override) | ENTRYPOINT ["nginx", "-g"] |
ARG |
Build-time variables | ARG VERSION=latest |
HEALTHCHECK |
Container health check | HEALTHCHECK curl localhost |
Best Practices
# 1. Use .dockerignore (like .gitignore)
# .dockerignore:
**/__pycache__
**/.git
**/.env
**/node_modules
# 2. Order instructions by change frequency
# Stable first (apt, pip), code last
# This maximizes layer caching
# 3. Use specific tags, not 'latest'
FROM python:3.12-slim # GOOD - specific
FROM python:latest # BAD - breaks unpredictably
# 4. Combine RUN commands to reduce layers
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# 5. Use multi-stage builds for smaller images
# See Node.js example above
# 6. Don't run as root
RUN useradd -m -u 1000 appuser
USER appuser
💡 Did You Know? A well-optimized Dockerfile can reduce image size by 90%+. Multi-stage builds alone can cut a 1.2GB Node.js image to under 150MB. The
alpinevariants are typically 5-10x smaller than full images.
🔗 Docker Compose
Compose lets you define and run multi-container applications with a single YAML file. This is how professional projects manage their services.
Basic Compose File
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- .:/app # Hot reload in dev
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypass
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
postgres_data:
redis_data:
Compose Commands
# Start all services docker compose up -d # Start specific services docker compose up -d web db # Build and start (rebuilds images) docker compose up -d --build # View logs docker compose logs -f docker compose logs -f web # Execute commands docker compose exec web python manage.py migrate # Check status docker compose ps # Stop all docker compose down # Stop and remove volumes (⚠️ destroys data!) docker compose down -v # Scale services docker compose up -d --scale web=3 # View resource usage docker compose top
Full-Stack Application
# docker-compose.yml (Production-ready Django app)
services:
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- static_volume:/static
ports:
- "80:80"
depends_on:
- web
web:
build: .
command: gunicorn myproject.wsgi:application --bind :8000 --workers 4
volumes:
- static_volume:/app/staticfiles
env_file: .env
depends_on:
- db
- redis
celery:
build: .
command: celery -A myproject worker -l info
env_file: .env
depends_on:
- db
- redis
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
env_file: .env
redis:
image: redis:7-alpine
volumes:
postgres_data:
static_volume:
💾 Volumes & Persistent Data
Containers are ephemeral — when you remove them, all data inside is gone. Volumes solve this by storing data outside the container.
Volume Types
# 1. Named Volumes (managed by Docker — preferred) docker volume create my_data docker run -v my_data:/data image # 2. Bind Mounts (direct host directory) docker run -v /host/path:/container/path image docker run -v $(pwd):/app image # Common for dev # 3. tmpfs (in-memory — fast, lost on stop) docker run --tmpfs /app/cache image # Volume commands docker volume ls docker volume inspect my_data docker volume prune # Remove unused volumes docker volume rm my_data
Compose with Volumes
services:
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # Init script
web:
image: myapp
volumes:
- .:/app # Mount code for hot reload
- /app/node_modules # Don't override node_modules
volumes:
postgres_data:
🌐 Networking
Network Types
# Docker networks: # bridge: Default. Containers communicate via IP. Good for standalone. # host: Container shares host's network (no isolation). # none: No network access. # overlay: Multi-host networking (required for Swarm). # Create networks docker network create my-network docker network create --driver overlay my-swarm-network # For Swarm # Connect containers docker run --network my-network --name app1 nginx docker run --network my-network --name app2 nginx # Now app1 can ping 'app2' — DNS resolution is automatic! # Network commands docker network ls docker network inspect my-network docker network connect my-network container_name docker network disconnect my-network container_name
DNS Resolution in Compose
# In Compose, every service name is DNS-resolvable:
# web can reach: db:5432, redis:6379
# No need for IP addresses! Docker's embedded DNS handles it.
# To expose an internal service only (no ports):
services:
db:
image: postgres:16-alpine
expose:
- "5432" # Accessible within Compose network, not externally
🔐 Environment Variables
# Method 1: Direct in command
docker run -e DATABASE_URL=postgresql://... myapp
# Method 2: .env file (reusable)
docker run --env-file .env myapp
# Method 3: In Compose
services:
web:
env_file: .env # Load from file
environment: # Or inline
- DEBUG=true
- PORT=8000
# Docker secrets (Swarm only — more secure than env vars)
docker secret create db_password password.txt
docker service create --secret db_password myapp
⚙️ Docker Swarm Orchestration
Docker Swarm turns a group of Docker hosts into a single virtual cluster. It’s native to Docker — no additional tools needed.
Why Swarm?
- Built into Docker — no extra installation
- Simpler than Kubernetes — 10x less complexity
- Good for small-medium clusters (3-50 nodes)
- Load balancing built-in (routing mesh)
- Desired state reconciliation — Swarm maintains the state you define
- Rolling updates with zero downtime
Initialize a Swarm
# On the manager node: docker swarm init --advertise-addr 10.0.0.1 # Output: docker swarm join --token SWMTKN-... 10.0.0.1:2377 # On worker nodes: docker swarm join --token SWMTKN-... 10.0.0.1:2377 # View nodes docker node ls # Promote worker to manager docker node promote node2
Swarm Services
In Swarm, you deploy services (not containers). A service defines the desired state:
# Create a service (3 replicas) docker service create \ --name web \ --replicas 3 \ --publish 80:80 \ --network my-swarm-net \ nginx:alpine # List services docker service ls # List tasks (containers in the service) docker service ps web # Scale a service docker service scale web=5 # Update image (rolling update) docker service update \ --image nginx:1.25 \ --update-parallelism 1 \ --update-delay 10s \ web # Inspect docker service inspect --pretty web # Remove service docker service rm web
Swarm Mode Load Balancing
# Swarm's built-in routing mesh: # 1. Deploy service with --publish 80:80 # 2. Swarm exposes port 80 on EVERY node # 3. Request hits any node → forwarded to a healthy container # 4. If a container fails, Swarm replaces it automatically # Internal load balancing: # Services resolve via DNS to virtual IP (VIP): # ping web → 10.0.0.2 (VIP) # The VIP load-balances across all 5 replicas # Use --endpoint-mode dnsrr for DNS round-robin instead
Swarm Stacks (Compose for Swarm)
# docker-stack.yml (Compose file for Swarm)
version: '3.8'
services:
web:
image: myapp:latest
ports:
- "80:8000"
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-first # Start new before stopping old
restart_policy:
condition: on-failure
max_attempts: 3
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.1'
memory: 256M
networks:
- app-net
db:
image: postgres:16-alpine
deploy:
replicas: 1
placement:
constraints: [node.role == manager] # DB on manager node
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-net
networks:
app-net:
driver: overlay
volumes:
postgres_data:
driver: local
# Deploy the stack
docker stack deploy -c docker-stack.yml myapp
# Stack commands
docker stack ls # List stacks
docker stack ps myapp # List services in stack
docker stack services myapp # List services
docker stack rm myapp # Remove stack
Swarm Node Management
# Node states docker node ls # ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS # abc123 node1 Ready Active Reachable # def456 node2 Ready Active Leader # Drain a node (move containers away for maintenance) docker node update --availability drain node2 # All containers move to other nodes — zero downtime # Activate it back docker node update --availability active node2 # Add labels for placement constraints docker node update --label-add region=us-east node1 # Then in stack file: # deploy: # placement: # constraints: [node.labels.region == us-east]
📊 Monitoring & Logging
# View container resource usage docker stats # Live CPU/memory/network docker stats --no-stream # Single snapshot # View logs docker logs container_name # All logs docker logs -f container_name # Follow mode docker logs --tail 100 container_name # Last 100 lines docker logs --since 5m container_name # Last 5 minutes # Inspect container docker inspect container_name # Full JSON config # Docker system commands (cleanup!) docker system df # Disk usage docker system prune # Remove unused (containers, networks, images) docker system prune -a # Remove ALL unused images docker system prune --volumes # Include volumes (⚠️ data loss!) # Health checks in Dockerfile HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1
🏗️ Real-World Project: Full-Stack App Deployment
Let’s deploy a complete Django + PostgreSQL + Redis + Celery application with Docker Compose and Swarm.
Step 1: Project Structure
myproject/ ├── docker-compose.yml # Dev environment ├── docker-stack.yml # Production Swarm stack ├── Dockerfile # App image ├── .dockerignore ├── .env ├── nginx/ │ └── default.conf # Nginx config ├── backend/ │ ├── manage.py │ ├── requirements.txt │ └── myproject/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── static/ # Static files
Step 2: Dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
Step 3: Dev Compose
# docker-compose.yml
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
env_file: .env
depends_on:
- db
- redis
celery:
build: .
command: celery -A myproject worker -l info
volumes:
- .:/app
env_file: .env
depends_on:
- db
- redis
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myproject
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypass
redis:
image: redis:7-alpine
volumes:
postgres_data:
Step 4: Production Swarm Stack
# docker-stack.yml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
configs:
- source: nginx_config
target: /etc/nginx/conf.d/default.conf
deploy:
replicas: 2
placement:
constraints: [node.role == manager]
depends_on:
- web
web:
image: myregistry/myapp:${TAG:-latest}
deploy:
replicas: 4
update_config:
parallelism: 2
delay: 10s
order: start-first
restart_policy:
condition: on-failure
resources:
limits:
cpus: '0.5'
memory: 512M
environment:
- DATABASE_URL=postgresql://db:5432/myproject
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
celery:
image: myregistry/myapp:${TAG:-latest}
command: celery -A myproject worker -l info
deploy:
replicas: 2
environment:
- DATABASE_URL=postgresql://db:5432/myproject
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:16-alpine
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
volumes:
postgres_data:
driver: local
configs:
nginx_config:
file: ./nginx/default.conf
Step 5: Deploy Commands
# 1. Build and push image docker build -t myregistry/myapp:v1.0 . docker push myregistry/myapp:v1.0 # 2. Deploy stack TAG=v1.0 docker stack deploy -c docker-stack.yml myapp # 3. Verify docker stack services myapp docker service logs myapp_web -f # 4. Update (zero-downtime) docker build -t myregistry/myapp:v1.1 . docker push myregistry/myapp:v1.1 TAG=v1.1 docker stack deploy -c docker-stack.yml myapp # 5. Rollback if needed docker service rollback myapp_web
❌ Common Mistakes & How to Avoid Them
🔴 Mistake #1: Running as Root
What happens: Containers run as root by default. If compromised, the attacker has root on your host.
How to fix: Use USER appuser in Dockerfile. Create user: RUN useradd -m appuser.
🔴 Mistake #2: Using :latest Tag
What happens: latest changes unpredictably. A rebuild might pull a different version and break everything.
How to fix: Always use specific tags: python:3.12-slim, nginx:1.25-alpine. Version your own images.
🔴 Mistake #3: Storing Secrets in Images
What happens: API keys and passwords baked into images. Anyone who pulls the image can extract them.
How to fix: Use environment variables at runtime. Never COPY or ENV secrets. Use Docker secrets in Swarm.
🔴 Mistake #4: Ignoring .dockerignore
What happens: COPY . . includes node_modules (500MB), .git (2GB), and **/__pycache__. Builds are slow, images are huge.
How to fix: Always maintain a .dockerignore with the same care as .gitignore.
🔴 Mistake #5: No Health Checks
What happens: Swarm thinks a broken container is “healthy” because the process is running but the app is stuck.
How to fix: Add HEALTHCHECK to Dockerfile or healthcheck to Compose/Stack.
🔴 Mistake #6: Bind Mounting in Production
What happens: Bind mounts tie containers to specific host paths. In Swarm, containers move between nodes — the mount breaks.
How to fix: Use named volumes or cluster-wide storage (NFS, EFS, GlusterFS) in production.
🔴 Mistake #7: One Container = One Process (Wrong)
What happens: People run both nginx AND the app in one container. Makes scaling impossible.
How to fix: One process per container. Use Compose to orchestrate multiple containers.
🧠 Test Your Knowledge
- What does
docker-compose up -d --builddo?
A) Starts containers and shows logs
B) Rebuilds images and starts containers in detached mode
C) Downloads new images
D) Removes old containers
Answer: B —--buildrebuilds images,-dstarts in background. - What’s the main difference between a bind mount and a named volume?
A) Nothing, they’re the same
B) Bind mounts use host paths; named volumes are managed by Docker
C) Named volumes are faster
D) Bind mounts persist after container removal
Answer: B — Bind mounts point to host directories. Named volumes are Docker-managed and portable across hosts. - In Docker Swarm, what does
docker service scale web=5do?
A) Creates 5 different services
B) Increases web service replicas to 5
C) Restarts the service 5 times
D) Distributes 5 ports
Answer: B — Scale adjusts the number of running replicas for a service. - Why should you COPY requirements.txt separately before the rest of the code?
A) It’s required syntax
B) To leverage layer caching — dependencies change less often than code
C) It’s more secure
D) It runs faster
Answer: B — If requirements don’t change, Docker reuses the cached layer instead of reinstalling all packages. - What’s a Docker stack?
A) A single container
B) A Compose file deployed to a Swarm cluster
C) A group of Docker hosts
D) A type of volume
Answer: B —docker stack deploytakes a Compose file and deploys it as a Swarm stack.
❓ Frequently Asked Questions (FAQ)
Q1: Docker vs Kubernetes — which should I learn?
Learn Docker first. Kubernetes is built on Docker (or containerd). Docker Swarm is simpler (takes 1 hour to learn) and perfect for small-medium clusters. Kubernetes is more powerful but 10x more complex. For a solo developer or small team, Swarm + Docker Compose is often the right choice.
Q2: Can I run Docker on Windows?
Yes. Docker Desktop for Windows uses WSL2 (Windows Subsystem for Linux) to run Linux containers natively. You can also run Windows containers for .NET apps. Performance is nearly identical to native Linux.
Q3: How do I reduce Docker image size?
Use Alpine-based images (python:3.12-alpine vs python:3.12 — 150MB vs 900MB). Multi-stage builds. Combine RUN commands. Use .dockerignore. Remove package manager caches. Use distroless images for production (gcr.io/distroless/python3).
Q4: Can I use Docker in production?
Absolutely. Most of the internet runs on containers. Docker in production needs: orchestration (Swarm or K8s), monitoring (Prometheus/Grafana), logging (ELK stack), persistent storage (NFS/EFS), secrets management (HashiCorp Vault or Docker secrets), and CI/CD pipeline for automated builds and deployments.
Q5: What’s the difference between CMD and ENTRYPOINT?
CMD provides defaults that can be overridden: docker run myimage bash replaces CMD. ENTRYPOINT is the main command that always runs — arguments are appended. Pattern: use ENTRYPOINT for the executable and CMD for default arguments.
Q6: How do Docker and CI/CD work together?
Build the Docker image in CI (GitHub Actions, GitLab CI), run tests inside the container, push to registry, deploy to production. The same image that passes tests in CI goes to production — “build once, run anywhere.” This eliminates the “it works on my machine” problem.
Q7: What’s Docker’s security model?
Containers isolate processes using Linux namespaces and cgroups. They share the host kernel, so a kernel exploit can break isolation. For production: don’t run as root, use read-only root filesystem, limit capabilities, scan images for vulnerabilities (Trivy, Snyk), use secrets, and consider rootless Docker.
Q8: Can Docker run on a Raspberry Pi?
Yes. Docker runs on ARM architecture. curl -fsSL https://get.docker.com | sh works on Raspberry Pi OS. Use arm64v8 or arm32v7 images. Many official images now support ARM. A Pi 4 can run a small Swarm cluster for learning or low-traffic applications.
Q9: What’s the difference between docker-compose and docker stack?
docker-compose runs on a single host (development). docker stack deploys the same YAML to a Swarm cluster (production). The YAML format is mostly the same, but stack adds deploy: sections for replicas, update config, restart policy, and resource limits.
Q10: How do I back up Docker volumes?
Run a temporary container that mounts the volume and creates a backup: docker run --rm -v my_volume:/data -v $(pwd):/backup alpine tar czf /backup/volume-backup.tar.gz -C /data .. Restore: docker run --rm -v my_volume:/data -v $(pwd):/backup alpine tar xzf /backup/volume-backup.tar.gz -C /data.
📖 Glossary
| Term | Definition |
|---|---|
| Container | A running instance of an image — isolated process with its own filesystem |
| Image | A read-only template with instructions for creating a container |
| Dockerfile | A script with instructions to build a Docker image |
| Volume | Persistent storage that outlives containers |
| Bind Mount | A host directory mounted into a container |
| Compose | Tool for defining and running multi-container Docker applications with YAML |
| Swarm | Docker’s native clustering and orchestration tool |
| Service | In Swarm, a definition of how to run a container (image, replicas, ports, etc.) |
| Stack | A group of Swarm services deployed together from a Compose file |
| Overlay Network | Multi-host networking that spans all Swarm nodes |
| Routing Mesh | Swarm’s built-in load balancer — any node can serve any service |
| Layer | A filesystem change created by each Dockerfile instruction — layers are cached and reused |
✅ Do’s & Don’ts
| ✅ Do | ❌ Don’t |
|---|---|
| Use Alpine-based images for smaller builds | Use :latest tag (unpredictable) |
| Use .dockerignore | COPY everything blindly |
| Run as non-root user | Run containers as root |
| Use multi-stage builds | Ship build tools in production images |
| Use environment variables for config | Hardcode secrets in Dockerfile |
| Add HEALTHCHECK | Assume running = healthy |
💡 10 Pro Tips Learned the Hard Way
- Learn docker inspect — it’s your best friend.
docker inspect container_namereturns everything: IP, mounts, environment variables, network settings, health status. Pipe it tojqfor querying:docker inspect nginx | jq '.[0].NetworkSettings.IPAddress'. - Use docker system prune weekly. Unused images, containers, volumes, and build cache accumulate fast. A 6-month-old Docker installation can easily waste 30GB of disk space.
- Pin base image versions with SHA256 hashes. Instead of
FROM python:3.12-slim, useFROM python:3.12-slim@sha256:abc123def456.... This guarantees bit-identical builds forever. Update intentionally. - Use labels for organization. Labels are metadata key-value pairs:
LABEL maintainer="team@company.com" project="myapp" version="1.0". Search withdocker ps --filter "label=project=myapp". - Master docker-compose exec for debugging. Instead of SSH into servers,
docker compose exec web bashgives you a shell inside the running container.docker compose exec web python manage.py shellfor Django shell. - Use HEALTHCHECK with curl. The simplest health check:
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1. Without it, Swarm has no idea your app is down. - Swarm doesn’t need a load balancer. The routing mesh does Layer 4 load balancing automatically. For Layer 7 (path-based routing, SSL termination), add Traefik or Nginx as a proxy service.
- Always specify –restart policies.
restart: unless-stoppedin Compose,restart_policy: condition: on-failurein Swarm. Without these, containers that crash stay crashed. - Use docker scan or Trivy for security.
docker scan myimagechecks for CVEs.trivy image myimage:latestis more comprehensive. Fix critical vulnerabilities before pushing to production. - Containerize your development environment too. Use a devcontainer (VS Code Remote — Containers) so every dev on your team has the exact same environment. No more “but it works on my machine.”
🗺️ Learning Roadmap
| Day | Topic | Goal | ⏱️ |
|---|---|---|---|
| 1 | Install & Basics | Install Docker, run your first containers, basic commands | 60 min |
| 2 | Dockerfiles | Build images, best practices, multi-stage builds | 90 min |
| 3 | Volumes & Networking | Persistent data, networks, DNS resolution | 90 min |
| 4 | Docker Compose | Multi-container apps, environment variables, volumes in Compose | 120 min |
| 5 | Docker Swarm Basics | Init swarm, services, scaling, rolling updates | 90 min |
| 6 | Swarm Stacks | Deploy production stacks, secrets, configs, placement constraints | 120 min |
| 7 | Production & CI/CD | Monitoring, logging, security, CI/CD pipeline | 120 min |
🔍 Troubleshooting
| ⚠️ Problem | 🔍 Cause | ✅ Solution |
|---|---|---|
| Container exits immediately | Process finishes or crashes | Check docker logs. Use docker run -it for interactive processes. |
| Port already in use | Another container or host process | docker stop conflicting_container or change host port. |
| Permission denied | User not in docker group | sudo usermod -aG docker $USER then log out & back in. |
| No space left | Unused images/volumes fill disk | docker system prune -a --volumes (⚠️ removes all unused) |
| Can’t connect to service from another container | Wrong network or DNS issue | Both containers must be on the same network. Use service name, not localhost. |
💬 What’s Your Experience?
What are you containerizing? Docker Swarm or Kubernetes? Drop a comment — I read every one.
Quick questions:
- What was your biggest Docker challenge?
- Swarm vs K8s — which do you use and why?
- What’s the coolest thing you’ve containerized?
- Which section helped you the most?
📌 TL;DR: If You Learn Nothing Else, Learn These 5
- Dockerfile Basics — FROM, WORKDIR, COPY, RUN, CMD. Multi-stage builds for small images.
- Docker Compose — Define all your services in one YAML.
docker compose up -dand you’re running. - Docker Swarm —
docker swarm initcreates a cluster.docker service create --replicas 3deploys. - Volumes & Networking — Named volumes for data persistence. Overlay networks for multi-host communication.
- Security — Don’t run as root. Pin image versions. Use secrets. Scan for CVEs.
More Free Courses on TricksPage
- Git & GitHub Course — Master Git from basics to collaboration workflows.
- Linux Commands Course — Complete Linux command line mastery.
- n8n Automation Course — Workflow automation with 400+ integrations.
- Python Course — Master Python from beginner to pro.
- Agentic AI Course — Build intelligent AI agents.
💭 Final Thoughts
Docker isn’t just a tool — it’s a fundamental shift in how we build and deploy software. Before Docker, every deployment was a unique snowflake. After Docker, every deployment is the same: “docker run myimage.”
The combination of Docker + Compose + Swarm covers 90% of deployment needs without the complexity of Kubernetes. For startups, small teams, and medium businesses, this stack is the sweet spot: powerful enough for production, simple enough for a single developer to manage.
🔥 Final Word: “It works on my machine” is the oldest excuse in software. Docker makes it work everywhere. Containerize everything. Your future self will thank you.
The best time to containerize was yesterday. The second best time is now. 🐳
More Free Courses on TricksPage
- Git & GitHub Course — Master Git from basics to collaboration workflows.
- Linux Commands Course — Complete Linux command line mastery.
- n8n Automation Course — Workflow automation with 400+ integrations.
- Python Course — Master Python from beginner to pro.
- Agentic AI Course — Build intelligent AI agents.
If this course helped you:
- 📌 Bookmark this page for future reference
- 📤 Share it with someone who needs it
- 💬 Leave a comment — I read every one
- ⭐ Follow the blog for more deep courses