BuildKit’s daemon, buildkitd, is the engine that actually executes your container builds. It’s not just a simple command-line wrapper; it’s a sophisticated, concurrent, and distributed build engine that can dramatically speed up your build times and improve their reliability.
Let’s see buildkitd in action. Imagine you have a simple Dockerfile:
# Dockerfile
FROM alpine:latest
RUN echo "Hello, BuildKit!" > /hello.txt
CMD ["cat", "/hello.txt"]
Normally, you’d build this with docker build. But if you have buildkitd running, you can use docker buildx build (which uses BuildKit by default) or even directly interact with buildkitd via its client API. For demonstration, let’s build it using docker buildx:
# Ensure buildx is configured and active
docker buildx create --use
# Build the image
docker buildx build --platform linux/amd64 -t my-hello-app .
When you run this, buildkitd takes over. It parses the Dockerfile, identifies the steps, and starts executing them. You’ll see output indicating layers being built, cached layers being utilized if available, and the final image being pushed (or stored locally). The magic here is that buildkitd doesn’t just run commands sequentially; it analyzes the build graph. If you had multiple independent RUN commands, it would execute them in parallel. If a layer’s inputs haven’t changed, it reuses the cached output without re-executing the command.
The core problem buildkitd solves is the inefficiency and inflexibility of traditional builders. Docker’s legacy builder was monolithic and less capable of parallel execution or advanced caching. buildkitd is designed from the ground up for speed, concurrency, and extensibility. It breaks down the build process into a directed acyclic graph (DAG) of build steps. Each step represents an operation (like copying files, running a command, or downloading a dependency), and its dependencies are clearly defined. This graph allows buildkitd to:
- Execute steps concurrently: Independent steps are run in parallel, utilizing multiple CPU cores.
- Cache aggressively: It caches not just entire layers, but individual build steps based on their inputs. If only a single line in a multi-line
RUNcommand changes, only that specific step (and subsequent dependent steps) needs to be re-executed. - Support advanced features: This includes features like multi-platform builds, remote caching, and exporting build artifacts in various formats.
Configuring buildkitd primarily involves its configuration file, typically located at /etc/buildkit/buildkitd.toml. This file controls everything from its network listeners to its caching behavior and external integrations.
Here’s a look at some key configuration options:
1. Network Listeners:
By default, buildkitd often listens on a Unix domain socket for local clients. You can configure it to listen on TCP ports for remote access or for specific client types.
# /etc/buildkit/buildkitd.toml
[grpc]
address = ["unix:///run/buildkit/buildkitd.sock", "tcp://0.0.0.0:1234"]
This configuration makes buildkitd listen on both its default Unix socket and on TCP port 1234 on all network interfaces. This is useful if you want to connect from a different machine or have clients that only support TCP.
2. Cache Configuration: BuildKit’s caching is one of its most powerful features. You can configure different types of caches, including local disk caches and remote caches.
# /etc/buildkit/buildkitd.toml
[worker.oci]
# Default cache size for local storage
cache-size = 10000000000 # 10GB
[[cache.blob]]
type = "filesystem"
# Path to store cache blobs
path = "/var/lib/buildkit/cache"
[[cache.registry]]
type = "redis"
# Redis connection string for remote cache
redis-addr = "redis://localhost:6379"
Here, we’re setting the default local cache size to 10GB and explicitly defining a filesystem cache stored at /var/lib/buildkit/cache. We’ve also added a configuration for a redis type cache, which would allow BuildKit to push and pull build cache manifests from a Redis instance, enabling shared caching across multiple build agents.
3. Rootless Mode: BuildKit can run in rootless mode, which is a significant security enhancement. This requires specific configuration for user namespaces and storage.
# /etc/buildkit/buildkitd.toml
[rootless]
enabled = true
# Specify a UID/GID range for user namespaces if needed
# uid-map = "0:100000:65536"
# gid-map = "0:100000:65536"
When rootless = true, buildkitd runs as a non-privileged user, leveraging user namespaces to isolate build operations. This drastically reduces the attack surface if the build process itself is compromised.
4. Registries and Authentication: You can configure BuildKit to use specific registries and manage authentication for them.
# /etc/buildkit/buildkitd.toml
[[registry.config]]
# Hostname of the registry
host = "docker.io"
# Path to a Docker-like config file for credentials
config-file = "/home/user/.docker/config.json"
[[registry.config]]
host = "my.private.registry:5000"
# Alternative: specify credentials directly (less secure)
# username = "myuser"
# password = "mypassword"
This allows buildkitd to authenticate with registries like docker.io using your existing Docker credentials, or with private registries using either a config file or direct credentials.
The most surprising thing about buildkitd’s caching is how granular it is. It’s not just about caching the result of a RUN command; it caches the execution of that command based on its exact inputs. If you have a RUN command that downloads a specific Git commit, and you change the commit hash, BuildKit will recognize this as a change in input and re-run that specific step, even if the previous command in the Dockerfile (e.g., apt update) remains the same. This fine-grained caching is what enables massive speedups on even minor code changes.
The next concept you’ll likely encounter is how to leverage BuildKit’s extensibility, particularly through its plugin system and how to configure custom solvers or exporters.