My home box runs a steadily growing pile of services, and for a while each one was started its own way: this one a hand-rolled docker run in a tmux pane, that one a half-remembered systemd unit, another a script I was frankly scared to look at. It worked right up until I needed to reboot, at which point bringing everything back was an archaeology project. Docker Compose fixed that, and the structure I landed on is the bit worth writing down.
One stack, not one giant file
The temptation is a single enormous docker-compose.yml with everything in it. I tried that. It is fine until you want to restart one thing and not the lot, or until a merge conflict in the media stack takes down the DNS server because they share a file.
What works better is one directory per logical stack, each with its own compose file:
services/
proxy/
docker-compose.yml
media/
docker-compose.yml
monitoring/
docker-compose.yml
Each stack is self-contained: its own services, its own .env, its own volumes. I can docker-compose up -d one of them without thinking about the others. The reverse proxy lives in its own stack and is the only thing exposed to the outside, with everything else reachable by container name on shared networks.
Networks are the glue
The one cross-cutting concern is networking. I declare a couple of external Docker networks once, and any stack that needs to talk to the proxy or the database joins the relevant one:
networks:
proxy:
external: true
backend:
external: true
The reverse proxy sits on proxy, the apps sit on both, the database sits only on backend. That gives me a tidy rule: nothing reaches the database from outside, and nothing reaches the apps except through the proxy. The network topology enforces the security boundary so I don't have to remember it.
The boring parts that matter
A few habits that have paid off:
- Pin image tags.
latestis how you find out a service has changed its config format at the worst possible moment. - Keep volumes named and explicit, never anonymous. Future-me wants to know exactly where the data lives when it is time to back it up.
- Put the
.envfiles in the backup, but never in version control. The compose files go to Git; the secrets stay local.
Bringing it all back
The real win shows up at reboot. A tiny script walks the services/ directory and runs docker-compose up -d in each, in a sensible order so the proxy and database come up before the things that depend on them. The whole house comes back in under a minute, in the right sequence, with no archaeology.
It is not clever. That is exactly why I like it. A reboot used to be a small adventure; now it is a non-event, which is the highest praise I can give any piece of home infrastructure.