skip to content

questions

4

What does `docker inspect` return, and how do you pull a single field out of it with --format?

level: juniorimportance: must knowfreq 78%

answer

  1. The raw output is not an object
  2. One template runs per element
  3. Declared settings versus host-side settings
  4. Hyphenated keys need a helper function
  5. A null subtree breaks a naive path

basics

~20 s

docker inspect prints a JSON array holding the engine's full low-level record for each named object — State, Config, HostConfig, NetworkSettings, Mounts. --format (or -f) applies a Go template to each object, so docker inspect -f '{{.State.Status}}' web prints just that field.

solid answer

~50 s

`docker inspect` asks the daemon for its complete record of an object and prints it as a **JSON array**, one element per object you named. For a container the useful branches are `State` (live status, Pid, ExitCode, OOMKilled, StartedAt, Health), `Config` (what the image and `docker run` declared: Env, Cmd, Entrypoint, Labels), `HostConfig` (host-side settings such as Binds, PortBindings, RestartPolicy) and `NetworkSettings` (per-network IPs and published ports). `--format` runs a **Go template** against each element, which is how you script against it: `docker inspect -f '{{.State.Status}} {{.State.ExitCode}}' worker`. Use `{{json .State}}` to dump a whole subtree, `{{index .Config.Labels "com.example.team"}}` for keys with dots or hyphens, and `{{range .Mounts}}{{.Source}}{{end}}` to walk a list. Two traps: the raw output is an array so shell JSON tooling needs `.[0]`, and `{{.State.Health.Status}}` errors on a container that has no HEALTHCHECK because `State.Health` is null.

code

bash · 19 lines
bash
# Whole record (a JSON array, even for one container)
docker inspect worker

# One field at a time
docker inspect -f '{{.State.Status}} {{.State.ExitCode}}' worker
docker inspect -f '{{.State.Pid}}' worker

# A map key that is not a bare identifier needs index
docker inspect -f '{{index .Config.Labels "com.example.owner"}}' worker
docker inspect -f '{{(index .NetworkSettings.Networks "queue-net").IPAddress}}' worker

# Health, guarded for images with no HEALTHCHECK
docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' worker

# Walk the mounts
docker inspect -f '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' worker

# Dump a subtree as JSON for further processing
docker inspect -f '{{json .NetworkSettings.Ports}}' worker

go deeper

for a junior

Be ready to name the sections of the output — State, Config, HostConfig, NetworkSettings, Mounts — and to write one -f '{{.State.Status}}' style command live without hesitating.

for a middle

Explain that the CLI is printing the daemon API's response verbatim as an array, and demonstrate index for hyphenated keys, range over Mounts and {{json .}} for a subtree.

for a senior

Show the scripting discipline: format strings rather than column parsing, guarding a null Health branch, and knowing --size costs a filesystem walk so it stays out of hot loops.

for a principal

Own the convention: agree one supported way for tooling across the fleet to read container facts, so scripts do not each invent a fragile docker ps parser that breaks on the next CLI release.

