skip to content

questions

16

What does `docker buildx build --push` do that `docker build` followed by `docker push` does not?

level: juniorimportance: must knowfreq 72%

answer

  1. Ask where the finished image landed
  2. Buildx builders have their own storage
  3. The flag is an exporter shorthand
  4. type=registry versus type=docker
  5. Log in before the build, not after

basics

~20 s

--push is shorthand for --output type=registry: buildx streams the finished layers from the builder straight to the registry. On a docker-container builder the image never enters the local image store, so a separate docker push would find nothing to push.

solid answer

~50 s

In buildx, where a build result lands is an explicit choice called an exporter, selected with `--output`. `--push` is shorthand for `--output type=registry` and `--load` is shorthand for `--output type=docker`. That matters because a CI job normally builds on a builder created with the `docker-container` driver, which is a separate BuildKit container with its own content store — nothing is written into the engine's image store automatically. Build with no output flag and buildx warns that the result will only remain in the build cache; `docker images` shows nothing and a follow-up `docker push` fails because no local image carries that tag. `--push` also means the upload happens at the end of the same command, so the runner must already be authenticated to the registry non-interactively before the build starts, or you burn the whole build and fail at the last step.

code

bash · 3 lines
bash
echo "$REGISTRY_TOKEN" | docker login registry.example.com -u ci-bot --password-stdin
docker buildx create --name ci --driver docker-container --use --bootstrap
docker buildx build --push -t registry.example.com/geo/tile-api:build-4712 .

go deeper

for a junior

Recall that --push is an output/exporter shorthand, not a second command, and that a buildx build can succeed while leaving nothing in docker images. Knowing --load as its counterpart is the other half of the answer.

for a middle

Explain the mechanism: exporters chosen with --output, type=registry versus type=docker, and why a builder that is a separate BuildKit container has no reason to touch the engine's image store. Be able to read the no-output warning correctly.

for a senior

Show the operational consequence — the upload runs at the end of a long build, so credentials must be in place before it starts, and unchanged blobs are skipped by digest. Talk about not materialising layers twice on a runner's disk.

for a principal

Own the policy: which jobs are allowed to publish at all, whether builds and publishes are the same command or deliberately separated so an artifact can be tested before it becomes visible, and what that choice costs in runner time and in reproducibility.

