If you use Docker Compose purely to start a stack of containers, it's easy to miss what's been added to it. Here are five things from the last few years that actually changed how I write these files.
1. docker compose watch instead of mounts
Slow bind mounts and file permission mismatches are the first wall you hit doing container development. watch solves it from the other side: it detects host changes and pushes them into the container.
services: web: build: . develop: watch: - action: sync path: ./web target: /app/web ignore: - node_modules/ - action: rebuild path: package.json
Start it with docker compose watch and source edits are a plain sync, while a change to package.json triggers a rebuild. You get to draw that line yourself, which helps in setups where hot reload is awkward.
2. profiles for things you don't always want running
Debug services like pgAdmin or MailHog don't need to be up every time.
services: app: image: my-app db-admin: image: dpage/pgadmin4 profiles: - debug
A plain docker compose up skips db-admin; it only starts when you pass --profile debug. That removes the reason to keep a separate docker-compose.override.yml around.
3. include for splitting files up
Where you'd previously reach for extends or a chain of -f flags, there's now include.
# compose.yaml include: - path: ./infra/compose.yaml - path: ./backend/compose.yaml services: proxy: image: nginx depends_on: - backend
A Compose file owned by another team joins your project by path, nothing else.
4. depends_on doesn't wait for readiness
"The app starts before the database is ready and dies" is not something depends_on fixes. It orders startup; it doesn't know whether the process inside is accepting connections.
Pair it with a healthcheck:
services: db: image: postgres healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 backend: build: . depends_on: db: condition: service_healthy
Now backend waits until pg_isready succeeds, and you stop watching restart loops.
5. name pins the project
By default Compose uses the folder name as the project name. Rename the directory and it becomes a different project — with your volumes no longer attached to it.
name: my-super-project services: ...
A top-level name keeps the project identity regardless of where the files sit. That matters most in CI, where the directory name isn't yours to control.
While you're in there: version: '3' hasn't been needed for a long time. Files that still carry it work fine without it.