Skip to content

Docker & Portainer

Docker is the primary container runtime for the homelab, running 100+ containerized services across multiple hosts. Portainer provides centralized management, monitoring, and deployment capabilities through a web-based interface.

Architecture

Multi-Host Setup

  • Primary Docker Host – Main VM with high resources for production services
  • Development Host – Separate environment for testing
  • VPS Docker Host – Public-facing services (Traefik, Authelia, MkDocs)
  • Portainer Central – Single control plane managing all hosts

Network Configuration

  • Bridge Networks – Isolated networks per stack
  • Proxy Network – Shared network for Traefik reverse proxy
  • Macvlan Networks – Direct host network access for specific containers
  • Overlay Networks – Multi-host networking (future Swarm migration)

Portainer Features

Stack Management

  • Deploy Stacks – Upload docker-compose.yml files via UI
  • Template Library – Pre-configured app templates
  • Git Integration – Pull compose files from Gitea repositories
  • Environment Variables – Manage env vars per stack

Container Operations

  • Quick Actions – Start, stop, restart, kill containers
  • Console Access – Web-based shell into containers
  • Logs Viewer – Real-time and historical container logs
  • Stats Dashboard – CPU, memory, network usage per container

Image Management

  • Pull Images – Download images from registries
  • Build Images – Build from Dockerfiles in UI
  • Registry Integration – Connect to private registries
  • Image Cleanup – Remove unused images

Access Control

  • User Management – Multiple users with role-based access
  • Team Support – Organize users into teams
  • Endpoint Permissions – Restrict access to specific hosts
  • Audit Logs – Track all user actions

Deployment Workflow

Stack Deployment Process

  1. Create Compose File – Write docker-compose.yml locally
  2. Test Locally – Validate with docker compose config
  3. Push to Gitea – Store in version control
  4. Deploy via Portainer – Use Git repository or upload
  5. Configure Environment – Set env vars and secrets
  6. Launch Stack – Deploy and monitor startup

Example Stack Structure

version: '3.8'

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Chicago
    volumes:
      - /opt/stacks/jellyfin/config:/config
      - /mnt/media:/media:ro
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.local`)"
      - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
    restart: unless-stopped

networks:
  proxy:
    external: true

Service Organization

Stack Categories

Stacks are organized by function for easier management:

/opt/stacks/
├── media/
│   ├── docker-compose.yml    # Jellyfin, *arr apps
│   └── .env
├── automation/
│   ├── docker-compose.yml    # N8N, Home Assistant
│   └── .env
├── monitoring/
│   ├── docker-compose.yml    # Checkmk, Graylog
│   └── .env
├── productivity/
│   ├── docker-compose.yml    # Vaultwarden, Paperless
│   └── .env
└── infrastructure/
    ├── docker-compose.yml    # Traefik, Portainer
    └── .env

Volume Management

Persistent Storage Strategies

Host Bind Mounts

volumes:
  - /opt/stacks/app/data:/data  # Config and app data
- Direct access to host filesystem - Easy backup and inspection - Performance considerations on network mounts

NFS Mounts

volumes:
  - type: volume
    source: media
    target: /media
    volume:
      nocopy: true
      driver: local
      driver_opts:
        type: nfs
        o: addr=truenas.local,rw
        device: ":/mnt/pool/media"
- Centralized storage on TrueNAS - Shared across multiple containers - Network overhead but better organization

Named Volumes

volumes:
  db_data:

services:
  postgres:
    volumes:
      - db_data:/var/lib/postgresql/data
- Docker-managed storage - Portable across hosts - Abstracted from filesystem

Networking Deep Dive

Proxy Network Pattern

Shared network for Traefik integration:

# Create external proxy network
docker network create proxy

# All web services join this network
# Traefik auto-discovers services via labels

Service-to-Service Communication

networks:
  backend:
    internal: true  # No external access

services:
  app:
    networks:
      - proxy      # Web access
      - backend    # Database access

  database:
    networks:
      - backend    # Internal only

Update Management

Manual Updates

# Pull latest images
docker compose pull

# Recreate containers with new images
docker compose up -d

# Remove old images
docker image prune -f

Automated Updates

  • WUD (Watchtower Update Daemon) – Monitors for updates
  • Notifications – Discord/Slack alerts for new versions
  • Staged Rollout – Test stack updated first, then production

Rollback Strategy

# List recent images
docker images app --format "{{.Tag}}" | head -5

# Rollback to previous version
docker tag app:previous app:latest
docker compose up -d

Monitoring & Logging

Built-in Portainer Metrics

  • Container CPU/memory usage graphs
  • Network I/O statistics
  • Container event history

External Monitoring

  • Checkmk – Container health checks and resource monitoring
  • Graylog – Centralized logging with Docker driver
  • Netdata – Real-time system metrics

Logging Configuration

services:
  app:
    logging:
      driver: gelf
      options:
        gelf-address: "udp://graylog.local:12201"
        tag: "app"

Security Best Practices

Container Security

  1. Non-Root Users – Run as PUID/PGID 1000
  2. Read-Only Filesystems – Where possible
  3. Resource Limits – Prevent resource exhaustion
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4G
    
  4. Secrets Management – Use Docker secrets or env files (not in compose)
  5. Network Isolation – Internal networks for backend services

Image Trust

  • Official Images – Prefer verified publishers
  • Pinned Tags – Avoid :latest, use specific versions
  • Registry Scanning – Scan images for vulnerabilities
  • Private Registry – Host critical images internally

Troubleshooting

Common Commands

# View logs
docker compose logs -f [service]

# Inspect container
docker inspect [container]

# Access shell
docker exec -it [container] /bin/bash

# Check networks
docker network inspect [network]

# Resource usage
docker stats

# Container processes
docker top [container]

Health Checks

services:
  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Backup & Recovery

Backup Strategy

# Backup compose files (in Git)
git add docker-compose.yml .env
git commit -m "Update stack config"

# Backup volumes (automated via script)
rsync -av /opt/stacks/ /mnt/backup/stacks/

# Export Portainer config
curl -X POST http://portainer.local/api/backup

Disaster Recovery

  1. Restore Compose Files – Clone from Gitea
  2. Restore Volumes – rsync from backup location
  3. Recreate Stacksdocker compose up -d
  4. Verify Services – Check logs and health

Resources