docker-debugging
Skill0 starsChanged 2 months agoApache-2.0View on GitHub
Debug containers that fail to start, exit immediately, cannot reach the network, or behave differently from the host - exit codes, entrypoint and shell-form problems, missing files after a build, volume and permission errors, port publishing, DNS inside containers, and architecture mismatches. Use when a container will not run, runs differently than expected, or works locally and fails elsewhere.
In this repository26 other files it gives its agents.
- AGENTS.mdAGENTS.md
- aws-production-debuggingSkill
- github-actions-debuggingSkill
- postgresql-troubleshootingSkill
- container-image-reviewSkill
- incident-triageSkill
- postmortem-analysisSkill
- production-debuggingSkill
- kubernetes-networkingSkill
- kubernetes-production-readinessSkill
- kubernetes-resource-debuggingSkill
- kubernetes-troubleshootingSkill
- linux-network-debuggingSkill
- linux-performanceSkill
- linux-troubleshootingSkill
- dns-troubleshootingSkill
- http-debuggingSkill
- tls-debuggingSkill
- observability-debuggingSkill
- prometheus-troubleshootingSkill
- slo-designSkill
- secrets-management-reviewSkill
- technology-jobSkill
- terraform-reviewSkill
- terraform-safe-changesSkill
- terraform-troubleshootingSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 108
Prefer exec form. When shell features are genuinely needed, exec the real process so it takes over PID 1.
- Distinctive: line 292
Better: Read the exit code and the logs first.
- Distinctive: line 299
Bad: Run as root to fix a permission error.
- Distinctive: line 303
Bad: Use shell-form ENTRYPOINT for convenience.
- Very specific: line 307
Bad: Add EXPOSE and expect the port to be reachable.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Docker Debugging
Find out what the container actually ran, in what environment, and why it stopped.
Purpose
Use this when:
- A container exits immediately, or restarts repeatedly.
- A file that exists in the build is missing at runtime.
- The container cannot reach the network, or cannot be reached.
- Permission denied on a mounted volume.
- It works on one machine and not another.
Do not use this when:
- The container runs under Kubernetes and the symptom is scheduling or probes — use
kubernetes-troubleshooting. - The question is image quality, size or security — use
container-image-review.
What this skill assumes: Docker or a compatible runtime on the host. Commands are shown for
docker; podman and nerdctl accept the same arguments for everything used here.
Operating Procedure
- Get the exit code and the reason.
docker ps -aanddocker inspectgive both. - Read the logs, including from the exited container. A stopped container keeps its logs until it is removed.
- Establish what actually ran. The effective entrypoint and command are frequently not what the Dockerfile appears to say.
- Compare build-time and run-time. Most "missing file" reports are a multi-stage build that did not copy it, or a volume mounted over it.
- Check the boundaries: network, mounts, users, and architecture.
- Reproduce interactively with an overridden entrypoint — the fastest way to inspect a broken image.
- Fix in the Dockerfile or the run configuration, then rebuild and verify.
What docker inspect reports is Observed. What the Dockerfile says is what was intended —
they differ whenever a base image, a compose file, or a runtime flag overrides something.
Investigation
Step 1 - status and exit code
docker ps -a
docker inspect <container> --format '{{.State.Status}} {{.State.ExitCode}} {{.State.Error}} {{.State.OOMKilled}}'
docker logs <container> --tail=100
docker logs <container> --timestamps 2>&1 | tail -50Exit codes carry specific meaning:
| Exit | Meaning |
| 0 | Clean exit. For a long-running service this is a bug: the process is not staying up. |
| 1 / 2 | Application error. Read the logs. |
| 125 | The Docker daemon itself failed — a bad flag or an invalid configuration. |
| 126 | The command was found but is not executable. Check the file mode and the shebang. |
| 127 | Command not found. The binary, or a shared library it needs, is absent from the image. |
| 137 | SIGKILL. With OOMKilled: true, the memory limit. Otherwise a failed graceful stop. |
| 139 | SIGSEGV. |
| 143 | SIGTERM — something asked it to stop. |
Exit 127 in a container that built successfully is almost always one of two things: a binary
compiled against glibc running on Alpine (musl), or a multi-stage build whose final stage lacks a
runtime dependency. Both are covered in container-image-review.
Step 2 - what actually ran
docker inspect <image> --format 'ENTRYPOINT={{.Config.Entrypoint}} CMD={{.Config.Cmd}} USER={{.Config.User}} WORKDIR={{.Config.WorkingDir}}'
docker history <image> --no-trunc | head -20The effective command is ENTRYPOINT with CMD appended as arguments. Three rules explain most
surprises:
- Specifying a command on
docker runreplacesCMD, notENTRYPOINT. - Exec form (
ENTRYPOINT ["app", "--flag"]) runs the binary directly as PID 1. - Shell form (
ENTRYPOINT app --flag) runs it under/bin/sh -c, which means the shell is PID 1 and signals do not reach your process. The container then ignores SIGTERM and gets SIGKILLed after the grace period, which shows up as slow, ungraceful shutdowns and dropped connections.
Prefer exec form. When shell features are genuinely needed, exec the real process so it takes
over PID 1.
Step 3 - inspect a broken image interactively
docker run --rm -it --entrypoint sh <image>
docker run --rm -it --entrypoint sh <image> -c 'ls -la /app; id; env | sort'
docker create --name tmp <image> && docker cp tmp:/app ./app-contents && docker rm tmpOverriding the entrypoint is the fastest way into an image whose startup fails. If the image has no
shell — distroless, scratch — use docker cp to extract the filesystem and inspect it on the
host instead.
For a running container:
docker exec -it <container> sh
docker diff <container>docker diff shows every file added, changed or deleted relative to the image. It is an
underused command: it answers "did the application write where I expected?" directly, and often
reveals a process writing to a path that a volume was supposed to cover.
Step 4 - volumes and permissions
docker inspect <container> --format '{{json .Mounts}}' | jq
docker exec <container> id
ls -ln <host-path>Two distinct problems that produce the same error message:
- A mount covers the image's content. Mounting a host directory at
/apphides everything the image put there. If the host directory is empty,/appis empty at runtime. A bind mount always wins over image content. - UID mismatch. The container's user has a UID that does not match the host file ownership.
Containers see numeric UIDs, not names —
appuserinside the container andappuseron the host are unrelated unless the numbers match. Comparedocker exec <container> idwithls -ln(numeric) on the host.
A named volume behaves differently from a bind mount on first use: Docker copies the image's content into a new, empty named volume. It does not do this for bind mounts, and it does not do it again once the volume has content — which is why a volume can keep serving old files long after the image changed.
Step 5 - networking
docker inspect <container> --format '{{json .NetworkSettings.Networks}}' | jq
docker port <container>
docker exec <container> sh -c 'command -v ss >/dev/null && ss -lntp || netstat -lntp'
docker exec <container> cat /etc/resolv.conf
docker exec <container> getent hosts <other-service>The usual causes, in order:
| Symptom | Cause |
| Published port refuses connections | The process binds 127.0.0.1 inside the container; nothing outside the namespace can reach it. Bind 0.0.0.0. |
EXPOSE present but port unreachable | EXPOSE is documentation. Publishing requires -p. |
| One container cannot reach another by name | They are on different networks, or on the default bridge, which has no DNS-based service discovery. Use a user-defined network. |
| Cannot reach the host from a container | host.docker.internal on Desktop; on Linux, the gateway address or --add-host. |
| DNS fails inside, works on the host | The daemon's DNS configuration, or a VPN changing the host resolver after the container started. |
Step 6 - architecture
docker image inspect <image> --format '{{.Architecture}}/{{.Os}}'
uname -m
docker run --rm <image> uname -mAn arm64 image on an amd64 host — or the reverse — fails with exec format error, or runs
under emulation at a fraction of the speed. This has become common with Apple Silicon laptops
building images for x86 servers. Build multi-architecture images with docker buildx, or set
--platform explicitly and consistently.
Decision Tree
Container problem
├── Exits immediately with 0 → the process is not long-running; check ENTRYPOINT/CMD
├── Exit 127 → command not found
│ ├── glibc binary on Alpine → musl mismatch
│ └── Multi-stage build did not copy a dependency
├── Exit 126 → found but not executable → file mode or shebang
├── Exit 125 → the daemon rejected the run → bad flag
├── Exit 137 + OOMKilled → memory limit
├── Exit 143 → asked to stop; if unexpected, check the orchestrator
├── exec format error → architecture mismatch
├── File missing at runtime
│ ├── docker diff / exec shows it absent → build did not produce it
│ └── Present in image, absent at runtime → a mount covers it
├── Permission denied on a mount → compare numeric UIDs
├── Cannot connect to a published port
│ ├── Bound to 127.0.0.1 inside → bind 0.0.0.0
│ └── Not published → -p
└── Container cannot reach another → same user-defined network?Common Causes
| Cause | Signal | Fix | Risk |
| Shell-form ENTRYPOINT | Signals ignored, slow stops | Exec form, or exec "$@" | mutating |
| Bind mount hides image content | Directory empty at runtime | Mount elsewhere, or remove the mount | mutating |
| UID mismatch | Permission denied on a volume | Align numeric UIDs, or fix ownership | mutating |
| Bound to localhost | Published port refused | Bind 0.0.0.0 | mutating |
| Default bridge network | Name resolution between containers fails | User-defined network | mutating |
| Architecture mismatch | exec format error | buildx multi-arch, or matching --platform | mutating |
| Stale named volume | Old files after an image update | Recreate the volume deliberately | destructive |
| Missing CA certificates | Every HTTPS call fails inside | Install ca-certificates | mutating |
Safety
Read-only
docker ps, logs, inspect, history, diff, port, top, and image inspect. These read
daemon state and change nothing.
docker exec is read-only only if the command you run is. A shell in a production container can
change files, and on most setups it is an audited action.
Potentially disruptive
docker restart/stop/kill— ends in-flight work.stopsends SIGTERM and then SIGKILL after the timeout (10s by default), which is frequently too short for a graceful shutdown; use-tto extend it.docker rm <container>— removes the container and its logs. Capturedocker logsbefore removing anything you might need to explain.docker network disconnect— severs connectivity for a running container immediately.- Changing a published port or a mount — requires recreating the container, not restarting it.
- Rebuilding with
--no-cache— slow, and pulls fresh base images, which can introduce changes you did not intend to adopt during an incident.
Destructive
docker volume rm <volume>— deletes the data in that volume permanently. Databases, uploads, and anything else that survives a container restart lives there. Required first: confirm what uses it (docker volume inspect,docker ps -a --filter volume=<name>), confirm a backup exists, and get explicit confirmation.docker system prune -a --volumes— removes all stopped containers, unused networks, all unused images, the build cache, and with--volumesevery volume not attached to a running container. A stopped database container's volume qualifies as unused. This is the single most common way local and staging data is destroyed. Required first: run the non-destructive listing (docker volume ls,docker ps -a) and confirm what would be removed.docker rm -fon a container with an anonymous volume — the container goes, and-vwould take the volume with it. Know which flags you are passing.
On a Kubernetes node, do not prune manually at all: the kubelet manages image garbage collection, and removing images by hand can leave workloads unable to restart.
Verification
Before: container exits with 127 immediately; no logs
After: container Up 10 minutes; /healthz returns 200; SIGTERM stops it in 2sdocker ps --filter name=<name> --format '{{.Status}}'
docker inspect <container> --format '{{.State.Status}} {{.State.ExitCode}} {{.RestartCount}}'
docker exec <container> wget -qO- http://localhost:8080/healthz
time docker stop <container>Three checks that matter beyond "it started":
- It stays up. A restart count that keeps climbing means it is still failing, just more slowly.
- It serves traffic, from outside the container as well as inside.
- It stops gracefully.
time docker stopshould finish well inside the timeout. If it takes the full 10 seconds, the process is being SIGKILLed — signals are not reaching it, and in production that means dropped connections on every deploy.
Anti-Patterns
Bad: The container exits, so rebuild with --no-cache.
Better: Read the exit code and the logs first. A cache rebuild takes minutes and fixes almost
none of the failures listed above.
Bad: docker system prune -a --volumes to free disk space.
Better: That deletes volumes belonging to stopped containers. Prune images specifically, and
check what will be removed before running anything with --volumes.
Bad: Run as root to fix a permission error. Better: It works, and it removes a real security boundary. Align the UIDs or fix the ownership of the host directory.
Bad: Use shell-form ENTRYPOINT for convenience. Better: The shell becomes PID 1, signals do not reach the application, and every stop takes the full timeout and kills in-flight requests.
Bad: Add EXPOSE and expect the port to be reachable.
Better: EXPOSE is metadata. Publishing needs -p or the orchestrator's equivalent.
Bad: Debug by adding RUN echo lines and rebuilding.
Better: docker run --entrypoint sh gets you inside the exact image in seconds, without a
rebuild cycle.
Composition
container-image-review— image size, base image choice, non-root users, build correctness.kubernetes-troubleshooting— the same containers under an orchestrator.linux-troubleshooting— host-level disk, memory and process problems beneath the runtime.dns-troubleshooting— name resolution inside containers.
References
- Docker: ENTRYPOINT and CMD interaction
- Docker: Networking overview — bridge versus user-defined networks and their DNS behaviour.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

