Skip to content
Laviyx Learn

Try

Docker and Kubernetes course menu

How to Debug Docker Containers

Find out why a container exits or misbehaves: exit codes, logs, inspect, running a shell, overriding the entrypoint, debugging builds, and checking networks.

Intermediate7 min readUpdated
On this page

Containers fail for a few common reasons, and a fixed routine finds most of them.

Step 1: What state is it in?#

Terminal
docker run -d --name broken alpine:3.24 sh -c 'echo "Starting"; echo "Config missing" >&2; exit 3' > /dev/null
sleep 1
docker ps --format '{{.Names}}' | wc -l
docker ps -a --format '{{.Names}} {{.Status}}' | sed 's/ [0-9a-zA-Z ]* ago//'
Output
0
broken Exited (3)

It is not in docker ps, because it has exited. ps -a shows it, with the exit code.

Exit codeMeaning
0Finished normally; the program simply ended
1The app reported an error
125docker run itself failed
126The command could not be run
127Command not found
137Killed, often out of memory
143Stopped by SIGTERM

Step 2: Read the logs#

Logs work on stopped containers too:

Terminal
docker logs broken
docker rm broken > /dev/null
Output
Starting
Config missing

This is usually enough to find the cause.

Step 3: Inspect it#

docker inspect shows everything Docker knows. --format picks out the useful parts:

Terminal
docker run -d --name web -p 8080:80 -e MODE=test nginx:1.30-alpine > /dev/null
docker inspect --format '{{.State.Status}} since {{.State.Running}}' web
docker inspect --format '{{json .NetworkSettings.Ports}}' web
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' web | grep MODE
Output
running since true
{"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"},{"HostIp":"::","HostPort":"8080"}]}
MODE=test

Step 4: Look inside#

For a running container, open a shell with docker exec -it web sh. One-off checks work without one:

Terminal
docker exec web ls /etc/nginx/conf.d
docker exec web ps | head -n 3
docker rm -f web > /dev/null
Output
default.conf
PID   USER     TIME  COMMAND
    1 root      0:00 nginx: master process nginx -g daemon off;
   30 nginx     0:00 nginx: worker process

Debugging a container that exits at once#

You cannot exec into a stopped container. Instead, start the image with a different command, and look around:

Terminal
mkdir app
cd app
cat > Dockerfile <<'EOF'
FROM alpine:3.24
WORKDIR /app
COPY start.sh .
CMD ["./start.sh"]
EOF
echo 'echo hi' > start.sh
docker build -q -t bad-app . > /dev/null
docker run --rm bad-app
echo "Exit code: $?"
Output
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: "./start.sh": permission denied

Run 'docker run --help' for more information
Exit code: 126

Permission denied. Override the command to look inside:

Terminal
docker run --rm bad-app ls -l /app
Output
total 4
-rw-rw-r--    1 root     root             8 Nov  5 07:04 start.sh

The script is not executable. Fix it, and add a shebang:

Terminal
printf '#!/bin/sh\necho hi\n' > start.sh
chmod +x start.sh
docker build -q -t bad-app . > /dev/null
docker run --rm bad-app
Output
hi

If an image has an ENTRYPOINT, replace it with docker run --entrypoint sh -it image.

Debugging a failed build#

When a step fails, the error shows that step's output:

Terminal
cd ~
mkdir bad
cd bad
cat > Dockerfile <<'EOF'
FROM alpine:3.24
RUN apk add --no-cache this-package-does-not-exist
EOF
docker build --progress=plain -t bad . 2>&1 | grep -E 'ERROR: unable|no such package|did not complete' | head -n 3 | cut -c1-80
Output
#5 ERROR: process "/bin/sh -c apk add --no-cache this-package-does-not-exist" di

Run the same step by hand to explore: docker run -it --rm alpine:3.24 sh.

Debugging networks#

Most network problems come down to a name, a port, or a network:

Terminal
docker network create dbg > /dev/null
docker run -d --name api --network dbg nginx:1.30-alpine > /dev/null
sleep 2
docker run --rm --network dbg alpine:3.24 sh -c 'nslookup api > /dev/null 2>&1 && echo "Name works"; nc -z -w 2 api 80 && echo "Port open"; nc -z -w 2 api 81 || echo "Port 81 closed"'
docker rm -f api > /dev/null
docker network rm dbg > /dev/null
Output
Port open
Port 81 closed

Images are often very small and lack tools. Attach a tool container to another container's network with --network container:name. The image nicolaka/netshoot is popular for this, and it is full of network tools.

Watch events#

docker events shows what the daemon is doing live, which helps with restart loops:

Terminal
docker events --since 30s --until 0s --filter event=die --format '{{.Actor.Attributes.name}} died' | tail -n 2
Output
cranky_snyder died
wizardly_shirley died

A troubleshooting routine#

  1. docker ps -a: is it running? What is the exit code?
  2. docker logs name: what did it say?
  3. docker inspect name: are the settings right?
  4. docker exec -it name sh: what do things look like inside?
  5. docker run -it --rm image sh: try it by hand.
  6. docker stats: is it out of memory or CPU?

Common mistakes#

Practice#

Practice exercise

Run an Alpine container that exits with code 7, then print its exit code using docker inspect.

Show solution
Terminal
docker run --name exit7 alpine:3.24 sh -c 'exit 7'
docker inspect --format '{{.State.ExitCode}}' exit7
docker rm exit7 > /dev/null
Output
7

Key takeaway#

Next: Container Security.

Finished this lesson?

Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.