skip to content

A docker build fails and the console shows only the last few lines of the failing step — how do you see that step's full output?

level: juniorimportance: must knowfreq 66%

answer

  1. The default renderer redraws and discards
  2. The output was never lost, just collapsed
  3. One flag switches to append-only logs
  4. An env var makes it CI's default
  5. --progress=plain and BUILDKIT_PROGRESS

basics

~20 s

Re-run the build with --progress=plain. The default progress display collapses each step to its last few lines, while plain mode streams every line of every step, prefixed with the step number and elapsed seconds, and leaves it all on screen.

solid answer

~50 s

BuildKit's default `--progress=auto` renders a live, redrawing view in a terminal: finished steps collapse to one line and a running step shows only a short tail. Re-run with `docker build --progress=plain .` and the builder streams the complete stdout/stderr of every step as ordinary log lines, each prefixed `#<step> <seconds>` — that is where the compiler error or package-manager message actually is. Set `BUILDKIT_PROGRESS=plain` in the environment to make it the default, which is what you want permanently in CI, where the redrawing renderer produces unreadable logs anyway. Read to the bottom for the `ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully: exit code: N` line — it names the exact command and its exit status. One caveat: steps that hit the build cache print `CACHED` and replay no output, so if the interesting log is above the failure you have to invalidate that stage to see it again.

code

bash · 1 line
bash
docker build --progress=plain -t gateway:dev .

go deeper

for a junior

Memorise the one flag and the reason it exists: the default display redraws and discards, plain keeps every line. Be ready to say where the real error message appears and to read the final failed to solve line aloud.

for a middle

Explain that --progress only selects a renderer over the builder's status stream, so it changes output and nothing else, and that #n prefixes exist because independent steps run concurrently and interleave.

for a senior

Show operational habits: BUILDKIT_PROGRESS=plain set in CI so a failure is legible the first time, awareness that cached steps replay no log, and the fact that plain output puts anything a step echoes into a retained CI log.

for a principal

Own the convention: which progress mode CI uses, how long build logs are retained and who can read them, and whether build output is treated as a secrets-disclosure surface in your pipeline's threat model.

## Two renderers, one build When you run `docker build`, the builder (BuildKit, the default engine since Docker Engine 23.0) and the CLI are separate things: the builder executes steps and emits a status stream, and the CLI *renders* that stream. The `--progress` flag chooses the renderer, and it changes nothing about how the image is built. - `--progress=auto` (the default) picks the TTY renderer when stdout is a terminal. It draws a live, redrawing table: each step is one line with a spinner and a duration, a running step shows only a short tail of its output, and when the step finishes that tail is thrown away and the line collapses. - `--progress=tty` forces that renderer. - `--progress=plain` renders the same stream as plain, append-only log lines with no cursor tricks. Every line the step wrote to stdout or stderr is printed and stays printed. Recent buildx versions also accept `--progress=rawjson` for machine-readable output, which is useful when a tool, not a human, is reading the build. ## What plain output looks like Each line carries two prefixes: ``` #14 [build 4/6] RUN go build -o /out/gateway ./cmd/gateway #14 12.03 # command-line-arguments #14 12.03 ./cmd/gateway/main.go:41:9: undefined: newIngestPipeline #14 ERROR: process "/bin/sh -c go build -o /out/gateway ./cmd/gateway" did not complete successfully: exit code: 2 ``` `#14` is the step's number, matching the step header line, and `12.03` is seconds elapsed **since that step started** — not since the build started. Because BuildKit runs independent steps concurrently, the `#n` prefix is what lets you untangle interleaved output from two steps running at once; grepping for `^#14 ` gives you one step's log in isolation. Steps served from the build cache print a single `#7 CACHED` line and no body, because nothing was executed to produce output. At the very bottom, a failed build prints the summary error: `ERROR: failed to solve: process "/bin/sh -c <command>" did not complete successfully: exit code: 2`. Read it before theorising — it tells you the literal shell command BuildKit ran (shell-form `RUN` is wrapped in `/bin/sh -c`) and the exit status, which is often enough on its own. ## Making it the default The environment variable `BUILDKIT_PROGRESS=plain` sets the renderer without touching the command line. Export it in CI: the TTY renderer's escape sequences turn a CI log into a wall of control characters, and CI is exactly where you cannot re-run interactively to look again. Many teams set it globally in the pipeline environment and never think about it again. ## Things that trip people up **There is no `docker logs` for a build.** `docker logs` reads a *container's* log; a build step's container is created and destroyed inside the builder and is never a `docker ps` container, so its output exists only in the build's status stream you are looking at right now. If you close the terminal, it is gone — which is the practical reason to run with plain progress from the start on a build you expect to fail. **Re-running does not always reproduce the log.** Everything above the failing step is now cached and will print `CACHED`. That is usually what you want (the failure reproduces in seconds), but if the message you need came from an *earlier* step, you must invalidate it — `--no-cache-filter=<stage>` for one named stage, or `--no-cache` for the whole build if you are willing to pay for it. **Plain output is a disclosure surface.** It prints everything the step wrote, so a `RUN` that echoes a token, or a package manager that prints a URL with credentials in it, lands in your CI log and stays there for the log's retention period. That is an argument for keeping credentials out of the command line, not an argument against plain progress. **There is no `-v`.** `docker build` has no verbosity flag; `--progress` is the knob. Candidates who reach for `-v`, `--debug` or `docker logs` are signalling they have never actually had to read a failing build.

  • You re-run with --progress=plain and every step above the failure now prints CACHED with no output. How do you get those earlier logs back?
    A cached step executes nothing, so it has no output to replay. Force it to run again: `--no-cache-filter=<stage>` re-runs one named stage and leaves the rest of the cache alone, while `--no-cache` re-runs the whole build and costs you the full build time. Pick the narrowest one that covers the step whose log you need.
  • In plain progress output, what do the two prefixes on a line such as `#14 12.03 ...` mean?
    `#14` is the step number, matching the `#14 [build 4/6] RUN ...` header, and `12.03` is seconds elapsed since that step started, not since the build began. BuildKit runs independent steps concurrently and interleaves their lines, so the `#n` prefix is how you separate them; the elapsed figure also shows you cheaply which step is the slow one.
  • Why is `docker logs` not an option for a failed build step?
    `docker logs` reads the log of a container in the engine's container list. A build step runs in a short-lived container the builder creates and destroys itself; it never appears in `docker ps -a` and has no log to read afterwards. The build's status stream, rendered by `--progress`, is the only copy — which is why you capture it during the run.

saying these in an interview costs you the question

  • Says docker logs shows the build's step output
  • Reaches for a -v or --debug flag on docker build
  • Thinks the collapsed output is permanently lost
  • Always adds --no-cache just to see the log
  • Guesses at the cause from the collapsed tail
  • Confuses the elapsed seconds with total build time

context