What does `docker inspect` return, and how do you pull a single field out of it with --format?
answer
- The raw output is not an object
- One template runs per element
- Declared settings versus host-side settings
- Hyphenated keys need a helper function
- A null subtree breaks a naive path
basics
~20 sdocker 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# 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}}' workergo deeper
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.
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.
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.
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