- Use compose.yaml (not docker-compose.yml) — the modern canonical filename
- Define health checks for all services: depends_on with condition: service_healthy for startup ordering
- Use named volumes for persistent data; bind mounts only for development source code hot-reloading
- Use profiles to group optional services: profiles: ["debug", "monitoring"] for conditional inclusion
- Override with compose.override.yaml for local development settings (ports, volumes, env vars)
- Use compose.yaml as the canonical filename; compose.override.yaml for local dev overrides
- Define healthcheck on every service; use depends_on with condition: service_healthy for proper startup ordering
- Named volumes for data persistence (databases, caches); bind mounts for development source code only
- Use profiles for optional services: profiles: ["debug"] for debugging tools, ["monitoring"] for observability stack
- Environment variables: use env_file for non-sensitive config, Docker secrets for sensitive values
- Use custom networks to isolate service groups: frontend network (app + proxy), backend network (app + db)
- Set resource limits (deploy.resources.limits) even in development to catch memory leaks early
- Use build.cache_from and build.target for efficient multi-stage build integration with Compose
- Define restart policies: restart: unless-stopped for production; restart: "no" for one-off tasks
- Use extends or YAML anchors to reduce duplication across similar service definitions
- Pin image versions in compose.yaml; use latest only in compose.override.yaml for development
- Add logging configuration: driver and options to control log output format and rotation