## What the command actually is `docker inspect` is a thin printer over the engine's own record of an object. The CLI calls the daemon's REST API (`GET /containers/<id>/json` for a container, and the equivalent endpoints for images, networks and volumes) and prints the JSON it gets back. Nothing is computed or prettified: what you see is the daemon's view of that object at that instant. That is why it is the first command to reach for when you want to know what a container *really* got, rather than what you think you asked for. The output is always a **JSON array**, even for one object, because you can pass several names or IDs at once. This trips people up constantly in scripts: `docker inspect web | jq '.State.Status'` returns `null`, while `docker inspect web | jq '.[0].State.Status'` works. Using `--format` avoids the problem entirely, because the template runs once per element with the dot bound to that element. ## The branches worth knowing For a container the record has a handful of top-level sections, and knowing which one owns which fact is most of the skill: - **`State`** — the live bits: `Status` (`created`, `running`, `paused`, `restarting`, `exited`, `dead`), `Running`, `Pid` (the host PID of the container's PID 1, `0` once it has exited), `ExitCode`, `OOMKilled`, `StartedAt`/`FinishedAt`, `Error`, and `Health` when the image or the run declared a HEALTHCHECK. - **`Config`** — what was *declared*: `Image`, `Env`, `Cmd`, `Entrypoint`, `User`, `WorkingDir`, `Labels`, `ExposedPorts`. This is the image's metadata merged with what `docker run` overrode. - **`HostConfig`** — the host-side knobs the run asked for: `Binds`, `Mounts`, `PortBindings`, `RestartPolicy`, `NetworkMode`, `LogConfig`, `CapAdd`/`CapDrop`, and the resource fields. - **`NetworkSettings`** — the runtime result: the per-network map under `Networks` (each with `IPAddress`, `Gateway`, `MacAddress`, `Aliases`) and the `Ports` map of container port to host bindings. - **`Mounts`** — a normalised list of everything mounted in, whichever syntax created it, each with `Type`, `Name`, `Source`, `Destination`, `RW`. The distinction between `Config` and `HostConfig` is a classic interview probe: `Config` is portable and comes largely from the image, `HostConfig` is machine-specific and comes from the run. ## Go templates in practice `--format`/`-f` takes a Go text template. Field access is a dotted path, so `-f '{{.State.Pid}}'`. A few built-ins carry almost all real usage: - `json` — `-f '{{json .NetworkSettings.Ports}}'` dumps a subtree as JSON, which you then pipe into a JSON tool. - `index` — the only way to read a map key that is not a bare identifier: `-f '{{index .Config.Labels "com.example.owner"}}'`, or `-f '{{index .NetworkSettings.Networks "my-net" }}'` for a user-defined bridge whose name contains a hyphen. `{{.NetworkSettings.Networks.my-net.IPAddress}}` is a template parse error, and this is the single most common `--format` failure. - `range` — iterate a list or map: `-f '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'`. - `printf` — formatting when you want more than concatenation. Useful flags alongside it: `--type container|image|network|volume|node` disambiguates when a container and an image share a name, and `--size` (`-s`) adds `SizeRw` (bytes written into the writable layer) and `SizeRootFs`, which are omitted by default because computing them costs a filesystem walk. ## Traps `{{.State.Health.Status}}` on a container with no HEALTHCHECK does not print empty — the template fails at runtime because `State.Health` is null, so a script that assumed a string gets a non-zero exit and an error on stderr. Guard it: `{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}`. The schema differs per object type. An image's record has no `State` and no `HostConfig`; a network's has `Containers` and `IPAM`. Templates are therefore not portable across `--type` values. Finally, `docker inspect` on a missing object prints an error and exits non-zero, which is exactly what you want in a script — `docker inspect -f '{{.State.Running}}' myapp >/dev/null 2>&1` is a serviceable existence check. ## Scripting against it Because the record is the API's own response, `--format` is the supported way to build tooling on top of the CLI. A wait loop is the canonical example: poll `docker inspect -f '{{.State.Health.Status}}' db` until it reports `healthy` before starting dependent work, rather than sleeping a fixed number of seconds and hoping. A cleanup script reads `{{.State.Status}}` to decide whether a container is worth stopping. A support bundle dumps `{{json .HostConfig}}` and `{{json .NetworkSettings}}` so the answer to "what was it actually run with?" survives the container. Each of these is a single field pulled from a structured source; each would be a fragile column-slicing exercise if it were built on human-readable output instead. It is also worth knowing that `docker inspect` reads state and never changes it. It is safe to run against a production container in any state — running, paused or exited — which makes it the correct first command in almost every container investigation, before anything that stops, restarts or removes. ## Why interviewers ask Because the alternative is parsing `docker ps` output with `awk`, which breaks the moment a column widens. Reaching for `--format` shows you know the CLI is a client over a structured API, and knowing which branch holds which fact shows you have actually debugged a container rather than restarted it.

  • Why does `docker inspect web | jq '.State.Status'` return null?
    Because `docker inspect` prints a JSON **array**, one element per object named, so the top-level value is a list rather than the container object. The path has to start at the first element: `jq -r '.[0].State.Status'`. Using `--format '{{.State.Status}}'` sidesteps it, since the template is evaluated once per element with the dot already bound to that element.
  • What is the difference between the `Config` and `HostConfig` sections of a container's inspect output?
    `Config` is the declared, largely image-derived identity of the process: Image, Env, Cmd, Entrypoint, User, WorkingDir, Labels, ExposedPorts. `HostConfig` is the host-specific runtime request made by `docker run`: Binds, PortBindings, NetworkMode, RestartPolicy, LogConfig, capabilities and resource fields. Move a container definition to another machine and `Config` travels with the image while `HostConfig` is re-specified per host.
  • Why does `docker inspect` omit the container's writable-layer size by default?
    Computing `SizeRw` and `SizeRootFs` requires walking the container's filesystem rather than reading engine metadata, so it is slow on a container that has written a lot. The daemon leaves those fields out unless you ask with `--size`/`-s`, keeping the default call a cheap metadata read that is safe to run in a loop.

docker ps is the label on the tin; docker inspect is the full manufacturing record, and --format is asking the clerk for one line of it instead of photocopying the whole file.

saying these in an interview costs you the question

  • Thinks the output is a single JSON object, not an array
  • Parses `docker ps` columns with awk instead of using --format
  • Writes .Networks.my-net.IPAddress and blames a Docker bug
  • Cannot say which branch holds the exit code
  • Assumes .State.Health always exists
  • Confuses Config with HostConfig

context

open as a page

How do you read the columns of `docker stats`, and when do you need --no-stream?

level: middleimportance: should knowfreq 61%

basics

~20 s

docker stats streams a live per-container table of CPU %, MEM USAGE / LIMIT, MEM %, NET I/O, BLOCK I/O and PIDS, refreshing until interrupted. --no-stream prints one sample and exits, which is what any script or CI step needs so the command terminates.

open as a page

How do you use `docker events --filter` to catch containers that die overnight?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Run docker events filtered to the actions you care about — --filter event=die --filter event=oom --filter event=health_status — and capture it to a file, because the stream is live and the daemon's replay buffer is small. Each die event carries the container's exitCode as an attribute.

open as a page

What do `docker top` and `docker diff` show for a container built with no shell?

level: middleimportance: nice to knowfreq 29%

basics

~20 s

docker top runs the host's ps against the container's processes, and docker diff lists files added, changed or deleted in the container's writable layer since the image. Both run entirely on the host, so they work on a distroless image that contains no shell and no tools.

open as a page