Emmanuel Corels
← Blog
Containers & Kubernetes

Inspect a running Docker container without turning debugging into the fix

Identify the exact replica, preserve logs and inspect data, use docker exec instead of attach, handle shell-less images, and carry the repair back into the image.

By Emmanuel Corels

A container is serving errors and you need to inspect its files. Installing SSH inside it creates another daemon, another credential path and a misleading mental model. Docker already provides a controlled way to start a diagnostic process in a running container: docker exec. Use it to gather evidence, not to make an undocumented production repair.

Identify the exact container and preserve its context

docker ps --no-trunc   --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}'

docker inspect --type container app-web-1   --format 'image={{.Config.Image}} pid={{.State.Pid}} status={{.State.Status}} started={{.State.StartedAt}}'

In a scaled service, a name copied from another host may point to the wrong replica. Record the host, container ID, image digest, start time and incident window. Save logs before a restart rotates or destroys the evidence:

mkdir -p incident-2026-07-27
docker logs --timestamps --since 30m app-web-1   >incident-2026-07-27/container.log 2>&1
docker inspect app-web-1 >incident-2026-07-27/inspect.json

Use exec, not attach, for a new shell process

docker exec -it app-web-1 sh

docker exec starts a new executable while the container’s primary process is running. -i keeps standard input open; -t allocates a pseudo-terminal. By contrast, docker attach connects your terminal to the existing PID 1 streams. Attaching to a web server does not create a shell and careless keystrokes can send signals or input to the application.

Prefer targeted, non-interactive evidence commands

docker exec app-web-1 ps
docker exec app-web-1 cat /etc/os-release
docker exec app-web-1 sh -c 'id; pwd; ls -la /app; df -h /app'
docker exec app-web-1 sh -c 'ss -lntp 2>/dev/null || netstat -lntp 2>/dev/null'

Targeted commands are reproducible and easy to record. Docker requires the command to be an executable: shell operators such as &&, pipes and redirections need an explicit shell, for example sh -c 'first && second'. Quoting the whole chain as the executable will fail.

Match the application user before assuming root

docker inspect app-web-1 --format 'configured-user={{json .Config.User}}'
docker exec --user 10001:10001 --workdir /app app-web-1 id
docker exec --user 10001:10001 --workdir /app app-web-1 ./healthcheck

Running diagnostics as root can hide the permission failure experienced by the service. Start as the configured runtime identity. Elevate only for a named read-only check and record it. Avoid --privileged: Docker documents that privileged containers receive broad host-facing capabilities and are not securely sandboxed.

Do not expect Bash—or any shell—to exist

docker exec -it app-web-1 /bin/bash
# If Bash is absent:
docker exec -it app-web-1 /bin/sh
# If every shell is absent, run a known application binary:
docker exec app-web-1 /app/server --version

Distroless, scratch and hardened images may intentionally contain no shell, package manager, ps or network utilities. “Executable file not found” does not mean the container is unreachable. It means the requested tool is absent from that filesystem.

Use Docker Debug for a shell-less image where available

docker debug app-web-1

Docker documents docker debug as an alternative that can provide a toolbox shell even when the target is slim or shell-less. Availability and licensing can vary by installation, so confirm it in your environment and audit its use. The toolbox is for observation; do not confuse tools visible in the debug environment with files shipped in the application image.

Copy evidence out without editing the container

docker cp app-web-1:/app/config/runtime.json   incident-2026-07-27/runtime.json
docker diff app-web-1 >incident-2026-07-27/filesystem-diff.txt
sha256sum incident-2026-07-27/runtime.json

Inspect copied material according to its sensitivity—runtime files may contain credentials or customer data. docker diff shows filesystem changes relative to the image, which can reveal an unexpected write path, but it does not explain volumes and bind mounts by itself.

Inspect mounts before blaming the image

docker inspect app-web-1 --format '{{range .Mounts}}{{println .Type .Source "->" .Destination "rw=" .RW}}{{end}}'
docker exec app-web-1 sh -c 'mount 2>/dev/null; df -h 2>/dev/null'

A file visible at /app/config may come from a bind mount, named volume, secret or configuration object. Editing it inside the container might mutate shared external state. Resolve the mount source and ownership before touching anything.

Understand environment inspection limits

docker inspect app-web-1 --format '{{range .Config.Env}}{{println .}}{{end}}'
docker exec app-web-1 env

Both commands can expose secrets to the terminal, shell history or incident bundle. Prefer listing variable names or querying a specific non-secret value. Docker notes that environment supplied to an exec process applies only to that process; it does not reconfigure PID 1.

Know why exec sometimes fails

SymptomMeaningNext check
Container is not runningExec requires a live PID 1Inspect state, exit code, OOM flag and logs
Container is pausedDocker rejects exec into paused stateConfirm why it was paused before unpausing
Executable not foundTool or shell is absent, or PATH differsUse an absolute path or approved debug toolbox
Permission deniedRuntime user or filesystem policy blocks itReproduce as configured UID; inspect modes and LSM events
Exec dies on restartIt exists only while the original PID 1 runsCapture evidence outside the container

Make the actual repair in source and rebuild

git switch -c fix/container-config-validation
docker build --pull --tag app:test .
docker run --rm app:test ./healthcheck
docker image inspect app:test --format '{{.Id}}'

A package installed or file edited through exec disappears when the container is replaced and cannot be reviewed from the Dockerfile. Reproduce the fault in a controlled environment, change source or image construction, test the health boundary, publish an immutable image, and roll it out through the normal deployment path.

Verify replacement and preserve the handover

docker ps --filter name=app-web   --format '{{.ID}} {{.Image}} {{.Status}}'
curl --fail --show-error --max-time 5 https://service.example.net/health
docker logs --timestamps --since 5m app-web-1

Confirm the user-facing service, not merely the presence of a shell or a green container status. Record the old and new image digests, evidence collected, commands run, change reviewed and rollback decision.

A shell inside a container is an observation point, not a configuration-management system. Diagnose the running instance, then make the durable repair in the image and deployment definition.

Sources: the solved Stack Overflow question (question by Andrew; accepted answer by larsks, CC BY-SA), Docker’s official container exec, container attach, and Docker Debug documentation.

Primary source: Review the official reference ↗