The Complete Docker Guide: From Install to Production

8 min read
Intermediate Docker Containers DevOps Guide

Prerequisites

  • Linux, Mac, or Windows machine
  • Basic terminal knowledge

Quick Answer: Install: curl -fsSL https://get.docker.com | sh. Run a container: docker run -d --name web -p 8080:80 nginx. See running containers: docker ps. Stop: docker stop web. Multi-container app: create compose.yml and run docker compose up -d.

Need a VPS? Vultr (free credit), DigitalOcean ($200 free credit), or RackNerd (cheap annual deals).

Docker packages your application and all its dependencies into a container that runs the same everywhere — your laptop, a test server, or production. No more "it works on my machine."

This guide takes you from zero to deploying Docker in production.


Part 1: Install Docker

Linux (Ubuntu/Debian)

# One-line install
curl -fsSL https://get.docker.com | sh

# Add your user to the docker group (so you don't need sudo)
sudo usermod -aG docker $USER

# Log out and back in, then verify
docker --version
docker run hello-world

Mac

Download Docker Desktop for Mac from docker.com. Install and run. Docker commands work in Terminal.

Windows

Download Docker Desktop for Windows. Requires WSL2. After install, Docker commands work in PowerShell, CMD, and WSL.


Part 2: Core Concepts

Image       = A blueprint (like a class)
Container   = A running instance of an image (like an object)
Dockerfile  = Instructions to build an image (like source code)
Registry    = Where images are stored (Docker Hub, GitHub Container Registry)
Volume      = Persistent storage that survives container restarts
Network     = How containers talk to each other

The Lifecycle

Dockerfile → docker build → Image → docker run → Container
                                                     ↓
                                              docker stop → Stopped Container
                                                     ↓
                                              docker rm → Removed

Part 3: Running Containers

Basic Run

# Run and see output
docker run nginx

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

# Run with name
docker run -d --name web nginx

# Run with port mapping (host:container)
docker run -d --name web -p 8080:80 nginx
# Visit http://localhost:8080

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

# Run interactively (get a shell)
docker run -it ubuntu bash
docker run -it --rm ubuntu bash    # Remove after exit

# Run with volume (persistent data)
docker run -d --name db \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

# Run with bind mount (share host files)
docker run -d --name web \
  -v $(pwd)/html:/usr/share/nginx/html \
  -p 8080:80 \
  nginx

Manage Containers

# List running containers
docker ps

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

# Stop
docker stop web

# Start a stopped container
docker start web

# Restart
docker restart web

# Remove
docker rm web
docker rm -f web                # Force (even if running)

# Remove all stopped containers
docker container prune

Inspect and Debug

# Logs
docker logs web
docker logs -f web              # Follow (live)
docker logs --tail 50 web       # Last 50 lines

# Shell into a running container
docker exec -it web bash
docker exec -it web sh          # If bash not available

# Run a command inside container
docker exec web cat /etc/nginx/nginx.conf

# Resource usage
docker stats
docker stats web

# Detailed info
docker inspect web

# Copy files in/out
docker cp file.txt web:/path/
docker cp web:/path/file.txt ./

Part 4: Images

Working with Images

# Search Docker Hub
docker search nginx

# Pull an image
docker pull nginx
docker pull nginx:1.25-alpine     # Specific tag
docker pull postgres:16-alpine

# List local images
docker images

# Remove an image
docker rmi nginx

# Remove unused images
docker image prune -a

Image Tags

nginx               → latest (default, not recommended for production)
nginx:1.25          → Specific version
nginx:1.25-alpine   → Alpine-based (smaller, ~5MB base)
nginx:1.25-bookworm → Debian-based (~150MB base)

Always use specific tags in production. latest can change and break things.

Alpine vs Debian-Based Images

Alpine Debian/Ubuntu
Size ~5MB base ~80-150MB base
Package manager apk apt
Shell sh (no bash) bash
Best for Production, small images Development, compatibility

Part 5: Building Images (Dockerfile)

Basic Dockerfile

# Start from a base image
FROM node:20-alpine

# Set working directory inside the container
WORKDIR /app

# Copy dependency files first (Docker caches this layer)
COPY package*.json ./

# Install dependencies
RUN npm ci --production

# Copy application code
COPY . .

# Expose port (documentation — doesn't actually publish)
EXPOSE 3000

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

Build and Run

# Build
docker build -t myapp .
docker build -t myapp:v1 .           # With tag

# Run
docker run -d --name myapp -p 3000:3000 myapp

Multi-Stage Build (Smaller Images)

# Stage 1: Build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production (only the built files)
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]

The final image only contains the build output — not the source code, dev dependencies, or build tools.

Python Dockerfile

FROM python:3.12-slim

WORKDIR /app

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

COPY . .

EXPOSE 8000

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

.dockerignore

