This tutorial walks you through using Depot API to build Docker images programmatically. The container builds API allows you to build Docker images on behalf of your users without managing build infrastructure.
Depot provides two SDKs for building images via the API:
Node.js SDK + Depot CLI
The Node.js SDK handles project management and build registration, then delegates the actual build to the Depot CLI. This approach is simpler and requires less code.
Go SDK + BuildKit
The Go SDK provides direct access to BuildKit, giving you full control over the build process. You manage the connection, configuration, and build steps yourself.
Select the SDK that best fits your use case:
This tutorial uses code from our example repository. Clone it to follow along:
git clone https://github.com/depot/examples.git
cd examples/build-apiThe example repository contains the following Node.js examples under (nodejs/):
list-projects.js - List all projectscreate-project.js - Create a new projectdelete-project.js - Delete a projectcreate-build.js - Build image with options (load/save/push)To get started, install Node.js dependencies:
cd nodejs
npm installtest-token) and click Create tokenSet the token as an environment variable:
export DEPOT_TOKEN=<your-org-token>Install via curl:
curl -L https://depot.dev/install-cli.sh | shOr via Homebrew (macOS):
brew install depot/tap/depotProjects in Depot provide isolated builder infrastructure and cache storage. We recommend creating a separate project for each customer organization to maximize cache effectiveness and prevent cache poisoning.
To create a project, use the ProjectService.createProject method with your organization token:
const {depot} = require('@depot/sdk-node')
const headers = {
Authorization: `Bearer ${process.env.DEPOT_TOKEN}`,
}
const result = await depot.core.v1.ProjectService.createProject(
{
name: 'my-project',
regionId: 'us-east-1',
cachePolicy: {keepBytes: 50 * 1024 * 1024 * 1024, keepDays: 14}, // 50GB, 14 days
},
{headers},
)
console.log(result.project.projectId)Try it with the example: node nodejs/src/create-project.js my-project
Save the projectId from the output, you'll need it for builds.
Example output:
_Project {
projectId: 'krt0wtn195',
organizationId: '3d1h48dqlh',
name: 'my-project',
regionId: 'us-east-1',
createdAt: Timestamp { seconds: 1708021346n, nanos: 83000000 },
cachePolicy: _CachePolicy { keepBytes: 53687091200n, keepDays: 14 }
}To build an image, first register a build with the Build API using BuildService.createBuild. This returns a build ID and one-time build token that you pass to the Depot CLI:
const {depot} = require('@depot/sdk-node')
const {exec} = require('child_process')
const headers = {
Authorization: `Bearer ${process.env.DEPOT_TOKEN}`,
}
// Register the build
const result = await depot.build.v1.BuildService.createBuild({projectId: '<project-id>'}, {headers})
// Execute build with Depot CLI
exec(
'depot build --load .',
{
env: {
DEPOT_PROJECT_ID: '<project-id>',
DEPOT_BUILD_ID: result.buildId,
DEPOT_TOKEN: result.buildToken,
},
},
(error, stdout, stderr) => {
if (error) {
console.error(`Error: ${error}`)
return
}
console.log(stdout)
},
)Try it with the example: node nodejs/src/create-build.js <project-id>
The --load flag downloads the built image to your local Docker daemon.
List your local Docker images:
docker image lsRun the built container:
docker run <image-id>You should see "Hello World" output from the Node.js application.
Instead of loading locally with --load, you can save the image to Depot Registry using the --save flag:
exec('depot build --save .', {
env: {
DEPOT_PROJECT_ID: '<project-id>',
DEPOT_BUILD_ID: result.buildId,
DEPOT_TOKEN: result.buildToken,
},
})Try it: node nodejs/src/create-build.js <project-id> save
The build output shows how to pull or push the saved image:
Saved target:
To pull: depot pull --project <project-id> <build-id>
To push: depot push --project <project-id> --tag <REPOSITORY:TAG> <build-id>To push directly to Docker Hub, GHCR, ECR, or other registries during the build, use the --push flag with --tag:
exec('depot build --push --tag docker.io/myuser/myapp:latest .', {
env: {
DEPOT_PROJECT_ID: '<project-id>',
DEPOT_BUILD_ID: result.buildId,
DEPOT_TOKEN: result.buildToken,
},
})First authenticate with docker login, then pushing to other registries simply requires setting the proper image name:
# Docker Hub
node nodejs/src/create-build.js <project-id> push docker.io/myuser/myapp:latest
# GitHub Container Registry
node nodejs/src/create-build.js <project-id> push ghcr.io/myorg/myapp:latest
# AWS ECR
node nodejs/src/create-build.js <project-id> push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latestThis tutorial uses code from our example repository. Clone it to follow along:
git clone https://github.com/depot/examples.git
cd examples/build-apiThe Go examples use two packages:
buf.build/gen/go/depot/api) - For project managementgithub.com/depot/depot-go) - For buildsAvailable examples:
list-projects/main.go - List all projectscreate-project/main.go - Create a new projectdelete-project/main.go - Delete a projectcreate-build/main.go - Build image (saved to Depot)build-and-push/main.go - Build and push to external registryInstall dependencies:
cd go
go mod downloadBuilding with the Go SDK involves three steps:
See the complete implementation in build-and-push/main.go.
test-token) and click Create tokenSet the token as an environment variable:
export DEPOT_TOKEN=<your-org-token>Projects in Depot provide isolated builder infrastructure and cache storage. To create a project, use the Buf Connect API client with ProjectService.CreateProject:
import (
"net/http"
corev1 "buf.build/gen/go/depot/api/protocolbuffers/go/depot/core/v1"
"buf.build/gen/go/depot/api/connectrpc/go/depot/core/v1/corev1connect"
"connectrpc.com/connect"
)
token := os.Getenv("DEPOT_TOKEN")
// Create the Project Service client
client := corev1connect.NewProjectServiceClient(
http.DefaultClient,
"https://api.depot.dev",
)
// Create a new project
req := connect.NewRequest(&corev1.CreateProjectRequest{
Name: "my-project",
RegionId: "us-east-1",
CachePolicy: &corev1.CachePolicy{
KeepGb: 50, // 50GB
KeepDays: 14, // 14 days
},
})
// Add authentication header
req.Header().Set("Authorization", fmt.Sprintf("Bearer %s", token))
resp, err := client.CreateProject(ctx, req)
if err != nil {
log.Fatal(err)
}
log.Printf("Project ID: %s", resp.Msg.Project.ProjectId)Try it with the example: go run ./create-project/main.go my-project
Save the project ID, you'll need it for builds.
To start a build, register it with the Build API using build.NewBuild. This returns a build ID and one-time build token:
import (
"github.com/depot/depot-go/build"
cliv1 "github.com/depot/depot-go/proto/depot/cli/v1"
)
token := os.Getenv("DEPOT_TOKEN")
projectID := os.Getenv("DEPOT_PROJECT_ID")
build, err := build.NewBuild(ctx, &cliv1.CreateBuildRequest{
ProjectId: projectID,
}, token)
if err != nil {
log.Fatal(err)
}
// Report build result when finished
var buildErr error
defer build.Finish(buildErr)The build.Finish() call reports success or failure back to Depot when your build completes.
With your build registered, acquire an ephemeral BuildKit machine using machine.Acquire. The machine comes pre-configured with your project's cache:
import "github.com/depot/depot-go/machine"
buildkit, buildErr := machine.Acquire(ctx, build.ID, build.Token, "arm64")
if buildErr != nil {
return
}
defer buildkit.Release()Specify "arm64" or "amd64" for your target platform. Released machines stay alive for 2 minutes to serve subsequent builds.
Connect to your BuildKit machine using buildkit.Connect:
import "github.com/moby/buildkit/client"
buildkitClient, buildErr := buildkit.Connect(ctx)
if buildErr != nil {
return
}This establishes a secure mTLS connection to the BuildKit endpoint.
Configure your build by creating a SolveOpt with your Dockerfile path, build context, and export settings:
import (
"github.com/docker/cli/cli/config"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider"
)
solverOptions := client.SolveOpt{
Frontend: "dockerfile.v0",
FrontendAttrs: map[string]string{
"filename": "Dockerfile",
"platform": "linux/arm64",
},
LocalDirs: map[string]string{
"dockerfile": ".",
"context": ".",
},
Exports: []client.ExportEntry{
{
Type: "image",
Attrs: map[string]string{
"name": "myuser/myapp:latest",
"oci-mediatypes": "true",
"push": "true",
},
},
},
Session: []session.Attachable{
authprovider.NewDockerAuthProvider(config.LoadDefaultConfigFile(os.Stderr), nil),
},
}The Session uses your Docker credentials from docker login to authenticate registry pushes.
To monitor build progress, create a status channel and process BuildKit status messages:
import "encoding/json"
buildStatusCh := make(chan *client.SolveStatus, 10)
go func() {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
for status := range buildStatusCh {
_ = enc.Encode(status)
}
}()This streams build progress in real-time as JSON.
Execute the build with buildkitClient.Solve. BuildKit automatically reuses cached layers from your project:
_, buildErr = buildkitClient.Solve(ctx, nil, solverOptions, buildStatusCh)
if buildErr != nil {
return
}When complete, your image is pushed to the registry specified in the Exports configuration.
Try the complete example: DEPOT_PROJECT_ID=<project-id> go run ./build-and-push/main.go
To push to external registries, configure the full registry path in your image name and provide authentication.
Exports: []client.ExportEntry{
{
Type: "image",
Attrs: map[string]string{
"name": "docker.io/myuser/myapp:latest", // or ghcr.io, ECR, etc.
"oci-mediatypes": "true",
"push": "true",
},
},
},The build-and-push example supports two options for authentication:
After running docker login, BuildKit automatically uses credentials from ~/.docker/config.json:
docker login docker.io
DEPOT_PROJECT_ID=<project-id> go run ./build-and-push/main.go docker.io/user/app:latestProvide credentials via environment variables:
DEPOT_PROJECT_ID=<project-id> \
REGISTRY_USERNAME=myuser \
REGISTRY_PASSWORD=mytoken \
REGISTRY_URL=https://index.docker.io/v1/ \
go run ./build-and-push/main.go docker.io/user/app:latestThe example automatically detects which method to use based on the presence of REGISTRY_USERNAME and REGISTRY_PASSWORD.
See the complete working examples in the repository: go/create-build/main.go and go/build-and-push/main.go.