Improving the Developer Experience with SBX Kits

11 min read
sbx
sbx

In previous articles, we walked step by step through setting up a secure, isolated web development environment (Web IDE) with sbx, integrating a development agent based on docker-agent and Docker Model Runner, creating an sbx sandbox template, and giving it specific capabilities through "mini skills".

A brand new sbx feature: Kits, which let you customize sbx sandboxes by adding specific capabilities in a simple, reusable way (tools to install, environment variables, credentials to inject, domains to allow, files, startup commands, etc).

This means, for example, that we can remove the development tools (editors, languages, etc) from our sbx sandbox template (Dockerfile) and put them in a separate kit, which we can then reuse across other sbx sandbox templates.

New sbx sandbox template

So I started by simplifying the sbx sandbox template we created in previous articles, to make a new, more generic one that only contains the base elements along with the Web IDE (Code Server) and docker-agent:

dockerfile
FROM docker/docker-agent:1.54.0 AS coding-agent

FROM --platform=$BUILDPLATFORM docker/sandbox-templates:shell

LABEL maintainer="@k33g_org"

ARG USER_NAME=agent

ENV TERM=xterm-256color
ENV COLORTERM=truecolor

ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US.UTF-8
ENV LC_COLLATE=C
ENV LC_CTYPE=en_US.UTF-8

USER root

