I've seen compose files that span three hundred lines, define seventeen networks, and require a dedicated README just to explain the environment variables. I've also written a few of them. That era is over. Every stack I deploy now follows a small set of principles that I've refined through a lot of painful rollbacks.
The Problem with Bloat
Configuration drift is silent and cumulative. A commented-out service here, an undocumented environment variable there — and six months later, no one (including you) knows what's load-bearing and what's vestigial. The compose file stops being documentation and becomes archaeology.
If you can't redeploy your stack from scratch in under ten minutes, your compose file has already drifted past the point of trustworthiness. — Personal notes, 2025
My Principles
These aren't rules I found in a book. They're the residue of things going wrong in production — or what passes for production in a home lab.
- One service, one file. Don't bundle unrelated services into a single compose. If they don't share a network, they don't share a file.
- Pin your tags.
latestis a liability. Always pin to a specific version and update deliberately. - Named volumes over bind mounts for persistence. Bind mounts are fine for config; named volumes are for data you can't afford to lose.
- Restart policy by default. Every service that should survive a reboot gets
restart: unless-stopped. - Networks are explicit. If a service doesn't need to talk to another, it doesn't go on the same network. Principle of least connectivity.
A Real Example
Here's what a clean, minimal stack looks like in practice — this is my Vaultwarden deployment:
services:
vaultwarden:
image: vaultwarden/server:1.30.5
restart: unless-stopped
environment:
DOMAIN: "https://vault.yourdomain.com"
SIGNUPS_ALLOWED: "false"
volumes:
- vw-data:/data
networks:
- proxy
volumes:
vw-data:
networks:
proxy:
external: true
That's it. No port mappings — NPM handles ingress. No custom build steps. No environment file for three variables. The intent is readable at a glance, and I could redeploy this from memory if I had to.
On Environment Files
.env files are powerful and easy to misuse. I use them only when a service
has more than four or five variables, or when values need to stay out of version control.
For anything smaller, inline environment variables in the compose file are clearer — you
see the config and the service definition in the same place.
What I never do: commit .env files to git. Not even with placeholder values.
Use a .env.example instead and document every variable.
Lessons Learned
The compose file isn't just an operational artifact — it's documentation. Treat it like code you'll have to read at 2am during an outage, because you will. Every line should justify its existence. If you're not sure what a setting does, that uncertainty is a signal to either understand it or remove it.
Minimalism in infrastructure isn't laziness. It's the discipline to resist adding complexity until complexity is genuinely required. Most of the time, it never is.