How Docker Builds Images: Inside the CLI, API, and BuildKit Mechanics
This article provides a comprehensive, step‑by‑step analysis of Docker's client‑server architecture, the Docker Engine API for image building, the internal workings of the Docker CLI, the differences between the legacy builder and BuildKit, and how the dockerd daemon processes build requests, complete with code excerpts and diagrams.
Docker Architecture Overview
Docker follows a client‑server (C/S) model where the docker command is the CLI client and dockerd is the daemon. On Linux the daemon is typically managed by systemd, but it can also be started directly as a binary with root privileges.
The Docker Engine exposes a REST API; running docker version | grep API shows the CLI API version (e.g., 1.41) and the daemon's minimum compatible version.
Docker Build API
After cloning the Docker source, the command make swagger-docs starts a container exposing the API documentation on port 9000. The build endpoint is POST /v1.41/build. It expects a tar archive (optionally compressed) as the request body and uses the application/x-tar content type.
➜ ~ docker version | grep API
API version: 1.41
API version: 1.41 (minimum version 1.12)Authentication information is passed via the X-Registry-Config header, which contains a Base64‑encoded ~/.docker/config.json structure.
Docker CLI – Legacy Builder (v1)
The CLI source resides in github.com/docker/cli. The main entry point for image building is cli/command/image/build.go. The function runBuild decides whether to use the legacy builder or BuildKit based on the DOCKER_BUILDKIT environment variable.
func runBuild(dockerCli command.Cli, options buildOptions) error {
buildkitEnabled, err := command.BuildKitEnabled(dockerCli.ServerInfo())
if err != nil {
return err
}
if buildkitEnabled {
return runBuildBuildKit(dockerCli, options)
}
// legacy builder logic omitted
}Key parameter handling includes mutual exclusion of --stream and --compress, and the ability to read the Dockerfile or build context from stdin using - as the filename.
(MoeLove)➜ x cat Dockerfile | DOCKER_BUILDKIT=0 docker build -f - .
Sending build context to Docker daemon 15.41kB
Step 1/3 : FROM scratch
--->
Successfully built abcdef12345Docker CLI – BuildKit
When BuildKit is enabled, the CLI creates a long‑lived gRPC session via trySession, which internally calls session.NewSession. This session enables advanced features such as streaming, secrets, and SSH forwarding.
func trySession(dockerCli command.Cli, contextDir string, forStream bool) (*session.Session, error) {
if !isSessionSupported(dockerCli, forStream) {
return nil, nil
}
sharedKey := getBuildSharedKey(contextDir)
s, err := session.NewSession(context.Background(), filepath.Base(contextDir), sharedKey)
if err != nil {
return nil, errors.Wrap(err, "failed to create session")
}
return s, nil
}BuildKit supports three output modes: local, tar, and the default daemon storage, specified with -o type=local,dest=path or similar.
outputs, err := parseOutputs(options.outputs)
for _, out := range outputs {
switch out.Type {
case "local":
// handle local output
case "tar":
// handle tar output
}
}Secrets and SSH are provided through secretsprovider and sshprovider, which attach to the session and allow secure credential handling during the build.
dockerd – Server Side Build Handling
The daemon registers the /build endpoint in api/server/router/build/build.go. The handler parses the request into backend.BuildConfig, extracts authentication headers, and then calls Backend.Build.
func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string, error) {
options := config.Options
useBuildKit := options.Version == types.BuilderBuildKit
var build *builder.Result
if useBuildKit {
build, err = b.buildkit.Build(ctx, config)
} else {
build, err = b.builder.Build(ctx, config)
}
// tag images, handle squash, return image ID
}The daemon chooses between the legacy builder and BuildKit based on the request's Version field. After the build completes, it may emit auxiliary information (e.g., image ID) and apply any tags supplied via -t.
Overall, the article walks through the full lifecycle of a Docker image build: from the high‑level C/S architecture, through the REST API contract, the CLI's decision logic, the creation of BuildKit sessions, handling of build contexts and Dockerfiles, to the daemon's backend execution and image tagging.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Cloud Native Technology Community
The Cloud Native Technology Community, part of the CNBPA Cloud Native Technology Practice Alliance, focuses on evangelizing cutting‑edge cloud‑native technologies and practical implementations. It shares in‑depth content, case studies, and event/meetup information on containers, Kubernetes, DevOps, Service Mesh, and other cloud‑native tech, along with updates from the CNBPA alliance.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
