Select your stack, then choose a security level: Bad shows annotated worst-practice configs (with multiple real examples), Good covers the bare minimum, Better is production-ready, and Strictest is for compliance workloads. See exactly what changes - and why.
Not used in Bad or Good configs - limits are added in Better.
| Feature | ⚠ Bad | ✓ Good | ◆ Better | ★ Strictest |
|---|---|---|---|---|
| Pinned image versions | ✕ | ✓ | ✓ | ✓ |
Secrets via .env file | ✕ inline | ✓ | ✓ | ✓ |
| Named volumes | ✕ | ✓ | ✓ | ✓ |
| Restart policy | ✕ | ✓ | ✓ | ✓ |
| DB not exposed on host port | ✕ | ✓ | ✓ | ✓ |
| Network isolation | ✕ flat | ✓ separate | ✓ + internal | ✓ + internal |
| Health checks | ✕ | ✓ basic | ✓ app response | ✓ app response |
| Health self-heal * | ✕ | ✕ | ✕ | ✕ |
| Log rotation | ✕ | ✓ | ✓ | ✓ |
| Digest-pinned images * | ✕ | ✕ | ✕ ** | ✕ ** |
| Orphan cleanup * | ✕ | ✕ | ✕ | ✕ |
| Resource limits (CPU + memory) | ✕ | ✕ | ✓ | ✓ |
| Linux capability drop | ✕ | ✕ | ✓ | ✓ |
| PID limit | ✕ | ✕ | ✓ | ✓ |
| Read-only filesystem | ✕ | ✕ | ✕ | ★ |
| Non-root user | ✕ | ✕ | ✕ | ★ |
| no-new-privileges | ✕ | ✕ | ✕ | ★ |
* These gaps exist in plain Docker Compose regardless of tier. Health checks report status but do not restart unhealthy containers. Image tags (:latest, :16) are mutable - two pulls can give different code. Removed services leave orphan containers running. All three close automatically with Docker Swarm.
** Digest pinning (@sha256:...) is recommended for Better/Strictest workloads but cannot be generated here since digests depend on your specific image builds.
Your compose file handles container configuration. These three operational gaps exist regardless of which tier you pick - they need host-level configuration or an orchestrator.
Health checks report status. They don't restart unhealthy containers. restart: unless-stopped reacts to exit, not unhealthy.
Fix: Docker Swarm restarts unhealthy tasks automatically. Or run the autoheal sidecar.
Remove a service from your compose file, run docker compose up -d, and the old container keeps running. Compose doesn't remove it.
Fix: always run docker compose up -d --remove-orphans. Or use docker stack deploy which is declarative - no orphans possible.
The logging: block in your compose file only applies to containers you define. System containers and anything outside compose write unbounded logs.
Fix: set max-size and max-file in /etc/docker/daemon.json to cap all containers on the host.
All three close automatically when you move to Docker Swarm. See the 6 changes that upgrade your compose file to Swarm.
vmfarms runs dedicated Docker Swarm clusters with automated Trivy and Wazuh security scanning, 24/7 AI-powered incident response, and Canadian data residency - starting at bare-metal pricing.