

Sarthak Varshney is a Docker Captain, 5x C# Corner MVP, and 2x Alibaba Cloud MVP, with over six years of hands-on experience in the IT industry, specializing in cloud computing, DevOps, and modern application infrastructure. He is an Author and Associate Consultant, known for working extensively with cloud platforms and container-based technologies in real-world environments.
Let me tell you something nobody says out loud when they're teaching Docker.
The first time your container refuses to start, or silently crashes two seconds after it launches, or your app is running but you can't reach it on any port — you will sit there staring at a blank terminal wondering if you even set anything up correctly.
Been there. Done that. More times than I'd like to admit.
Here's the good news: Docker failures are almost never random. There's always a reason. And once you know how to read what Docker is actually telling you — through its logs, its inspect output, its error messages — you stop guessing and start solving. That's what this article is about.
We're going to build a proper debugging instinct. Not just "try these commands and hope something works," but an actual, systematic approach to figuring out what went wrong and why.
When something breaks in Docker, most beginners immediately start tweaking things. Change a port here, restart the container there, maybe delete it and rebuild. This is the debugging equivalent of shaking your phone when an app crashes.
The right move is to ask Docker what happened before you change anything.
Think of it this way — your container is like a room where something went wrong. Before you start rearranging furniture, you want to walk in and look around. Docker gives you several tools to do exactly that. The trick is knowing which tool to reach for first.
This sounds obvious, but you'd be surprised how often people skip it. Before anything else:
docker ps
This shows you all currently running containers. If your container isn't here, it either never started or it started and then crashed.
Now run this:
docker ps -a
The -a flag shows all containers, including the ones that have stopped or exited. If your container shows up here with a status like Exited (1) or Exited (127), that exit code is your first clue.
Exit codes decoded:
Exited (0) — The container finished successfully. This is fine for short-lived containers. For a web server, this means something told it to stop.Exited (1) — General error. The app inside crashed or hit an unhandled error.Exited (127) — Command not found. Your CMD or ENTRYPOINT is trying to run something that doesn't exist inside the image.Exited (137) — The container was killed. Often because it ran out of memory (OOM killed by the kernel), or someone ran docker kill.Exited (139) — Segmentation fault. The process crashed hard, usually a bug inside the app itself.Exited (143) — The container received a SIGTERM and shut down gracefully. Normal behavior when you run docker stop.So if you see Exited (127), you immediately know the problem is in how the container was told to run — not in your app logic.
Logs are where containers talk to you. Most beginner Dockerfiles are configured to send application output to stdout and stderr, which Docker captures automatically.
docker logs <container_name_or_id>
This dumps everything the container has printed since it started. For a container that keeps crashing, you might want:
docker logs --tail 50 <container_name>
That gives you the last 50 lines — usually the most relevant part.
For something that's crashing over and over (a container in a restart loop), add the -f flag to follow the logs in real time:
docker logs -f <container_name>
Watch it start, fail, and print the error. That error message is exactly what you need to search for.
One thing people miss: logs are printed to two streams — stdout and stderr. By default, docker logs shows both. If you're seeing duplicate output or want to isolate errors, you can filter:
docker logs <container_name> 2>&1 | grep ERROR
If docker logs is your first-responder, docker inspect is the forensics team.
docker inspect <container_name_or_id>
This prints a massive JSON blob containing everything Docker knows about that container — its config, its network settings, its mounted volumes, its environment variables, its current state, when it started, when it stopped, and why.
It's a lot of output. Here's how to make it useful.
Check the container state:
docker inspect <container_name> --format='{{json .State}}'
This gives you just the State section, which looks something like:
{
"Status": "exited",
"Running": false,
"ExitCode": 1,
"Error": "",
"StartedAt": "2024-11-10T08:45:00Z",
"FinishedAt": "2024-11-10T08:45:03Z"
}
Three seconds between StartedAt and FinishedAt. Something crashed almost immediately after launch.
Check environment variables:
docker inspect <container_name> --format='{{json .Config.Env}}'
This is incredibly useful when your app depends on env vars like DATABASE_URL or API_KEY. If they're missing or wrong, this will show you instantly.
Check port mappings:
docker inspect <container_name> --format='{{json .NetworkSettings.Ports}}'
This tells you exactly which host ports are mapped to which container ports. If your app isn't reachable, start here. You might find that the mapping is missing, or that the container port doesn't match what your app is actually listening on.
Check volume mounts:
docker inspect <container_name> --format='{{json .Mounts}}'
If your app reads from a file or writes to a directory, and that volume isn't mounted correctly, you'll see it here. Wrong source path, wrong destination, wrong mode — all visible in this output.
A good habit: after running any new container, run docker inspect on it immediately. It takes 30 seconds and can save you 30 minutes of confusion later.
Sometimes you need to poke around inside a running container to understand what's going on. Like literally walk into the room.
docker exec -it <container_name> bash
Or if the image doesn't have bash (common in Alpine-based images):
docker exec -it <container_name> sh
Once you're inside, you can:
env | grep SOMETHINGping other-container-namenetstat -tlnp or ss -tlnpThis is like having a terminal inside the container itself. It's extremely useful for diagnosing subtle issues — wrong file permissions, missing config files, mismatched paths.
One caveat: you can only exec into a running container. If the container crashed, it's gone. For containers that crash on startup, you'll need a workaround — change the ENTRYPOINT to something like sleep infinity, start the container, exec in, and then manually run your actual start command to watch it fail.
Let's go through the errors you'll see most often, and exactly what they mean.
Error response from daemon: driver failed programming external connectivity:
Bind for 0.0.0.0:8080 failed: port is already allocated
Something else on your machine is already using port 8080. Could be another container, could be a process running directly on your host.
Find what's using it:
sudo lsof -i :8080
# or
sudo netstat -tlnp | grep 8080
Then either stop that process or change the port in your docker run command: -p 9090:8080 instead of -p 8080:8080.
COPY ./app /app
COPY failed: file not found in build context or excluded by .dockerignore: stat app: file does not exist
Docker builds are context-aware. When you run docker build ., it sends the current directory as the build context. If your Dockerfile references a path that doesn't exist relative to where you're running the command from — or if your .dockerignore is excluding it — you'll hit this.
Check your .dockerignore file. Also double-check that you're running docker build from the correct directory.
You run a web server container, you map the port, you open the browser, and... nothing. Connection refused.
Nine times out of ten, the app inside the container is binding to 127.0.0.1 (localhost) instead of 0.0.0.0. When an app binds to localhost, it only accepts connections from within the container. Docker port mapping can't reach it.
Fix: configure your app to listen on 0.0.0.0. For something like a Node.js server, that means app.listen(3000, '0.0.0.0'). For Flask, it's app.run(host='0.0.0.0').
The other possibility: your container is running, but you're checking the wrong port. Use docker inspect to confirm the actual port mapping.
docker inspect <container> --format='{{.State.OOMKilled}}'
# true
OOM = Out Of Memory. The Linux kernel killed your container because it tried to use more RAM than was allowed. This often shows up in containers running Java apps, ML workloads, or anything with large in-memory caches.
Either increase the memory limit: docker run -m 512m your-image, or reduce the memory usage inside your app.
Error response from daemon: network myapp_network not found
You're referencing a Docker network that either doesn't exist or was created under a different name. List your networks:
docker network ls
If you're using Docker Compose, note that Compose creates networks with a prefix based on the project name. The network might be called myproject_default instead of just default.
To check which network a container is on:
docker inspect <container_name> --format='{{json .NetworkSettings.Networks}}'
Error response from daemon: manifest for myimage:latest not found: manifest unknown
The image tag you're requesting doesn't exist in the registry. Either the image name is wrong, the tag is wrong, or you're looking at the wrong registry.
Double check: docker pull myimage:latest and look at the exact error. Try searching Docker Hub for the image name to confirm the correct tag.
When something breaks, here's the mental path to follow — in order:
Container not working?
│
├─ docker ps -a
│ ├─ Container not listed? → It was never created. Check your docker run command for syntax errors.
│ │
│ └─ Container listed but Exited?
│ ├─ Exit code 127? → CMD/ENTRYPOINT references a command that doesn't exist in the image.
│ ├─ Exit code 137? → OOM killed, or docker kill was called.
│ ├─ Exit code 1? → App crashed. Check logs next.
│ └─ Exit code 0? → Ran and finished. Was it supposed to stay running?
│
├─ docker logs <container>
│ ├─ Clear error message? → Search for that specific error, fix it.
│ ├─ "Permission denied"? → File/socket permissions inside the container.
│ ├─ "Connection refused"? → Dependent service isn't up yet, or wrong host/port.
│ └─ Silent / no output? → App might be writing to a file instead of stdout.
│
├─ Container is Running but app unreachable?
│ ├─ docker inspect → check port mappings
│ ├─ App binding to 127.0.0.1 instead of 0.0.0.0?
│ └─ Firewall rules on host blocking the port?
│
├─ App running but behaving wrong?
│ ├─ docker inspect → check env vars
│ ├─ docker inspect → check volume mounts
│ └─ docker exec -it <container> bash → poke around live
│
└─ Two containers can't talk to each other?
├─ docker network ls → are both on the same network?
├─ docker inspect → check NetworkSettings.Networks for both
└─ Are you using the container name as the hostname? (not IP address)
Follow this tree and you'll find the problem 90% of the time.
Check container resource usage in real time:
docker stats
Shows CPU, memory, network, and disk I/O for all running containers. Useful for spotting a container that's quietly eating all your RAM.
See what changed inside a container (filesystem diff):
docker diff <container_name>
Shows every file that was added (A), changed (C), or deleted (D) compared to the original image. If your app is creating unexpected files or a config is being overwritten, you'll see it here.
Check events in real time:
docker events
This is like Docker's internal activity log. Run it in one terminal while you reproduce the issue in another. You'll see exactly when containers start, stop, die, and restart — with timestamps.
Clean up and start fresh:
Sometimes you just want to nuke everything and rebuild cleanly. This command removes all stopped containers, dangling images, unused networks, and build cache:
docker system prune -a
Use carefully — it will remove things you might still want. But for a development machine that's gotten cluttered, it's a lifesaver.
Here's a deliberately broken container. Your job is to figure out what's wrong and fix it using only the commands we covered.
Challenge 1 — The Crash:
docker run --name broken-app -d nginx:alpine sh -c "nginx -g 'daemon off;' && echo done"
docker ps -a
Look at the exit code. What does it tell you? Now check the logs. What actually happened?
Challenge 2 — The Invisible App:
docker run --name invisible-web -d -p 8080:80 nginx
The container is running. But try opening http://localhost:8080 and it should work fine. Now — using only docker inspect, find out: what IP address is the container using? What is its gateway? What network is it on?
Challenge 3 — The Missing Variable:
docker run --name env-test -e APP_MODE=production -d alpine sleep 300
Exec into this container and verify that APP_MODE is actually set. Then create a second container without that env var and confirm it's missing. Use docker inspect to cross-check from outside without exec-ing in.
Debugging isn't magic. It's just asking the right questions in the right order.
Next time a container misbehaves, resist the urge to immediately delete it and start over. Walk through the tree. Check the exit code first. Read the logs. Inspect the state. Exec inside if you need to.
Docker is remarkably transparent about what's happening inside it — you just have to know where to look. The more you practice this systematic approach, the faster you'll get at it. And eventually, you'll look at an error message and know exactly what went wrong before you've even run a second command.
That's the goal. Not memorizing commands — building instinct.