LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 1213 min read

Debugging Containers

Find out why a container exits, crashes or misbehaves: exit codes, logs, inspect, exec, restart policies, health checks, stats and docker cp.

On this page 12 sections

Introduction

Containers fail like any software: a typo in a command, a missing package, a wrong port, an app that runs out of memory. The good news is that Docker gives you a small, reliable set of tools to find out what happened.

What You Will Learn
  • A step-by-step routine for diagnosing a broken container.
  • How to read exit codes, logs and docker inspect.
  • How restart policies and health checks keep services running.
  • How to look inside running containers with exec, top, stats, diff and cp.

A Debugging Checklist

Mini Challenge · Put It in Order

Put this debugging routine in a sensible order.

  1. docker inspect NAME — check the exit code, command, ports and environment
  2. Fix the cause, rebuild if needed, and start a new container
  3. docker ps -a — is the container running, and what is its status?
  4. docker logs NAME — what did the app print before it failed?
  5. docker exec -it NAME sh — look around inside, if it is running

The Container Will Not Start

If the command itself is wrong, Docker cannot start the container at all. A typo:

docker run --name oops quotes-api pyhton app.py

Docker reports that the executable “pyhton” was not found in $PATH, and docker run exits with code 127. The container exists but never ran, so docker ps -a shows it with status Created. Read the error, fix the command, then remove the failed container with docker rm oops.

The Container Crashes

More often the app starts and then fails. Here the app tries to import a package that is not installed in the image:

docker run -d --name crash quotes-api python -c "import yaml"
docker ps -a --filter name=crash --format "{{.Names}}: {{.Status}}"

The status is Exited (1). The logs show exactly why — a Python traceback ending in ModuleNotFoundError: No module named 'yaml':

docker logs crash
docker inspect crash --format "Status: {{.State.Status}}, exit code: {{.State.ExitCode}}"

The fix is in the image: add the package to requirements.txt and rebuild. Logs are kept after a container stops, so you can always read why it died — until you remove it.

Restart Policies

A restart policy tells Docker what to do when a container’s main process exits:

PolicyBehaviour
no (default)Never restart
on-failure[:N]Restart only after a non-zero exit code, optionally at most N times
unless-stoppedAlways restart (also after a reboot), unless you stopped it yourself
alwaysAlways restart, even if you stopped it, once the Docker daemon restarts
docker run -d --name flaky --restart on-failure:3 alpine sh -c "echo starting...; sleep 1; exit 1"
docker logs flaky
docker inspect flaky --format "Restarts: {{.RestartCount}}, status: {{.State.Status}}"

After a few seconds the log shows “starting...” four times — the first run plus three restarts — and then Docker gives up, leaving the container exited. A container stuck in a restart loop shows the status Restarting in docker ps; its logs tell you why it keeps failing.

Health Checks

A running process is not necessarily a working app — it could be stuck. A health check is a command Docker runs regularly inside the container; if it fails several times in a row, the container is marked unhealthy. The slim Python image has no curl, so the check uses Python itself:

docker run -d --name api -p 5000:5000 \
--health-cmd "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:5000/')\"" \
--health-interval 5s \
quotes-api
docker ps --filter name=api --format "{{.Names}}: {{.Status}}"

The status goes from (health: starting) to (healthy). Health checks are usually written into the Dockerfile with HEALTHCHECK (lesson 14), and Compose uses them for depends_on conditions (lesson 10).

Looking Inside a Running Container

CommandWhat it shows
docker exec -it api shA shell inside the container (bash if the image has it)
docker exec api ls -la /appRun one command, e.g. check the files are where you expect
docker top apiThe processes running inside the container
docker stats --no-stream apiCPU, memory, network and disk use (without --no-stream it updates live)
docker diff apiFiles added (A), changed (C) or deleted (D) since the container started
docker cp api:/app/app.py .Copy a file out of a container (or in, with the arguments swapped)
docker logs --since 10m -f apiFollow logs from the last ten minutes
docker inspect apiEverything Docker knows: config, mounts, networks, state
docker eventsA live stream of what Docker is doing (start, die, health_status…)
docker exec api python -c "import flask; print(flask.__version__)"
docker exec api sh -c "echo debug > /tmp/notes.txt"
docker diff api

The first command confirms which Flask version is really inside the image; docker diff then lists /tmp/notes.txt as added. Remember that changes made by hand inside a container are lost when it is recreated — fix the Dockerfile, not the container.

Mini Challenge · Predict the Output

docker ps -a shows a container with status Exited (137). What most likely happened?

Choose one answer

Exit Codes Reference

Exit codeUsual meaning
0Finished successfully
1General application error — read the logs
125docker run itself failed (bad option, port in use…)
126The command exists but could not be executed (permissions)
127Command not found — typo or missing program in the image
137Killed with SIGKILL — docker kill or out of memory
143Stopped with SIGTERM — a normal docker stop

Common Mistakes

Avoid These Mistakes
  • Removing a crashed container before reading its logs.
  • Using --rm while debugging, so the container and its logs disappear immediately.
  • Fixing things by hand inside a container instead of in the Dockerfile.
  • Expecting tools like curl, ps or bash in slim and minimal images — use the language itself or a debugging container.

Frequently Asked Questions

Read docker logs first. To look around, start the image with a shell instead of its normal command: docker run -it --rm --entrypoint sh quotes-api.

Docker Desktop and recent Docker versions offer docker debug, and in general you can run a tools container that shares the target’s network or process namespace, e.g. docker run -it --rm --network container:api nicolaka/netshoot.

By default Docker keeps them as JSON files managed by the daemon. Lesson 14 shows how to limit their size so they cannot fill the disk.

Key Takeaways

  • Start with docker ps -a and docker logs — they explain most problems.
  • Exit codes point to the cause: 127 command not found, 137 killed, 1 app error.
  • Restart policies keep services running; health checks detect stuck apps.
  • exec, top, stats, diff and cp let you look inside running containers.
  • Fix problems in the Dockerfile and recreate the container.

Summary

You now have a dependable routine for fixing broken containers. Next you will learn to share images through registries such as Docker Hub.

Next Lesson →

Sharing Images: Docker Hub and Registries