Docker Compose Best Practices for Home Servers
Structure stacks, volumes, networks, secrets and updates so your Compose homelab stays clean, secure and easy to rebuild.
Why conventions matter
A home server can easily grow to 30+ containers. Without a consistent structure, you end up with mystery volumes, :latest tags that broke six months ago, and no idea how to rebuild after a disk failure. These practices keep your Docker homelab maintainable and reproducible.
1. One folder (and stack) per app group
Give each service or logically-related group its own directory with its own compose.yaml:
~/docker/
nextcloud/compose.yaml
media/compose.yaml # jellyfin + arr stack together
monitoring/compose.yaml # grafana + prometheusThis isolates failures, makes updates independent, and lets you back up or move one app without touching the rest.
2. Keep everything in version control
Store your compose files (and non-secret config) in a Git repository. This gives you history, easy rollbacks, and a documented source of truth. Commit after every change; your future self will thank you when rebuilding.
3. Always use named volumes or bind mounts
Never let important data live only inside a container — it vanishes when the container is recreated. Mount persistent data explicitly:
services:
app:
image: my/app
volumes:
- ./config:/config # bind mount, easy to back up
- app_data:/var/lib/app # named volume, managed by Docker
volumes:
app_data:Bind mounts (like ./config) are the easiest to back up because you can see them in the filesystem.
4. Pin image tags — avoid :latest
:latest means "whatever is newest today", which makes builds unpredictable and upgrades surprising. Pin a specific major/minor version:
image: nextcloud:29-apache # not nextcloud:latestUpdate deliberately by bumping the tag, reading release notes, and testing — not by accident.
5. Handle secrets safely
Don't hard-code passwords in compose.yaml (which lives in Git). Use an .env file that is git-ignored:
environment:
- DB_PASSWORD=${DB_PASSWORD}# .env (add to .gitignore)
DB_PASSWORD=super-secret-valueFor higher security, use Docker secrets or a secrets manager.
6. Set restart policies and healthchecks
Make services resilient across reboots and crashes:
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 30s
retries: 3A healthcheck lets Docker (and dependencies) know a container is truly ready, not just running.
7. Use explicit networks and a reverse proxy
Create a shared network for web apps and put a reverse proxy in front instead of publishing a dozen ports:
networks:
proxy:
external: trueContainers reach each other by name on the same network; you expose only 80/443 to your LAN. See Docker networking explained.
8. Pin resource limits where needed
Stop one runaway container from starving the host:
deploy:
resources:
limits:
memory: 1g9. Plan updates and cleanup
- Update images intentionally:
docker compose pull && docker compose up -d. - Read release notes for anything with a database.
- Prune periodically:
docker image prunereclaims space from old layers. - Back up volumes before major upgrades.
10. Make rebuilds boring
The end goal: if your server died today, you could restore from Git (compose files) plus your volume backups and be running again in under an hour. Test that assumption occasionally — a backup you've never restored is just a hope.
Next steps
Apply these patterns as you deploy services like Nextcloud, the *arr stack, and monitoring, and manage them visually with Portainer.
Verwandte Artikel
Docker Networking Explained for Home Servers
Understand bridge, host and macvlan networks, container DNS, and how to expose services safely behind a reverse proxy.
Docker unter Ubuntu installieren
Ein Schritt-für-Schritt-Tutorial zur Installation von Docker Engine und Docker Compose auf Ubuntu Server – inklusive erstem Container.
Docker-Grundlagen für Self-Hosting
Lerne Images, Container, Volumes und Docker Compose, um selbst gehostete Apps souverän bereitzustellen.