Like .gitignore — files to exclude from the build context:

node_modules
.git
.env
*.log
dist
__pycache__
.DS_Store

Dockerfile Best Practices

  1. Use specific base image tagsnode:20-alpine not node:latest
  2. Copy dependency files first — Docker caches layers, so package.json changes less often than source code
  3. Use multi-stage builds — production image doesn't need build tools
  4. Don't run as root — add USER node or USER nobody
  5. Use .dockerignore — keep image small
  6. One process per container — don't run nginx + app + db in one container

Part 6: Docker Compose

Why Compose?

Most applications need multiple containers (app + database + cache + reverse proxy). Compose lets you define them all in one file and manage them together.

Basic compose.yml

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - api

  api:
    build: ./api
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/myapp
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Compose Commands

# Start all services
docker compose up -d

# Stop all services
docker compose down

# Stop and remove volumes (careful — deletes data)
docker compose down -v

# View logs
docker compose logs -f
docker compose logs api -f       # Single service

# Rebuild images and start
docker compose up -d --build

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

# Execute command in service
docker compose exec api bash

# List services
docker compose ps

# Pull latest images
docker compose pull

# Restart single service
docker compose restart api

More examples: Docker Compose Examples


Part 7: Volumes (Persistent Data)

Containers are ephemeral — data is lost when a container is removed. Volumes persist data.

Named Volumes (Recommended)

# Create
docker volume create mydata

# Use in a container
docker run -d -v mydata:/var/lib/mysql mysql:8

# List volumes
docker volume ls

# Inspect
docker volume inspect mydata

# Remove
docker volume rm mydata

# Remove ALL unused volumes
docker volume prune

Bind Mounts (Host Directories)

# Mount current directory into container
docker run -d -v $(pwd):/app myapp

# Read-only mount
docker run -d -v $(pwd)/config:/etc/nginx/conf.d:ro nginx
Named Volume Bind Mount
Managed by Docker You
Location Docker internal Anywhere on host
Best for Databases, persistent data Config files, development
Portability Easy to backup/restore Host-specific

Backup a Volume

docker run --rm -v mydata:/data -v $(pwd):/backup alpine \
  tar czf /backup/mydata-backup.tar.gz -C /data .

Part 8: Networking

How Containers Talk to Each Other

Containers on the same Docker network can reach each other by container name:

services:
  api:
    image: myapi
    # Can reach database at: postgres://db:5432
  db:
    image: postgres:16
    # Container name "db" is the hostname

Network Types

# List networks
docker network ls

# Create a network
docker network create mynet

# Run container on a network
docker run -d --name web --network mynet nginx

# Connect running container to network
docker network connect mynet web

# Inspect (see connected containers)
docker network inspect mynet
Network Driver Use Case
bridge Default. Containers on same host talking to each other
host Container uses host's network directly (no isolation)
none No networking
overlay Multi-host networking (Docker Swarm)

Part 9: Production Deployment

Health Checks

services:
  api:
    image: myapi
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

Resource Limits

services:
  api:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

Restart Policies

services:
  api:
    restart: unless-stopped
    # Options: no, always, on-failure, unless-stopped

Logging

services:
  api:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Security

# Don't run as root
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
USER app
WORKDIR /home/app
COPY --chown=app:app . .
CMD ["node", "server.js"]

Production Checklist

  • Use specific image tags (never latest)
  • Set resource limits (memory + CPU)
  • Configure health checks
  • Set restart policy to unless-stopped
  • Limit log size
  • Don't run as root
  • Use .dockerignore
  • Pin dependency versions
  • Use multi-stage builds
  • Back up volumes regularly

Part 10: Cleanup

# Remove stopped containers
docker container prune

# Remove unused images
docker image prune
docker image prune -a            # Including tagged images not in use

# Remove unused volumes
docker volume prune

# Remove unused networks
docker network prune

# Nuclear: remove everything unused
docker system prune -a --volumes
# WARNING: removes ALL unused images, containers, volumes, networks

# Check disk usage
docker system df
docker system df -v              # Detailed

Part 11: Troubleshooting

Container Won't Start

# Check logs
docker logs container-name

# Run interactively to see errors
docker run -it image-name bash

# Check if port is already in use
ss -tlnp | grep :8080

Can't Connect to Container

# Check port mapping
docker port container-name

# Check if container is running
docker ps

# Check container IP
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container-name

Out of Disk Space

# Check Docker disk usage
docker system df

# Clean up
docker system prune -a

# Check overlay2 storage
du -sh /var/lib/docker/

Container Exits Immediately

# Check exit code
docker inspect --format='{{.State.ExitCode}}' container-name

# Run without detaching to see output
docker run image-name

# Check if CMD/ENTRYPOINT is correct
docker inspect image-name | grep -A5 Cmd

Related Guides on SamNet