Docker in Production: Lessons from the Field
Real-world patterns for running Docker in MSP environments — networking gotchas, volume management, and keeping containers healthy.
Running Docker on your laptop is easy. Running it reliably across dozens of client environments at an MSP is a different game entirely. Here's what I've learned the hard way.
Network Modes: Don't Default to Bridge
The default bridge network works fine for single-host development. In production you'll hit its limits fast:
- Container-to-container DNS only works within the same user-defined network
- The default
bridgenetwork doesn't support DNS resolution by container name - Port mapping overhead adds latency
What I use instead:
For services that need to talk to each other: always create user-defined bridge networks. DNS resolution by service name works out of the box.
docker network create app-network
docker run -d --name postgres --network app-network postgres:16
docker run -d --name api --network app-network myapi:latest
# "api" container can reach postgres at hostname "postgres"
For services that need host-level performance (e.g., a Prometheus node exporter): use --network host. You lose network isolation but gain full throughput.
Volume Management
Volumes are where most production Docker deployments get messy. My rules:
Always use named volumes, never bind mounts for application data.
Bind mounts couple your container to the host filesystem path. Named volumes are managed by Docker and survive container recreation.
# docker-compose.yml
services:
db:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data # named volume ✓
# NOT: - ./data:/var/lib/postgresql/data # bind mount ✗
volumes:
postgres_data:
Exception: config files and secrets. Bind-mounting a config file from /etc/myapp/config.yaml is fine and makes editing straightforward.
Backup Strategy
Named volumes don't back themselves up. My approach for client environments:
# Dump to a tarball on the host
docker run --rm \
-v postgres_data:/data \
-v /backup:/backup \
alpine tar czf /backup/postgres_data_$(date +%Y%m%d).tar.gz -C /data .
Pair this with a cron job and offsite sync (rsync to a NAS or Backblaze B2).
Health Checks: Stop Relying on "Running" Status
A container can be "running" and completely broken. Always define a HEALTHCHECK:
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
In Compose:
services:
api:
image: myapi:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
With health checks defined, docker ps shows healthy/unhealthy status, and dependent services (via depends_on: condition: service_healthy) won't start until dependencies pass.
Restart Policies
Every production container should have a restart policy. I use unless-stopped as my default — it survives reboots but doesn't fight you when you deliberately stop a container for maintenance.
services:
app:
image: myapp:latest
restart: unless-stopped
Avoid always in most cases — if a container crashes in a tight loop, always will hammer your host. unless-stopped combined with health checks gives you the right behavior.
Logging
The default json-file driver is fine until your logs fill the disk. Set limits:
services:
app:
image: myapp:latest
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
For centralised logging across clients, I ship to a Loki instance via the loki logging driver. One Grafana dashboard covers everything.
Compose in Production
docker compose is underrated for production on single hosts. It gives you:
- Declarative service definitions in version control
docker compose pull && docker compose up -dfor zero-downtime-ish updates- Easy rollback by pinning image tags
For multi-host orchestration, Swarm or Kubernetes come into play — but most MSP client workloads don't need that complexity. A well-configured single host with Compose handles more than you'd expect.
The biggest lesson: treat your Docker setup like infrastructure code. Version control your Compose files, document your volume layout, and test your backup restores. The failure mode isn't usually Docker itself — it's the operational practices around it.