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¶
- Create Compose File – Write docker-compose.yml locally
- Test Locally – Validate with
docker compose config
- Push to Gitea – Store in version control
- Deploy via Portainer – Use Git repository or upload
- Configure Environment – Set env vars and secrets
- 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
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"
Named Volumes¶
volumes:
db_data:
services:
postgres:
volumes:
- db_data:/var/lib/postgresql/data
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¶
- Non-Root Users – Run as PUID/PGID 1000
- Read-Only Filesystems – Where possible
- Resource Limits – Prevent resource exhaustion
deploy: resources: limits: cpus: '2.0' memory: 4G
- Secrets Management – Use Docker secrets or env files (not in compose)
- 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¶
- Restore Compose Files – Clone from Gitea
- Restore Volumes – rsync from backup location
- Recreate Stacks –
docker compose up -d
- Verify Services – Check logs and health