### Where a build result actually goes Classic `docker build` hands the build to the Docker daemon and, on success, writes the image into the daemon's **local image store** under whatever tags you passed with `-t`. From there `docker images` lists it, `docker run` can start it, and `docker push` is a second, separate command that reads that stored image and uploads it. Two steps, two commands, one place in the middle where the image sits. Buildx keeps the command shape but changes the model underneath. A buildx build runs on a named **builder instance**, and the destination of the result is an explicit choice called an **exporter**, selected with `--output`. `--push` is shorthand for `--output type=registry`. `--load` is shorthand for `--output type=docker`, which imports the result into the local image store. Other exporters write a directory of files (`type=local`), a tarball (`type=tar`), an OCI layout (`type=oci`), or nothing but cache (`type=cacheonly`). ### Why the difference bites specifically in CI The capability you get for free depends on the builder's **driver**. The default builder uses the `docker` driver — BuildKit embedded in the daemon — and it loads results into the local image store automatically, which is why `docker build` on a laptop feels like it always did. A CI job normally creates its own builder with the `docker-container` driver, because that driver can do things the embedded one cannot. That builder is a separate BuildKit container with its own content store, and it does **not** write into the engine's image store unless you ask. So on a CI runner, a plain `docker buildx build -t app:1 .` with no output flag produces a warning that no output was specified and that the build result will only remain in the build cache. `docker images` shows nothing. The next line of the job, `docker push app:1`, then fails saying no local image exists with that tag — and the failure is confusing precisely because the build printed a successful, fully green log a second earlier. ### The push is part of the build command With `--push`, BuildKit uploads each layer as the build finishes it, straight from the builder to the registry, and then writes the manifest under every tag you passed with `-t` (you can repeat `-t`). Practical consequences: - **No double storage on the runner.** Layers are streamed out instead of being materialised into the local image store and then read back out again for an upload. - **Blobs the registry already has are skipped.** The push negotiates by digest, so an unchanged base or unchanged dependency layer costs a HEAD request rather than an upload. - **It is the only exporter that can publish a manifest list.** A result built for more than one platform cannot be represented in the local image store the way a single image can, so the registry is the destination. - **Authentication must already be in place.** The upload happens at the *end* of a build that may run for minutes. Buildx takes the credentials from the CLI's config file and passes them to the builder over the build session, so the job has to authenticate non-interactively first — a runner has no TTY, so an interactive password prompt would simply hang the job until it times out. Log in as an early step, not as a step before the push, because there is no separate push step to precede. ### A worked example A geospatial tile server ships as a Node.js API on an alpine base. Its CI job logs in, creates a `docker-container` builder, and runs one build command with `--push -t registry.example.com/geo/tile-api:build-4712`. The 6 m 41 s build ends with an upload of the four layers that changed; the other layers are already in the registry and are skipped. Nothing is left in the runner's image store, which is fine, because the runner is discarded moments later. If that same job needs the image locally as well — say a smoke test runs it before publishing — the honest shape is to `--load` it, test it, and push afterwards, or to run the test as part of the build. What does not work is assuming that a buildx build has quietly populated `docker images` for you. ### The short version `docker build` + `docker push` is build-into-a-store, then upload-from-the-store. `docker buildx build --push` is build-and-upload as one operation, with no local copy in between, on a builder that would not have made a local copy anyway.

  • What would you use instead of `--push` if the job must run the image before publishing it?
    `--load`, which is shorthand for `--output type=docker` and imports the result into the runner's local image store so `docker run` can start it. It costs an extra materialisation of the layers on the runner's disk, and it cannot import a multi-platform result, so a job that both tests and publishes typically loads a single-platform image for the smoke test and pushes in a separate build, or runs the test inside the build itself.
  • The job builds for six minutes and then fails on the push with an authorization error. What went wrong and how do you avoid it?
    The runner was not authenticated to the target registry when the exporter ran. Because `--push` uploads at the end of the same command, missing credentials cost the entire build before surfacing. Fix it by making the non-interactive login one of the job's first steps, and by failing fast — a cheap credential check early is worth six minutes of runner time on every broken build.
  • Can you push several tags from one buildx build?
    Yes — repeat `-t`. All tags reference the same manifest and the same layer blobs, so the extra tags cost only additional manifest writes, not another upload of the content. That is why publishing an immutable build-specific tag alongside a moving one is essentially free at push time.

Classic build is printing a document to your desk and mailing it later; --push is sending it straight to the print shop's outbox. Look on the desk afterwards and there is nothing there — which is the point, not a bug.

saying these in an interview costs you the question

  • Assumes the image always appears in docker images after a buildx build
  • Describes --push as just running docker push afterwards
  • Adds a separate docker push step and cannot explain the failure
  • Thinks buildx authenticates to the registry on its own
  • Confuses --load with --push, or thinks they are the same exporter
  • Believes an output flag is optional because the build exits zero

context

open as a page

Why do developers bind-mount source into a running Docker container instead of rebuilding the image on every edit?

level: juniorimportance: must knowfreq 76%

basics

~20 s

A bind mount maps a host directory onto a path inside the running container, so an edit on the host is visible to the process instantly. Source baked in with COPY changes only when you rebuild the image.

open as a page

In a Dockerfile, what happens to `docker build` when a `RUN` test command exits non-zero?

level: juniorimportance: must knowfreq 66%

basics

~20 s

A non-zero exit from any RUN command aborts docker build immediately: no layer is committed for that step and no image is tagged. Running the test suite in a RUN step is what turns a failing test into a failing build.

open as a page

Why does a CI job run `docker buildx create --driver docker-container` before it builds?

level: middleimportance: must knowfreq 63%

basics

~20 s

It creates a builder instance backed by a BuildKit container rather than the BuildKit embedded in the daemon. That driver is what unlocks exporting cache to an external backend, building for other platforms, and a custom BuildKit config.

open as a page

