Intro
Production containers are easy to start but much harder to operate consistently. A useful debugging guide shows what to configure, which command proves the configuration works, and what failure looks like when the setup is wrong.
This guide covers the Docker debugging commands developers use most often: logs, exec, inspect, stats, and practical checks for containers that fail or behave unexpectedly. The goal is practical — understand the moving parts, test them locally, and avoid surprises when the same patterns move into CI/CD or production.
Workflow Overview
Effective Docker debugging follows a simple loop: identify the resource, make one configuration change, then run the command that proves the observed state matches expectations. Document what breaks when the setup is missing, misconfigured, or runs in a production-like environment.
Teams often discover hidden assumptions here. Local paths, image tags, network names, environment files, resource limits, and permissions behave differently across laptops, CI runners, and production hosts. Make those assumptions explicit before relying on the setup.
Core commands for the debugging workflow:
| Command | Purpose | Typical Use Case |
|---|---|---|
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"" | List running containers with status and ports | Quick inventory of what's up |
docker logs <container> --tail 100 | Show recent log output | Spot startup failures or runtime errors |
docker inspect <container> | Full container metadata (mounts, networks, env, health) | Verify volume mounts, network config, healthcheck status |
docker stats <container> --no-stream | Current CPU, memory, network, block I/O | Confirm resource limits are enforced |
For Compose projects, the equivalents are docker compose ps, docker compose logs -f <service>, and docker compose exec <service> sh — these let you debug without rebuilding the image.
Storage Assumptions to Verify
Before changing containers, confirm where files are actually stored. A named volume like app_data:/var/lib/app is managed by Docker and survives container rebuilds cleanly. A bind mount like ./data:/var/lib/app maps a host directory directly — useful for local development, but it introduces permission, portability, and backup problems if the same path doesn't exist on another machine.
Run a restart test: stop the container, recreate it, and confirm the application still sees the expected files. If data disappears, the service was writing to the container filesystem instead of a volume or mount.
Local Pilot Plan
A local pilot validates the debugging workflow against a realistic workload before committing to automation or promotion. Pick one representative service and run it through a complete cycle: build, run, inspect, stress, stop, recreate.
Step-by-Step Pilot
- Build with a fixed tag —
docker build -t myapp:v1.2.3 .avoids thelatesttag ambiguity that causes "it works on my machine" failures. - Run with explicit limits —
docker run -d --name pilot --memory=512m --cpus=1.0 -p 8080:80 myapp:v1.2.3makes resource constraints visible todocker stats. - Verify health and mounts —
docker inspect pilot | jq '.[0].Mounts, .[0].State.Health'confirms volumes are mounted and healthchecks pass. - Simulate load —
hey -z 30s -c 10 http://localhost:8080/(orab,wrk) generates traffic while you rundocker stats pilot --no-streamevery 10 seconds. Watch for memory growth or CPU throttling. - Check logs under load —
docker logs pilot --tail 50 --since 30sreveals timeout errors, connection pool exhaustion, or GC pauses that don't appear at idle. - Restart and validate persistence —
docker stop pilot && docker rm pilot, then rerun step 2. Query the application (ordocker exec pilot ls -la /var/lib/app) to confirm data survived.
What the Pilot Exposes
- Missing healthchecks: Container shows "healthy" but the app returns 500s.
- Volume misconfiguration: Bind mount works locally but fails in CI because the host path doesn't exist.
- Resource limits too tight: OOM kills under modest load;
docker inspectshowsOOMKilled: true. - Log driver issues: JSON-file driver fills disk;
docker logstruncates output.
Document each finding with the exact command that revealed it. This becomes your team's debugging runbook for this service.
Conclusion
Docker container debugging works best when the team treats configuration as something to test, not just copy. Keep examples small, run commands locally, and confirm expected behavior before adding services or automation.
For a next step, choose one service and document the exact commands to build, run, inspect, stop, and recreate it. Compare the results against production requirements — log retention, persistent data survival, resource quotas — so the implementation fits the larger operating model.
A reliable container workflow makes failure visible: logs are easy to find, persistent data survives container rebuilds, and local behavior matches production closely enough to catch mistakes early.