Build an agent workload
Build a kit that runs Claude Code on a Linux base image of your choice. You'll install the agent, give it access to the Anthropic API, and add model settings and instructions for your team. The result is a v3 workload kit you can run locally or publish for others to use. You can follow the same steps for other agents or your organization's own base images.
This tutorial prepares the whole environment, from system packages to the command that starts the agent. If you already have a suitable agent image, see Package an existing agent image. To combine a published agent kit with tools, author a kit set.
If you're starting with kits, read Author kits for an introduction to the files you'll create. Field definitions are in the upstream v3 specification.
Prepare the kit directory
You need sbx, Docker with Buildx, and an Anthropic
API key. Create a directory beside the project you want the agent to work on:
$ mkdir claude-team
The completed directory contains three files:
claude-team/
├── claude-team.yaml
├── claude-team.dockerfile
└── context.mdThe Dockerfile installs the agent and sets the command to run it. The YAML file, called the descriptor, tells Docker Sandboxes what the agent needs to run, such as network access and credentials. The Markdown file contains instructions for the agent. Give the YAML file and Dockerfile the same name before the extension so the build can find both files.
This kit uses v3 and is independent of the built-in claude kit, which uses
v2. Any mixins you add must also use v3. To customize the built-in kit, see
Kits v2.
Use your own base image
This tutorial starts from Red Hat Universal Base Image (UBI) 9. The following steps add the tools, user account, and certificates the sandbox needs. You can use another Linux image, including one maintained by your organization. Adjust the package and account commands for that image, following the same base image requirements.
Install the system packages
Create claude-team/claude-team.dockerfile with the base image and packages:
FROM registry.access.redhat.com/ubi9/ubi:9.8
USER root
RUN dnf install -y bash ca-certificates curl-minimal git shadow-utils tar gzip \
&& dnf clean allBash runs shell commands, Git accesses source repositories, and curl uses the CA certificates to make HTTPS requests. The remaining packages create the agent's user account and unpack its installer. Add any compilers, libraries, or other tools your projects need here.
Create the agent account
The sandbox needs a non-root agent user with UID 1000 and home directory
/home/agent. Append the following to the Dockerfile to create that account
and writable directories for its workspace, configuration, and state:
RUN groupadd --gid 1000 agent \
&& useradd --uid 1000 --gid 1000 --create-home --shell /bin/bash agent \
&& mkdir -p /home/agent/workspace /home/agent/.local/bin \
/home/agent/.local/share /home/agent/.local/state \
/home/agent/.config/claude-team /home/agent/.docker/sandbox/locks \
&& chown -R agent:agent /home/agentCreating these directories during the build makes the agent their owner
before Docker Sandboxes mounts the workspace and storage. The agent runs
without sudo in this example, so install system packages in the Dockerfile.
Use install hooks for setup that depends on an individual sandbox.
Prepare certificate trust
HTTPS requests from the sandbox go through a proxy. Docker Sandboxes adds
the proxy's certificate authority (CA) to the sandbox's trusted certificates
when it starts. UBI keeps its certificates at a different path from the one
sbx uses. Copy them to the expected path, then tell tools to use that file
so they trust both public certificates and the proxy:
RUN update-ca-trust \
&& mkdir -p /usr/local/share/ca-certificates /etc/ssl/certs \
&& cp /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crtIf you add corporate CA certificates during the build, add them to
/etc/pki/ca-trust/source/anchors/ before running update-ca-trust and
copying the bundle. For another distribution, use its certificate management
command and source bundle path.
Prepare the shell environment
An environment variable exported in one shell isn't automatically available in another. Give the agent a file where it can save variables for later shell sessions. The following lines create that file and arrange for login, interactive, and non-interactive Bash shells to read it:
RUN touch /etc/sandbox-persistent.sh \
&& chown agent:agent /etc/sandbox-persistent.sh \
&& chmod 0644 /etc/sandbox-persistent.sh \
&& printf '%s\n' '. /etc/sandbox-persistent.sh' \
> /etc/profile.d/sandbox-persistent.sh \
&& printf '%s\n' '. /etc/sandbox-persistent.sh' >> /home/agent/.bashrc
ENV HOME=/home/agent \
PATH="/home/agent/.local/bin:${PATH}" \
BASH_ENV=/etc/sandbox-persistent.shBASH_ENV tells non-interactive Bash to read the file. You'll include
instructions for the agent to use it later in the tutorial.
Build the agent into the image
Append the following to the Dockerfile to install Claude Code as agent and
set its launch command:
USER agent
ENV CLAUDE_ENV_FILE=/etc/sandbox-persistent.sh \
IS_SANDBOX=1
ARG CLAUDE_VERSION
RUN curl -fsSL https://claude.ai/install.sh -o /tmp/install-claude.sh \
&& bash /tmp/install-claude.sh "${CLAUDE_VERSION}" \
&& rm /tmp/install-claude.sh
WORKDIR /home/agent/workspace
ENTRYPOINT ["claude", "--settings", "/home/agent/.config/claude-team/settings.json"]
CMD []Installing as agent puts Claude Code under /home/agent/, where that user
can access it. CLAUDE_ENV_FILE points Claude Code to the file you prepared
for persistent environment variables. You'll set CLAUDE_VERSION in the
descriptor in the next step.
The Dockerfile also sets the user, working directory, environment variables,
and launch command. CMD [] clears any arguments inherited from the base
image. The --settings option loads the model you choose for each sandbox.
You'll create its settings file in Write the model settings.
Each sandbox created from this image gets the same Claude Code binary. The next steps configure how Docker Sandboxes runs it. Any mixins you add must use v3 and contain tools compatible with this workload's operating system and architecture.
Describe the workload and its inputs
Create claude-team/claude-team.yaml. Start by identifying the workload and
declaring two arguments:
versionselects the Claude Code version to install during the image build.modelselects the model to use when creating a sandbox.
# syntax=docker/sandbox-kit:3
schemaVersion: "3"
kind: workload
displayName: Team Claude Code
description: Claude Code with team defaults and API-key authentication
args:
version:
default: "2.1.278"
pattern: '^[0-9]+\.[0-9]+\.[0-9]+$'
buildArg: CLAUDE_VERSION
model:
default: sonnet
enum: [sonnet, opus, haiku]
provides: ["claude@${{ kit.args.version }}"]version sets the Dockerfile's CLAUDE_VERSION build argument. The build
checks the version against pattern, then includes it in provides so
other kits can check which Claude Code version is installed.
model lets you choose a model when creating each sandbox. The choice goes
into a settings file, so it doesn't change the installed agent.
Allow access to the API
With the agent installed, the next step is to give it access to the Anthropic
API. Add a capabilities list at the top level of claude-team.yaml, after
provides:
capabilities:
- type: com.docker.sandbox/sbx@1
- type: com.docker.sandbox/network-policy@1
config:
runtime:
allow:
- api.anthropic.com:443The network rule permits HTTPS requests to the Anthropic API while the sandbox runs. Downloading Claude Code in the Dockerfile uses the builder's network, so it doesn't need a rule here.
The sbx@1 entry declares that the workload is prepared for sbx to manage
the agent's launch. See
Declare the agent launch contract
for the shells and user account that this requires.
Declare the credential
Claude Code can reach the API, but it also needs to authenticate. Add a credential capability to tell Docker Sandboxes which key to use and how to include it in requests. You'll store the key on your host, outside the kit.
Append this entry to the same capabilities list:
- type: com.docker.sandbox/credential@1
description: Anthropic API access
config:
service: anthropic
phase: runtime
apiKey:
name: ANTHROPIC_API_KEY
proxyManaged: true
inject:
- domain: api.anthropic.com
header: x-api-key
format: "%s"The sandbox receives a placeholder in ANTHROPIC_API_KEY. When Claude Code
makes a request to api.anthropic.com, the host proxy inserts the real API
key into the x-api-key header. The key stays on the host. You'll supply
its value and approve its use when launching the kit.
Write the model settings
The Dockerfile's launch command reads
/home/agent/.config/claude-team/settings.json. Use the lifecycle capability
to create that file with the model chosen for the sandbox.
Append this entry to capabilities:
- type: com.docker.sandbox/lifecycle@1
config:
files:
- path: /home/agent/.config/claude-team/settings.json
content: |
{"model": "${{ kit.args.model }}"}
mode: "0644"Docker Sandboxes fills in the chosen model and writes the file before
Claude Code starts. Creating the file at this point lets each sandbox use a
different model with the same image.
Docker Sandboxes writes these files as UID 1000 after running install hooks.
Use an absolute path the agent can write to, as in this example. Shell
variables such as $HOME aren't expanded in the path.
Add agent instructions
The kit can also give Claude Code instructions about the environment. Save the following Markdown alongside the descriptor and Dockerfile:
## Team workflow
Read the project's README before changing code. Run the project's checks
before reporting a task complete, and report any checks you couldn't run.
Claude Code is installed in this sandbox. Its additional settings are at
`/home/agent/.config/claude-team/settings.json`.
Use `/etc/sandbox-persistent.sh` for environment exports needed by later
Bash commands. Keep shell completion scripts out of that file because
non-interactive commands also source it.Append an agent-context entry to capabilities to include these instructions:
- type: com.docker.sandbox/agent-context@1
config:
filename: CLAUDE.md
contentFile: ./context.mdThe build includes context.md in the image. When the sandbox runs, sbx
writes a CLAUDE.md file in the parent directory of the mounted workspace.
That file points Claude Code to your context.md. A CLAUDE.md in your
project stays in place.
Use contentFile, as in this example, to keep longer instructions in a
separate Markdown file. You can also write instructions directly in the
workload's descriptor using content instead of contentFile.
For how instructions from multiple kits work together, see
Runtime access and instructions.
Store the key and run
Store your Anthropic API key on the host:
$ sbx secret set anthropic
From the directory containing claude-team, launch the kit:
$ sbx run --name claude-team ./claude-team
sbx builds the kit, mounts your current directory as the workspace, and
launches Claude Code. To work on another project, append its path to the command.
When prompted, approve the kit's request to use your stored key, then follow
Claude Code's first-run prompts. The agent needs both the stored key and your
approval to use it. See
Credential bindings.
Choose a different model when creating a sandbox:
$ sbx run --name claude-team-opus ./claude-team \
--kit-arg claude-team.model=opus
The claude-team prefix matches the local kit directory's name. Docker
Sandboxes checks that opus is one of the choices in enum, then writes it
to the settings file before Claude Code starts.
Iterate and publish
Edit the descriptor, Dockerfile, or context file and create another sandbox with a different name to test the changes:
$ sbx run --name claude-team-test-2 ./claude-team
Restarting an existing sandbox won't pick up your edits. To try another
agent version locally, update args.version.default in the descriptor and
create another sandbox.
sbx reuses the local build when the source files and kit arguments are
unchanged. Changing either can trigger a build, even for arguments such as
model that only affect sandbox setup. BuildKit can still reuse unchanged
image layers.
Publish the workload
When the kit is ready to share, sign in to Docker Hub, then build and push it
with Docker Buildx. Replace <NAMESPACE> with a Docker Hub namespace you can
push to:
$ docker login
$ docker buildx build ./claude-team \
--file ./claude-team/claude-team.yaml \
--tag docker.io/<NAMESPACE>/claude-team:1.0.0 \
--push
The --file option tells Buildx to read the descriptor. Its syntax line
selects the kit frontend, which reads the companion Dockerfile and includes
the descriptor in the published image. To publish a different agent version,
add --build-arg version=<CLAUDE_VERSION>. This flag uses the kit argument
name, version, which the descriptor maps to the Dockerfile's CLAUDE_VERSION.
Run the published kit by its image reference:
$ sbx run --name claude-team-shared docker.io/<NAMESPACE>/claude-team:1.0.0
For multi-platform images and distribution details, see
Build and distribute kits.
To learn how to package a tool separately from its workload, see
Build a tool mixin.
That tutorial also packages Claude Code, so use its mixin with a shell workload.
Combining it with this workload would give two kits that provide claude,
which Docker Sandboxes rejects.
You can also include the published workload in a
kit set and add settings
and instructions there. The workload still defines how to prepare the base
image, install the agent, and launch it.