BuildKit’s gRPC API lets you orchestrate builds from external applications, bypassing the typical docker build CLI.
Here’s a simple Go program that builds an image using BuildKit’s gRPC API. This demonstrates the fundamental workflow: connecting to BuildKit, defining a build source, and initiating the build.
package main
import (
"context"
"fmt"
"io"
"os"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/frontend/dockerfile/builder"
"github.com/moby/buildkit/frontend/dockerfile/dockerfilelang"
"google.golang.org/grpc"
)
func main() {
// Connect to the BuildKit daemon. Default is "unix:///var/run/docker.sock"
// If BuildKit is running elsewhere, adjust this address.
conn, err := grpc.Dial("unix:///var/run/docker.sock", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
c := client.NewClient(conn)
// Define the build source. Here, it's a Dockerfile.
// The source is a map where keys are filenames and values are file contents.
// For a real-world scenario, you'd likely read this from a file.
src := map[string]io.Reader{
"Dockerfile": io.NopCloser(bytes.NewBufferString(`
FROM alpine:latest
RUN echo "Hello from BuildKit gRPC!" > /hello.txt
CMD ["cat", "/hello.txt"]
`)),
}
// Define build options.
// This includes the frontend to use (dockerfilelang.Name) and its options.
// The "exports" specify what to capture from the build, like the image digest.
// Here, we're exporting the "image" which will contain the built image.
opt := &client.BuildOpt{
Frontend: dockerfilelang.Name,
FrontendInputs: src,
Localτευ: []client.LocalTarget{
{
Name: "dockerfile",
Path: "/app", // This path is relative to the build context, which is our `src` map
},
},
Exports: []client.ExportOpt{
client.ExportAttr(client.AttrImage, "my-grpc-built-image:latest"),
},
}
// Start the build process.
// The context is used for cancellation.
// The result is a channel that streams build progress and results.
result, err := c.Build(context.Background(), nil, nil, opt, nil)
if err != nil {
panic(err)
}
// Process the build results.
// We iterate over the result channel to see build logs and final status.
for res := range result {
if res.Err != nil {
fmt.Printf("Build error: %v\n", res.Err)
return
}
// Print build output (e.g., logs from RUN commands)
if res.Description != nil {
fmt.Printf("Build output: %s\n", res.Description.Text)
}
// Print any exported attributes, like the image digest
for k, v := range res.Attributes {
fmt.Printf("Attribute: %s = %s\n", k, v)
}
}
fmt.Println("Build completed successfully!")
}
BuildKit’s core is a sophisticated build engine that can execute build steps defined in various formats (Dockerfile, OCI, etc.) and optimize them across different platforms and caches. The gRPC API exposes this engine’s power, allowing you to integrate custom build logic directly into your applications or CI/CD pipelines. You’re essentially telling the BuildKit daemon, "Here’s my build definition, go build it," and receiving structured responses about the process and the final artifact.
When you run this code, you’ll see output detailing the build process, similar to what docker build shows, and finally, an indication of success or failure. The key is that the client.Build function initiates the entire operation, and the returned channel (result) is your window into its execution. You’re not just running a command; you’re interacting with a stateful service.
The client.BuildOpt struct is where you define what to build and how. Frontend specifies the build language (like dockerfilelang.Name). FrontendInputs provides the actual build definition files (e.g., your Dockerfile). Localτευ defines the build context – the files and directories available to the build process. Exports tell BuildKit what outputs you care about, such as the final image tag or digest.
The result channel yields client.BuildResult structs. These contain information about the build’s progress, including logs (res.Description.Text) and any errors (res.Err). Crucially, they also contain res.Attributes, which can include exported image references, build cache information, or other metadata specified in your Exports.
A common pitfall is misconfiguring the Localτευ. The paths within Localτευ are relative to the build context. If your Dockerfile refers to a file named app.conf in the same directory, and you’ve set Localτευ with Path: "/app", BuildKit will look for app.conf within the /app directory of your build context. In our example, the Dockerfile itself is the only thing in the context, so paths within it are relative to the root of that context.
Most people don’t realize that BuildKit’s gRPC API allows for much more than just building Dockerfiles. You can use different frontends, like the oci frontend for building OCI images directly, or even custom frontends you’ve developed. This flexibility makes BuildKit a powerful foundation for complex build systems beyond traditional container images.
The next step after mastering programmatic builds is exploring advanced BuildKit features like cache management and multi-platform builds via the gRPC API.