Containers Changed Everything

For self-hosters, this means you can run 20 different services on one machine without them interfering with each other.

Docker vs Podman

FeatureDockerPodman
DaemonRequires dockerd (root)Daemonless
RootlessPossible but complexNative
Composedocker compose (v2)podman-compose
EcosystemMassiveGrowing
CompatibilityStandardDocker-compatible CLI
Systemd integrationLimitedNative (podman generate systemd)

For most self-hosters: Docker is the practical choice. The ecosystem is larger, documentation is more abundant, and nearly every self-hosted project provides a Docker image and Compose file.

Podman is better if: You're security-conscious about running a root daemon, or you're on a system where Docker isn't available (some enterprise Linux distributions).

Docker Fundamentals

Images, Containers, and Volumes

  • Image — A read-only template (like a blueprint). You pull images from Docker Hub or GitHub Container Registry.
  • Container — A running instance of an image. You can have multiple containers from the same image.
  • Volume — Persistent storage that survives container restarts and recreations.

Essential Commands

# Pull an image
docker pull nextcloud:latest

# Run a container
docker run -d --name myservice -p 8080:80 -v data:/app/data nextcloud:latest

# List running containers
docker ps

# Stop / Start / Restart
docker stop myservice
docker start myservice
docker restart myservice

# View logs
docker logs myservice
docker logs -f myservice  # Follow in real-time

# Enter a running container
docker exec -it myservice bash

# Remove a container (preserves volumes)
docker rm myservice

# Remove unused images
docker image prune

Docker Compose: The Self-Hoster's Standard

Compose lets you define multi-container stacks in a single YAML file. This is how most self-hosters manage their services.

Example: Full Home Lab Stack

version: "3.8"

services:
  # ── Reverse Proxy ─────────────────
  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

  # ── File Sync ─────────────────────
  nextcloud:
    image: nextcloud:latest
    volumes:
      - nc_data:/var/www/html
    environment:
      - POSTGRES_HOST=nc_db
      - POSTGRES_DB=nextcloud
    depends_on:
      - nc_db
    restart: unless-stopped

  nc_db:
    image: postgres:16-alpine
    volumes:
      - nc_db_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=nextcloud
    restart: unless-stopped

  # ── Password Manager ─────────────
  vaultwarden:
    image: vaultwarden/server:latest
    volumes:
      - vw_data:/data
    restart: unless-stopped

  # ── Media Server ──────────────────
  jellyfin:
    image: jellyfin/jellyfin:latest
    volumes:
      - jf_config:/config
      - /mnt/media:/media:ro
    restart: unless-stopped

  # ── DNS & Ad Blocking ────────────
  pihole:
    image: pihole/pihole:latest
    ports:
      - "53:53/tcp"
      - "53:53/udp"
    volumes:
      - pihole_data:/etc/pihole
    environment:
      - TZ=Europe/London
    restart: unless-stopped

  # ── Monitoring ────────────────────
  uptime-kuma:
    image: louislam/uptime-kuma:latest
    volumes:
      - kuma_data:/app/data
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:
  nc_data:
  nc_db_data:
  vw_data:
  jf_config:
  pihole_data:
  kuma_data:

Compose Commands

# Start all services
docker compose up -d

# Stop all services
docker compose down

# Update all images
docker compose pull
docker compose up -d

# View logs for a specific service
docker compose logs -f nextcloud

# Rebuild after Compose file changes
docker compose up -d --force-recreate

Container Networking

By default, Docker creates a bridge network for each Compose stack. Containers in the same stack can reach each other by service name.

For more control:

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

services:
  caddy:
    networks:
      - frontend
  
  nextcloud:
    networks:
      - frontend
      - backend
  
  nc_db:
    networks:
      - backend

This ensures the database is only reachable from Nextcloud, not from the reverse proxy.

Updates and Maintenance

Watchtower — Automatic Updates

Watchtower monitors running containers and automatically pulls new images:

services:
  watchtower:
    image: containrrr/watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_SCHEDULE=0 0 4 * * *  # 4 AM daily
    restart: unless-stopped

Caution: Automatic updates can break things. Consider using Watchtower in notification-only mode for critical services, and auto-update only for low-risk ones.

Resource Limits

Prevent a single container from consuming all system resources:

services:
  nextcloud:
    image: nextcloud:latest
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2G
        reservations:
          cpus: "0.5"
          memory: 512M

Health Checks

Docker can monitor container health and restart unhealthy containers:

services:
  nextcloud:
    image: nextcloud:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/status.php"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

Beyond Docker Compose

When one machine isn't enough:

  • Docker Swarm — Built-in orchestration for multi-node Docker clusters. Simpler than Kubernetes, sufficient for most self-hosters.
  • Kubernetes (K3s) — Lightweight Kubernetes for home labs. Overkill for most setups, but educational.
  • Nomad — HashiCorp's alternative to Kubernetes. Simpler, multi-workload (Docker + VMs + raw binaries).

For the vast majority of self-hosters, a single machine with Docker Compose is more than enough.

Product links may include affiliate partnerships — see our affiliate disclosure for details.