How to inspect container state, logs, and metadata?
Three commands cover the entire observability story for a single container: docker ps for high-level state, docker logs for what the app printed, docker inspect for everything else. Plus docker stats for live resource use.
Theory
TL;DR
docker ps -a= current state, ports, image, name (one-line summary).docker logs <name>= container's stdout + stderr.-fto follow live,--tail Nfor last N lines,--since 10mfor time-bounded.docker inspect <name>= full JSON of the container's config, state, networks, volumes, mounts, env. Use--formatto extract one field.docker stats= live CPU, memory, network, block I/O.--no-streamfor a single snapshot.docker top <name>= process tree inside the container.docker port <name>= which host ports map to which container ports.
docker ps — quick state
$ docker ps -a --filter name=web
CONTAINER ID IMAGE STATUS PORTS NAMES
a3f9d2b8c1e4 nginx:1.27-alpine Up 5 minutes 0.0.0.0:8080->80/tcp webFour columns answer most questions: is it running? what image? since when? which ports?
docker logs — what the app said
The daemon captures stdout and stderr from PID 1 inside the container. docker logs reads it back.
docker logs web # all logs since start (can be huge)
docker logs -f web # follow live (like tail -f)
docker logs --tail 100 web # last 100 lines
docker logs --since 10m web # last 10 minutes
docker logs --since 2026-04-30T10:00:00 web # since timestamp
docker logs -t web # prepend timestampsImportant: logs come from PID 1's stdout/stderr. If your app writes to a file inside the container, docker logs will not show it. Configure your app to log to stdout for this to work — that is the 12-factor norm.
docker inspect — everything else
Full JSON dump of the container's state and config. Without --format, it is a 200-line JSON document. With --format, you get exactly one field:
# Status
docker inspect --format '{{.State.Status}}' web
# → running
# Exit code (after the container exits)
docker inspect --format '{{.State.ExitCode}}' web
# → 137
# Was it OOM-killed?
docker inspect --format '{{.State.OOMKilled}}' web
# → true / false
# Restart count
docker inspect --format '{{.RestartCount}}' web
# → 3
# IP address on the default bridge
docker inspect --format '{{.NetworkSettings.IPAddress}}' web
# → 172.17.0.2
# All published ports
docker inspect --format '{{json .NetworkSettings.Ports}}' web
# → {"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"}]}
# Mounts
docker inspect --format '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}\n{{end}}' webThe --format template uses Go's text/template syntax. The output of docker inspect <name> (without format) shows the field structure you can target.
docker stats — live resources
$ docker stats --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a3f9d2b8c1e4 web 0.01% 12.5MiB / 7.8GiB 0.16% 2.1kB / 832B 0B / 0B 5
b7e1f4d6a2b8 db 2.34% 145MiB / 7.8GiB 1.82% 18.4kB / 12.7kB 4.1MB / 0B 9Without --no-stream, it refreshes live like top. Useful for spotting memory leaks (steadily-rising MEM USAGE) or noisy neighbors.
docker top — process tree inside
$ docker top web
UID PID COMMAND
root 84231 nginx: master process nginx -g daemon off;
nginx 84290 nginx: worker processHost-side view: PIDs are host PIDs, not container's PID 1 numbering. Useful for matching ps aux | grep on the host with what is inside.
docker port — port mappings
$ docker port web
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080Quick way to see what is exposed without parsing inspect output.
Common mistakes
Looking in the writable layer for logs
# WRONG: app logs to /var/log/myapp.log inside the container
$ docker exec web cat /var/log/myapp.log
# RIGHT: configure the app to log to stdout, then
$ docker logs webApps that log to files inside the container do not show up in docker logs. They are also lost on docker rm. Always log to stdout/stderr for containerized apps.
Forgetting --no-stream in scripts
# Hangs forever in a script (waits for live updates)
$ docker stats > stats.txt
# Snapshot, exit, return
$ docker stats --no-stream > stats.txtdocker stats defaults to streaming. In a CI or scripted context, always add --no-stream.
Using docker inspect without --format in scripts
# Verbose, hard to parse
$ docker inspect web | grep IPAddress | head -1 | awk -F'"' '{print $4}'
# Clean
$ docker inspect --format '{{.NetworkSettings.IPAddress}}' webThe --format flag is exactly to skip the grep/awk dance.
Reading old logs without --tail
A long-running container can have gigabytes of logs. docker logs <name> (no flags) tries to dump them all. Use --tail 1000 or --since to bound the output.
Real-world usage
- "Is service X up?" →
docker ps --filter name=X --filter status=running -q. Empty output = not running. - "Why did the build container exit?" →
docker logs --tail 200 build-container. - "What IP is the database on?" →
docker inspect --format '{{.NetworkSettings.Networks.bridge.IPAddress}}' db. - "How much memory is web using right now?" →
docker stats --no-stream web. - "Was that crash an OOM?" →
docker inspect --format '{{.State.OOMKilled}}' container.
Follow-up questions
Q: How is docker logs different from docker logs -f?
A: Without -f, it dumps logs accumulated so far and exits. With -f (follow), it streams new logs as they appear, like tail -f. Press Ctrl+C to stop following.
Q: Can I see logs from before the container restarted?
A: Yes — docker logs retains logs across restarts (until docker rm). The default driver (json-file) appends to a log file on disk that survives restart. Other drivers (syslog, journald, fluentd) ship logs externally, in which case docker logs may not work — check docker inspect --format '{{.HostConfig.LogConfig.Type}}'.
Q: What is the difference between docker inspect and docker container inspect?
A: docker inspect is generic — works on containers, images, volumes, networks. docker container inspect is the explicit form for containers only. Identical output for containers.
Q: How do I see resource limits applied to a container?
A: docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}' <name>. Memory in bytes, CPUs in nanoCPUs (1 CPU = 1e9). Or docker stats shows usage vs limit live.
Q: (Senior) How do you debug a container that is restart-looping too fast to attach to?
A: A few options. (1) Check docker logs --tail 200 between restarts — even short bursts of output land. (2) Override the entrypoint with a sleep so it stays up: docker run --entrypoint sleep myimg 3600, then docker exec in. (3) Add --restart=no so it crashes once and stays exited; then docker logs and docker inspect give you a stable snapshot. (4) Run the broken image with the actual entrypoint command shifted into a debug shell wrapper.
Examples
Quick health-check loop
$ for c in web api db; do
s=$(docker inspect --format '{{.State.Status}}' "$c" 2>/dev/null || echo missing)
h=$(docker inspect --format '{{.State.Health.Status}}' "$c" 2>/dev/null || echo "-")
echo "$c: $s ($h)"
done
web: running (healthy)
api: running (healthy)
db: running (starting)State plus health status for several containers at once. Great for shell scripts.
Tail logs from multiple services in Compose
$ docker compose logs -f --tail 50
# Streams from all services, color-coded by service name.
$ docker compose logs -f --tail 50 api db
# Only api and db.Compose layers nice multi-service tailing on top of plain docker logs.
Check why the last exited container died
$ docker inspect --format '{{.State.Status}} ({{.State.ExitCode}}) OOM={{.State.OOMKilled}} Err={{.State.Error}}' \
$(docker ps -alq)
exited (137) OOM=true Err=
# → exited with 137 (SIGKILL); OOMKilled=true → memory limit hit.Four fields together usually pinpoint the cause without opening a JSON dump.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.
Comments
No comments yet