A systematic approach to diagnosing and resolving the most common Docker issues — from container crashes to networking failures
A systematic approach prevents random guessing and saves hours of debugging time.
docker ps -adocker logs <container>docker inspectdocker statsAlways check docker logs first — 80% of container issues are visible in the application logs before you need any other tool.
| Exit Code | Meaning | Common Cause |
|---|---|---|
| 0 | Normal exit | Process completed successfully |
| 1 | Application error | Unhandled exception, config error, missing file |
| 126 | Permission denied | Entrypoint/CMD not executable |
| 127 | Command not found | Binary missing from image, wrong PATH |
| 137 | SIGKILL (128+9) | OOM killed or docker kill |
| 139 | SIGSEGV (128+11) | Segmentation fault in application |
| 143 | SIGTERM (128+15) | Graceful stop via docker stop |
# Check exit code of a stopped container
docker inspect --format='{{.State.ExitCode}}' my-container
# See full state including OOMKilled flag
docker inspect --format='{{json .State}}' my-container | jq .
Container logs capture everything written to stdout and stderr by the main process (PID 1).
# Basic log viewing
docker logs my-container # all logs
docker logs --tail 100 my-container # last 100 lines
docker logs --since 5m my-container # last 5 minutes
docker logs -f my-container # follow (live tail)
docker logs --timestamps my-container # show timestamps
# Combine flags for precision
docker logs --since 2024-01-01T10:00:00 --until 2024-01-01T11:00:00 my-app
json-file — default, stored on disklocal — optimised, compressedsyslog — sends to syslog daemonjournald — systemd journalfluentd / gelf / awslogsdocker logs# Check which log driver a container uses
docker inspect --format='{{.HostConfig.LogConfig.Type}}' my-container
# Limit log file size in daemon.json or per-container
docker run --log-opt max-size=10m --log-opt max-file=3 my-image
Shell into a running container to inspect state, test connectivity, and debug in real time.
# Interactive shell
docker exec -it my-container /bin/bash
docker exec -it my-container /bin/sh # alpine / minimal images
# Run a single command
docker exec my-container cat /etc/hosts
docker exec my-container env # check environment variables
docker exec my-container ps aux # see running processes
# As a specific user
docker exec -u root my-container apt-get update
# Set environment variables for the exec session
docker exec -e DEBUG=true my-container ./run-diagnostics.sh
cat /etc/resolv.conf — DNS configping / curl / wget — connectivitydf -h — disk space inside containertop / htop — process resource usagels -la /app — verify files & permissionsdocker run insteaddocker run -it --entrypoint /bin/sh imagedocker debug my-container (Docker Desktop)docker inspect returns the complete JSON configuration of any Docker object — the single most powerful diagnostic command.
# Full JSON output (pipe to jq for readability)
docker inspect my-container | jq .
# Targeted queries with Go templates
docker inspect -f '{{.State.Status}}' my-container
docker inspect -f '{{.NetworkSettings.IPAddress}}' my-container
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' my-container
docker inspect -f '{{json .Config.Env}}' my-container | jq .
# Inspect images and networks too
docker image inspect nginx:latest -f '{{.Config.ExposedPorts}}'
docker network inspect bridge | jq '.[0].Containers'
Real-time resource usage for all running containers — your first stop for performance issues.
# Live dashboard (updates every second)
docker stats
# One-shot snapshot (good for scripts)
docker stats --no-stream
# Specific containers with custom format
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" web api db
# Check memory limits vs actual usage
docker inspect -f '{{.HostConfig.Memory}}' my-container # 0 = unlimited
| Column | What It Shows | Warning Signs |
|---|---|---|
| CPU % | CPU usage vs available cores | >100% = using multiple cores heavily |
| MEM USAGE/LIMIT | Current memory / hard limit | Approaching limit = OOM risk |
| NET I/O | Network bytes in/out | Unexpectedly high = possible loop |
| BLOCK I/O | Disk read/write bytes | High writes = logging or temp files |
| PIDs | Number of processes | Growing count = possible fork bomb |
docker run --memory=512m --cpus=1.5 --pids-limit=100 my-image — always set limits in production to prevent a single container from taking down the host.
Build failures happen at image creation time. The key is identifying which layer failed and why.
.dockerignoreapt-get update before install--progress=plain for full output--no-cache to rule out stale layersRUN ls -la to verify file state--targetDOCKER_BUILDKIT=0 to get intermediate IDs# Verbose build output
docker build --progress=plain --no-cache -t myapp .
# Debug a failed multi-stage build at a specific stage
docker build --target=builder -t myapp-debug .
docker run -it myapp-debug /bin/sh
# With BuildKit, use inline cache for CI
docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t myapp .
Docker caches each layer. When the cache breaks unexpectedly — or does not break when it should — you get confusing behaviour.
. before installing deps invalidates cache on every file changeADD with URLs always re-downloadsapt-get update cached with old package listpip install -r requirements.txt cached but file changed--no-cache for CI releases# BAD: cache busts on any source change
COPY . /app
RUN npm install
# GOOD: install deps first, then copy source
COPY package.json package-lock.json /app/
RUN npm install
COPY . /app
# Bust cache from a specific point using build arg
ARG CACHE_BUST=1
RUN git clone https://github.com/org/repo.git
Networking is the most common source of "works locally, fails in Docker" issues.
/etc/resolv.conf--dns 8.8.8.8localhost inside container != hosthost.docker.internal for hostport is already allocatedlsof -i :8080 to find culprit-p# Debug networking step by step
docker network ls # list networks
docker network inspect my-network # show connected containers
docker exec my-container cat /etc/resolv.conf # check DNS config
docker exec my-container ping other-container # test connectivity
docker exec my-container curl -v http://api:3000/health # test service discovery
# Create a user-defined network for DNS resolution
docker network create my-app-net
docker run --network my-app-net --name api my-api-image
docker run --network my-app-net --name web my-web-image # can reach "api" by name
Bind mounts and volumes cause some of the most frustrating debugging sessions, especially around file permissions.
:z or :Z)CRLF)RUN chown -R appuser:appuser /appdocker run --user $(id -u):$(id -g)umask in entrypoint script# Debug volume/permission issues
docker exec my-container ls -la /app # check ownership
docker exec my-container id # who am I in the container?
docker inspect -f '{{json .Mounts}}' my-container | jq . # list mounts
# Fix ownership at runtime
docker exec -u root my-container chown -R 1000:1000 /data
# SELinux context (Fedora/RHEL)
docker run -v /host/data:/data:z my-image # shared label
docker run -v /host/data:/data:Z my-image # private label
A container in a restart loop is one of the most common and frustrating patterns. The key is breaking the cycle to inspect state.
docker logs --tail 50 my-appdocker update --restart=no my-appdocker run --entrypoint /bin/sh -it imagedocker inspect -f '{{.RestartCount}}'docker inspect -f '{{.State.OOMKilled}}'# Full restart debugging workflow
docker ps -a | grep my-app # see status & restart count
docker logs --tail 50 my-app # read the crash output
docker inspect -f '{{.State.ExitCode}}' my-app # what exit code?
docker inspect -f '{{.State.OOMKilled}}' my-app # was it OOM?
docker update --restart=no my-app # stop the loop
docker run -it --entrypoint /bin/sh my-image # debug interactively
| Error | Cause |
|---|---|
manifest unknown | Tag does not exist in registry |
pull access denied | Private repo, need docker login |
no matching manifest for linux/arm64 | Image not built for your architecture |
TLS handshake timeout | Network issue or proxy config |
toomanyrequests | Docker Hub rate limit (100 pulls/6h) |
docker login registry.example.comdocker manifest inspect image:tagimage@sha256:abc...daemon.json# Debug pull issues
docker pull --platform linux/amd64 nginx:latest # force platform
docker manifest inspect nginx:latest # check available architectures
docker login ghcr.io # authenticate to GitHub registry
# Check rate limit status (Docker Hub)
curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nginx:pull" \
| jq -r .token | xargs -I {} curl -sI -H "Authorization: Bearer {}" \
https://registry-1.docker.io/v2/library/nginx/manifests/latest 2>&1 | grep ratelimit
Docker can silently consume tens of gigabytes with old images, stopped containers, and unused volumes.
# See what Docker is using
docker system df # summary: images, containers, volumes, build cache
docker system df -v # detailed breakdown per object
# The nuclear option: remove everything unused
docker system prune -a --volumes # WARNING: removes all stopped containers,
# unused images, unused networks, and volumes
docker container prune — remove stopped containersdocker image prune -a — remove unused imagesdocker volume prune — remove orphan volumesdocker builder prune — clear build cachedocker image prune --filter "until=720h" — older than 30 days--rm flag for ephemeral containersdocker system prune -f.dockerignore to exclude large files# Find the biggest images
docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -hr | head -10
# Find containers writing lots of data to their writable layer
docker ps -s --format "table {{.Names}}\t{{.Size}}"
When containers are slow, systematically check CPU, memory, I/O, and networking.
docker stats shows CPU %--cpus, --cpu-sharesdocker exec my-app top -bn1 — find hot processhtop, check steal time in VMsdocker stats shows MEM USAGE / LIMITdmesg | grep -i oom-XX:MaxRAMPercentage=75--max-old-space-size=384docker exec my-app iostat (if available)--tmpfs /tmp--network host removes NAT (Linux only)# Host-level process inspection
docker inspect -f '{{.State.Pid}}' my-container # get PID on host
sudo strace -p <PID> -c # syscall profiling
sudo nsenter -t <PID> -n ss -tlnp # network sockets from container namespace
When the problem is not the container but Docker itself — daemon logs reveal engine-level errors.
| OS / Init | Command |
|---|---|
| systemd (most Linux) | journalctl -u docker.service -f |
| macOS (Docker Desktop) | ~/Library/Containers/.../log/vm/dockerd.log |
| Windows (Docker Desktop) | Event Viewer → Application |
| Amazon Linux / CentOS | /var/log/messages |
"debug": true to /etc/docker/daemon.jsonsudo systemctl reload dockersudo kill -SIGHUP $(pidof dockerd)# Check daemon status
sudo systemctl status docker
sudo journalctl -u docker.service --since "1 hour ago"
# Enable debug logging
sudo tee /etc/docker/daemon.json <<'EOF'
{
"debug": true,
"log-level": "debug"
}
EOF
sudo systemctl reload docker
Storage driver errors, out of disk space, failed to start (port 2376 in use), DNS resolution failures, cgroup v1/v2 incompatibility, AppArmor/SELinux denials.
Third-party tools that make Docker troubleshooting significantly easier.
dive my-image:latestdive --ci my-image (fails on waste)htop but for Dockerdocker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock quay.io/vektorlab/ctopbrew install lazydocker or go installnsenter -t PID -n ip addrnsenter -t PID -m cat /etc/hosts# Install and run common tools
brew install dive lazydocker ctop # macOS
apt install -y strace ltrace tcpdump # Linux debugging essentials
# Use docker debug (Docker Desktop 4.27+)
docker debug my-container # drops into a shell with tools pre-installed
| Symptom | First Command | Next Step |
|---|---|---|
| Container won't start | docker logs <name> | Check exit code with docker inspect |
| Container keeps restarting | docker logs --tail 50 | docker update --restart=no |
| App not reachable | docker port <name> | Check app binds to 0.0.0.0, not 127.0.0.1 |
| Container slow | docker stats | Check limits, profile with strace |
| Out of disk space | docker system df | docker system prune -a |
| Build failing | docker build --progress=plain | --no-cache, check .dockerignore |
| Can't pull image | docker manifest inspect | docker login, check tag exists |
| Permission denied | docker exec ls -la /app | Match UID/GID, check SELinux |
| DNS not resolving | docker exec cat /etc/resolv.conf | Use user-defined network |
| OOM killed | docker inspect {{.State.OOMKilled}} | Increase --memory limit |
docker ps -a && docker logs --tail 30 <container> && docker inspect -f '{{json .State}}' <container> | jq .
docker logs and docker inspect