RUN <<EOF
apt-get update
apt-get install -y wget curl build-essential xz-utils software-properties-common
rm -rf /var/lib/apt/lists/*
EOF

# ------------------------------------
# Install code-server
# ------------------------------------
RUN curl -fsSL https://code-server.dev/install.sh | sh

# ------------------------------------
# Install docker-agent
# ------------------------------------
COPY --from=coding-agent /docker-agent /usr/local/bin/docker-agent

# Switch to the regular user
USER ${USER_NAME}

# ------------------------------------
# Install OhMyBash
# ------------------------------------
RUN <<EOF
bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmybash/oh-my-bash/master/tools/install.sh)"
EOF

EXPOSE 8080

You can find the code here: Dockerfile

Then we need to build and publish our new image:

bash
DOCKER_HANDLE=k33g
TAG=0.0.0
NAME=sbx-bill
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ${DOCKER_HANDLE}/${NAME}:${TAG} --push .

Replace DOCKER_HANDLE, NAME, and TAG with whatever values you want (you can also put them in a .env file for convenience).

Now let's move on to creating the sbx kit that will install the development tools in our sbx sandbox at launch time.

Creating the sbx kit

We're going to define an sbx kit that will install development tools (Go, Node.js, etc) in our sbx sandbox when it starts. This means the first sandbox launch will take a bit longer than usual since it needs to install the tools, but we gain flexibility — and the ability to modify the kit without having to rebuild the template.

So I created a new bill folder (following the same pattern as bob and riker from previous articles), with a config.yaml configuration file for docker-agent. I then added a kit subfolder (you can name it whatever you want) with a spec.yaml specification file that describes what to add to the sbx sandbox at launch:

bash
bill
├── config.yaml
├── kit
   └── spec.yaml
└── README.md

What I need in my sbx sandbox is the following tools:

  • Go (version 1.26.2)
  • Node.js (version 25)
  • Code Server extensions (Go, etc)

And finally, the Code Server development server needs to start each time the sbx sandbox launches.

sbx kit specification

Here's what the spec.yaml specification file for my sbx kit looks like:

yaml
schemaVersion: "1"
kind: agent
name: we-are-legion
displayName: Bill [We are legion]
description: Bill, a first generation clone of Bob

network:
  allowedDomains:
    - host.docker.internal:12434
    - open-vsx.org
    - openvsx.eclipsecontent.org
    - go.dev
    
agent:
  image: docker.io/k33g/sbx-bill:0.0.0

environment:
  variables:
    GOROOT: /usr/local/go
    GOPATH: /home/agent/go

commands:
  install:
    # Go
    - command: "wget https://golang.org/dl/go1.26.2.linux-arm64.tar.gz -O /tmp/go.tar.gz && tar -xf /tmp/go.tar.gz -C /tmp && mv /tmp/go /usr/local && rm /tmp/go.tar.gz"
      user: "0"
      description: Install Go

    # Add Go to the persistent PATH (used by subsequent install commands)
    - command: "echo 'export PATH=/usr/local/go/bin:/home/agent/go/bin:$PATH' >> /etc/sandbox-persistent.sh"
      user: "0"
      description: Persist Go PATH

    # Node.js via nodesource
    - command: "apt-get update && apt-get install -y ca-certificates curl gnupg && curl -fsSL https://deb.nodesource.com/setup_25.x | bash - && apt-get install -y nodejs"
      user: "0"
      description: Install Node.js

    # Go tools (gopls, etc.)
    - command: "PATH=/usr/local/go/bin:$PATH GOPATH=/home/agent/go go install golang.org/x/tools/gopls@latest && go install github.com/ramya-rao-a/go-outline@latest"
      user: "1000"
      description: Install Go tools

    # code-server extensions (download VSIX from Open VSX)
    - command: |
        mkdir -p /tmp/vsix
        curl -fsSL -o /tmp/vsix/material-icons.vsix \
          https://open-vsx.org/api/PKief/material-icon-theme/5.33.1/file/PKief.material-icon-theme-5.33.1.vsix
        curl -fsSL -o /tmp/vsix/material-product-icons.vsix \
          https://open-vsx.org/api/PKief/material-product-icons/1.7.1/file/PKief.material-product-icons-1.7.1.vsix
        curl -fsSL -o /tmp/vsix/go-ext.vsix \
          https://open-vsx.org/api/golang/Go/0.52.2/file/golang.Go-0.52.2.vsix
        code-server \
          --install-extension /tmp/vsix/material-icons.vsix \
          --install-extension /tmp/vsix/material-product-icons.vsix \
          --install-extension /tmp/vsix/go-ext.vsix
        rm -rf /tmp/vsix
      user: "1000"
      description: Install code-server extensions


  startup:
    - command: ["sh", "-c", "code-server --auth none --bind-addr 0.0.0.0:8080 \"${SBX_PROJECT_DIR:-${PWD:-$HOME}}\" > /tmp/code-server.log 2>&1"]

      user: "1000"
      background: true
      description: Start code-server on port 8080

sbx kit walkthrough

Header / Manifest

yaml
schemaVersion: "1"
kind: agent
name: we-are-legion
displayName: Bill [We are legion]
description: Bill, a first generation clone of Bob
FieldValueWhy
schemaVersion"1"Current and only version of the spec schema. Always "1".
kindagentDeclares an autonomous agent (image + full configuration). The mixin alternative enriches an existing agent without defining a new one.
namewe-are-legionMachine identifier used by sbx to reference the kit (e.g. sbx create --kit ./we-are-legion). No spaces or uppercase.
displayNameBill [We are legion]Name displayed in interfaces (CLI, UI).
descriptionBill, a first generation clone of BobDescription text.

agent

yaml
agent:
  image: docker.io/k33g/sbx-bill:0.0.0

image: the base Docker image the kit is applied on.

  • Unlike a Dockerfile where the image is built once (docker build), here the image is a ready-to-use base that the kit enriches at sandbox creation time via commands.install.
  • The image must already contain the minimum system dependencies (Ubuntu, apt, curl, wget, code-server). Business tools (Go, Node.js, extensions) are installed by the kit commands.

environment

yaml
environment:
  variables:
    GOROOT: /usr/local/go
    GOPATH: /home/agent/go

These variables are injected as container environment variables at startup, equivalent to ENV instructions in a Dockerfile.

VariableValueWhy
GOROOT/usr/local/goPoints to the Go installation directory. That's where wget + tar drops the official archive. Conventional value for manual Go installations.
GOPATH/home/agent/goGo user workspace: holds downloaded modules (pkg/) and installed binaries (bin/). Placed in the agent user's home (uid 1000) to avoid permission issues.

Important: these variables don't modify PATH automatically. PATH is managed separately via /etc/sandbox-persistent.sh (see install section).

commands

commands.install

install commands run once, at sandbox creation (sbx create). They're the equivalent of RUN instructions in a Dockerfile, but applied to the base image at runtime rather than build time.

Installing Go
yaml
- command: "wget https://golang.org/dl/go1.26.2.linux-arm64.tar.gz -O /tmp/go.tar.gz && tar -xf /tmp/go.tar.gz -C /tmp && mv /tmp/go /usr/local && rm /tmp/go.tar.gz"
    user: "0"
    description: Install Go
  • user: "0": root is required to write to /usr/local.
  • Architecture: arm64 (adjust for your target)
Persisting Go in PATH
yaml
- command: "echo 'export PATH=/usr/local/go/bin:/home/agent/go/bin:$PATH' >> /etc/sandbox-persistent.sh"
    user: "0"
    description: Persist Go PATH
  • Why not use environment.variables for PATH: this section injects static values. Overwriting PATH entirely would risk erasing existing system paths.
  • /etc/sandbox-persistent.sh: file sourced before each bash command in the sandbox. Adding export PATH=... here guarantees that subsequent install commands — and all sessions — can see the Go binaries.
  • Order matters: this command must come after Go is installed and before any commands that use go (like installing Go tools).
Installing Node.js
yaml
- command: "apt-get update && apt-get install -y ca-certificates curl gnupg && curl -fsSL https://deb.nodesource.com/setup_25.x | bash - && apt-get install -y nodejs"
    user: "0"
    description: Install Node.js
  • Why NodeSource and not apt-get install nodejs directly: Ubuntu repositories ship an old version of Node.js (often 18 LTS or older). NodeSource maintains official repositories for recent LTS versions.
  • setup_25.x: installs Node.js 25.
  • ca-certificates curl gnupg: prerequisites needed to verify the setup script's signature and communicate over HTTPS.
  • user: "0": modifying the package system requires root.
Installing Go tools
yaml
- command: "PATH=/usr/local/go/bin:$PATH GOPATH=/home/agent/go go install golang.org/x/tools/gopls@latest && go install github.com/ramya-rao-a/go-outline@latest"
    user: "1000"
    description: Install Go tools
  • Why prefix PATH= and GOPATH=: environment variables injected via environment.variables and sandbox-persistent.sh aren't necessarily available at this stage of install command execution. Prefixing the variables directly in the command guarantees their presence regardless of the runner implementation.
  • user: "1000": the agent user. Go binaries (gopls, etc.) are installed in $GOPATH/bin which belongs to this user. Installing as root would create files with the wrong permissions.
  • gopls: Go language server, essential for autocompletion and navigation in code-server.
  • go-outline: provides Go file structure to the VS Code/code-server extension.
  • @latest: always get the latest compatible version. For a reproducible environment, replace with a specific tag.
Installing code-server extensions
yaml
- command: |
    mkdir -p /tmp/vsix
    curl -fsSL -o /tmp/vsix/material-icons.vsix \
        https://open-vsx.org/api/PKief/material-icon-theme/5.33.1/file/PKief.material-icon-theme-5.33.1.vsix
    curl -fsSL -o /tmp/vsix/material-product-icons.vsix \
        https://open-vsx.org/api/PKief/material-product-icons/1.7.1/file/PKief.material-product-icons-1.7.1.vsix
    curl -fsSL -o /tmp/vsix/go-ext.vsix \
        https://open-vsx.org/api/golang/Go/0.52.2/file/golang.Go-0.52.2.vsix
    code-server \
        --install-extension /tmp/vsix/material-icons.vsix \
        --install-extension /tmp/vsix/material-product-icons.vsix \
        --install-extension /tmp/vsix/go-ext.vsix
    rm -rf /tmp/vsix
    user: "1000"
    description: Install code-server extensions
  • user: "1000": extensions are installed in the agent user's profile. Installing as root would drop them in /root/.local and they wouldn't be visible to agent.
ExtensionRole
material-icon-themeFile icons in the explorer (PKief theme)
material-product-iconscode-server UI icons
golang.GoFull Go support: autocompletion, formatting, debugging, linting
commands.startup

startup commands run on every sandbox start. They're used to launch background processes or initialize the session environment.

yaml
startup:
  - command: ["sh", "-c", "code-server --auth none --bind-addr 0.0.0.0:8080 \"${SBX_PROJECT_DIR:-${PWD:-$HOME}}\" > /tmp/code-server.log 2>&1"]

    user: "1000"
    background: true
    description: Start code-server on port 8080
  • Array format ["sh", "-c", "..."]: startup commands can be arrays.
  • --auth none: disables password authentication. The sandbox is already network-isolated, so this extra layer adds no value and would complicate access.
  • --bind-addr 0.0.0.0:8080: listens on all network interfaces, not just localhost. Required for the port to be accessible from the host via sbx ports.
  • ${SBX_PROJECT_DIR:-${PWD:-$HOME}}: opens code-server on the project directory if defined, otherwise the current directory, otherwise home. This gets the file explorer pointing at the right folder from the start.
  • > /tmp/code-server.log 2>&1: redirects stdout and stderr to a log file. Without this, code-server output would pollute the agent's terminal or potentially block the process.
  • background: true: the kit automatically wraps the command with sh -c ... & to detach it. Do not manually add & in the command — that would create a double detachment (... & &).
  • user: "1000": code-server needs to run as agent to access its profile, extensions, and settings.

Launching our new sbx sandbox with the kit

This time, the commands to launch our web development environment are much simpler:

bash
sbx create --kit ./kit we-are-legion .

Then we can expose the code-server port to access it from the host with sbx ports:

bash
current_dir=$(basename "$PWD")
published_port=6060

# we-are-legion-${current_dir} is the name of the sandbox
sbx ports we-are-legion-${current_dir} --publish ${published_port}:8080/tcp
echo "http://localhost:${published_port}/?folder=$(pwd)"

And we can open our Web IDE in a browser at http://localhost:6060:

sbx
sbx

We can verify that everything is installed and working (Go, Node.js, ...) by opening a terminal in code-server:

sbx
sbx

And finally launch our docker-agent:

sbx
sbx

Conclusion

We now have everything we need to spin up secure on-demand web development environments with a code agent TUI using a local language model via Docker Model Runner, all inside an sbx sandbox that's fully customizable through a kit.

The source code used in this blog post is available here: we-are-legion/bill.

sbx Kits are a very powerful feature and I've barely scratched the surface. I encourage you to check out the official documentation: Customizing sandboxes with Kits.

Written by

0 Comments

No comments yet. Be the first to comment!

Copyright © 2026k33g's BlogPowered by Writizzy