Why does a host debugger fail to reach a JVM's JDWP port in a container despite -p 5005:5005?

level: middleimportance: must knowfreq 68%

basics

~20 s

Because the debug agent is listening on the container's own loopback address, so the published port forwards to nothing. Bind the agent to all interfaces instead - JDWP address=*:5005, debugpy --listen 0.0.0.0:5678 - and the attach succeeds.

open as a page

In a Docker dev container, why does a bind mount over /app hide node_modules, and how do you fix it?

level: middleimportance: must knowfreq 66%

basics

~20 s

The bind mount replaces the whole /app directory, so the node_modules the image installed at build time is shadowed by the host's copy, which is often missing. Fix it with a deeper mount: an anonymous volume at /app/node_modules, seeded from the image.

open as a page

How do you make a containerized process wait at startup until your debugger attaches?

level: juniorimportance: should knowfreq 52%

basics

~10 s

Start the debug agent in suspend mode: JDWP suspend=y, debugpy --wait-for-client, or dlv exec in headless mode. The process then blocks before your code runs, so a debugger attaching afterwards still catches early startup.

open as a page

Why does bind-mounting source into a Docker container not reload a compiled service such as a .NET app?

level: middleimportance: should knowfreq 45%

basics

~20 s

The container runs a compiled artefact produced at build time, and a bind mount only changes source on disk. Nothing recompiles it: a runtime-only image has no compiler and nothing is watching. You need a dev stage with the SDK plus a watch process.

open as a page

Why does `docker build .` never run the tests in your Dockerfile's test stage?

level: middleimportance: should knowfreq 55%

basics

~20 s

BuildKit builds only the target stage and the stages that target depends on. The default target is the last stage in the file, so a test stage nothing depends on is pruned and never executed. Run it explicitly with docker build --target test.

open as a page

Why is the Docker build cache empty on every job when CI runners are ephemeral, and where can it live instead?

level: seniorimportance: should knowfreq 60%

basics

~20 s

BuildKit keeps its cache in the builder's own state — a volume on the runner — and an ephemeral machine is destroyed after the job. To reuse anything, export cache to an external backend such as a registry and import it next build.

open as a page

A debugger attached to a container hits the wrong lines or no breakpoints at all - what causes that?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Either the code in the image is not the code in your editor, or the debug information records paths from inside the build stage that do not exist on your machine. Fix the first by rebuilding from the exact tree, the second with a source-path substitution.

open as a page

In a multi-stage Docker build, what does a green test stage not prove about the shipped image?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The test stage is a different image: fuller base, dev dependencies, usually root, no runtime limits. A green suite proves the code passes, not that the shipped image starts — missing libraries, USER permissions, a wrong ENTRYPOINT and memory limits all survive it.

open as a page

Would you give a 47-node CI fleet one shared BuildKit builder or a fresh builder per job?

level: principalimportance: should knowfreq 39%

basics

~20 s

It trades warm cache against isolation. A long-lived shared builder keeps cache local and skips import cost, but is stateful infrastructure with shared cache mounts and fleet-wide blast radius; a per-job builder is disposable but always cold.

open as a page

Why does a ptrace-based profiler fail to attach to a process inside a Docker container?

level: seniorimportance: nice to knowfreq 31%

basics

~10 s

The container's default privilege set excludes CAP_SYS_PTRACE, so a process that is not the target's parent is refused permission to trace it. Adding --cap-add=SYS_PTRACE to the debug run is the usual fix.

open as a page

Why does a file watcher inside a Docker container miss host edits made over a bind mount?

level: seniorimportance: nice to knowfreq 33%

basics

~20 s

Watchers rely on kernel inotify events. On a native Linux engine the bind mount is the same filesystem, so events arrive; when the directory reaches the container through a VM's file-sharing layer, host-side changes are often never raised as events. Polling is the fallback.

open as a page

How do you get test and coverage reports out of a test stage in a `docker build`?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

A build writes nothing to the host, so export the files: docker buildx build --target report --output type=local,dest=./reports writes that stage's filesystem into ./reports. Or build the test stage into an image and run it with a bind mount.

open as a page