For about a year my home server has run its services as a heap of docker run commands that lived, variously, in my shell history, a notes.txt, and my memory. This works right up until the box reboots after a power cut and I cannot for the life of me remember the exact -v and -p and --restart incantation for the thing that I now urgently need back. So this weekend I finally did the obvious and moved the whole house into one docker-compose.yml.
The pitch for Compose is simple: the commands stop being commands and become a file you can read, diff and commit. That last bit is the real win. The container args were the configuration, and they lived nowhere durable. Now they live in a git repo, and git log tells me why I added that odd capability to the torrent client at 1am six months ago.
what's actually in the stack
Nothing exotic. The usual self-hosted suspects: a reverse proxy, a couple of media bits, a database, monitoring. The point isn't the apps, it's that they're now declared together.
version: "3"
services:
proxy:
image: jwilder/nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./certs:/etc/nginx/certs:ro
restart: unless-stopped
db:
image: postgres:9.6
volumes:
- ./data/postgres:/var/lib/postgresql/data
env_file: .env
restart: unless-stopped
The nginx-proxy image is doing a lot of quiet work here. It watches the Docker socket and writes its own config from container labels, so adding a new service is VIRTUAL_HOST=thing.home.lan on that service and nothing else. No editing nginx by hand, which is the step I always put off and then resent.
the bits that bite
Two things caught me, and they're worth flagging because they're the difference between a stack that survives a reboot and one that comes up in a heap.
First, volumes. Every container that has state needs its data on a bind mount or a named volume, and you need to actually check which. The Postgres data directory above is on the host. If I'd left it as an anonymous volume, docker-compose down followed by an image bump could have quietly orphaned a year of data. Anything stateful gets an explicit path under ./data, and that directory gets backed up. Anything I can't point at on disk, I treat as disposable on purpose.
Second, secrets. I started with passwords inline in the YAML, realised I wanted to commit the file, and stopped. They went into a .env that's gitignored, referenced with env_file. It's not Vault, it's not even close, but it gets the credentials out of the file I'm pushing to a repo, and for a home box that's the bar.
The one genuinely pleasant surprise was depends_on plus restart: unless-stopped. Bring the whole lot up with docker-compose up -d, reboot the host, and it all comes back in roughly the right order without me. After a year of post-reboot archaeology, watching the stack just reappear was almost emotional.
was it worth a Saturday
Yes, plainly. The services run exactly as before, so there's no new capability to show off. What changed is that the configuration now exists, in one place, in version control, and a power cut is no longer a small research project. The next time I want to try a new service I'll add eight lines and a label rather than inventing a docker run line I'll have forgotten by Tuesday. That's the whole pitch, and for a homelab it's more than enough.