Things break in containers. A service won’t start, a container keeps restarting, the app throws weird errors. The good news is Docker gives us great tools to figure out what’s going on.
The essentials
These are the commands we’ll use every single day.
# List running containers
docker ps
# List ALL containers (including stopped/crashed ones)
docker ps -a
# View container logs
docker logs my-app # all logs
docker logs -f my-app # follow (like tail -f)
docker logs --tail 50 my-app # last 50 lines
docker logs --since 5m my-app # last 5 minutes
# Get a shell inside a running container
docker exec -it my-app sh # alpine/minimal images
docker exec -it my-app bash # debian/ubuntu images
# Run a one-off command in a container
docker exec my-app cat /etc/hosts
Inspecting a container
docker inspect is the Swiss Army knife. It dumps everything Docker knows about a container — network settings, mounts, environment variables, restart policy, health status, and more.
# Full JSON dump (very verbose)
docker inspect my-app
# Get specific fields using Go templates
docker inspect --format='{{.State.Status}}' my-app
docker inspect --format='{{.NetworkSettings.IPAddress}}' my-app
docker inspect --format='{{.State.ExitCode}}' my-app
docker inspect --format='{{json .Mounts}}' my-app | jq
Debugging a crashed container
When a container exits immediately or keeps restarting, here’s our debugging flow:
docker ps -a
→ find the stopped container, check STATUS column
docker logs my-app
→ read error messages from stdout/stderr
docker inspect my-app
→ check ExitCode, OOMKilled, Error fields
# Check why a container exited
docker inspect --format='{{.State.ExitCode}}' my-app
# Exit code 0 = normal exit
# Exit code 1 = application error
# Exit code 137 = killed (OOM or docker stop)
# Exit code 139 = segfault
# Check if it was killed due to out-of-memory
docker inspect --format='{{.State.OOMKilled}}' my-app
# Start a crashed container's image with a shell to poke around
docker run -it --entrypoint sh my-app:latest
Resource monitoring
# Live resource usage (CPU, memory, network I/O)
docker stats
# Resource usage for specific containers
docker stats my-app db redis
# See running processes inside a container
docker top my-app
# Copy files between host and container
docker cp my-app:/app/logs/error.log ./error.log
docker cp ./config.json my-app:/app/config.json
Common issues and fixes
Port conflict — “Bind for 0.0.0.0:3000 failed: port is already allocated”
# Find what's using that port
lsof -i :3000
# Either stop that process or use a different host port
docker run -p 3001:3000 my-app
Permission denied — often happens with volumes when the container runs as a non-root user but the volume files are owned by root.
# Fix: match the user ID in the container
docker run -u $(id -u):$(id -g) -v $(pwd):/app my-app
OOM Killed — container used more memory than its limit. Exit code 137.
# Check memory limit
docker inspect --format='{{.HostConfig.Memory}}' my-app
# Run with a higher memory limit
docker run -m 512m my-app
Cleanup commands
Docker doesn’t automatically clean up old stuff. Over time, unused images, stopped containers, and dangling volumes eat up disk space.
# Remove stopped containers
docker container prune
# Remove unused images (not used by any container)
docker image prune
# Remove unused volumes (careful — might delete data!)
docker volume prune
# Nuclear option — remove everything unused
docker system prune -a --volumes
# Check disk usage
docker system df
A good habit: run docker system prune every few weeks on development machines. On production, be more careful — we probably don’t want to accidentally remove volumes with data.