The automatic token is rejected by the package registry regardless of the permissions the job requests, so the push uses a personal access token scoped to package read and write. The job no longer asks for packages: write, since nothing it does with the automatic token touches the registry.
claude-sbx
A Docker Sandboxes template carrying Malcolm's Claude Code configuration — plugins,
skills, hooks and memory files — published to the Gitea container registry at
git.mroberts.dev.
Consumed by the ai:sbx mise task in
mroberts/ai-sandbox via
AI_SBX_TEMPLATE.
docs/HANDOFF.md is the original brief. Where the two disagree, this file is current:
several of the brief's assumptions turned out to be wrong once tested.
Status: blocked upstream
sbx v0.37.0 and v0.37.1 cannot consume custom templates at all. Every layer
stacked on the base image is silently dropped — the sandbox boots with base content
only, sbx create exits 0, and sbx inspect reports the correct image. Verified here
across all three delivery paths: sbx template load from a tar, sbx template save
snapshots, and a registry pull.
This is docker/sbx-releases#366 —
the erofs snapshotter stopped building the merged fsmeta.erofs at the top of the
chain. Confirmed by others on macOS, Windows, Ubuntu and WSL2, across Homebrew,
winget, apt and template load. v0.35.0 is unaffected, and templates saved under
0.37.1 materialize correctly when launched under 0.35.0.
Until it is fixed, this image builds and pushes correctly but produces a sandbox with
none of its contents. Use mise run ai:sbx -- setup instead — it installs the same
configuration and plugins into a stock sandbox at runtime, and works on 0.37.x today.
What the image contains
| Path | Source |
|---|---|
claude/CLAUDE.md, claude/AGENTS.md |
~/.claude, verbatim |
claude/hooks, claude/skills |
~/.claude, verbatim |
plugins.json |
generated from ~/.claude/plugins/known_marketplaces.json and settings.json |
| mise | https://mise.run, with shims on PATH via .bashrc |
Credentials, transcripts, history.jsonl, projects/, file-history/ and
session-env/ are never copied. ~/.claude/plugins/ is not copied either — the build
reinstalls plugins from the manifest, which keeps the repository small and the image
reproducible from source without any access to the host.
agents/ and commands/ are absent because both are empty on the host.
Building
docker build -t git.mroberts.dev/mroberts/claude-sbx:v1 .
Requires no access to ~/.claude. The build installs 10 marketplaces and 17 plugins
and fails if the resulting enabled-plugin count does not match the manifest.
Pushing
docker push fails against this registry: git.mroberts.dev is behind Cloudflare,
which rejects request bodies over 100 MB, and the base image has a 325 MB compressed
layer. Push chunked instead:
mise use -g "github:regclient/regclient[exe=regctl,matching=regctl-linux-amd64]"
regctl registry set git.mroberts.dev --blob-chunk 50000000 --blob-max 50000000
docker save git.mroberts.dev/mroberts/claude-sbx:v1 -o image.tar
regctl image import git.mroberts.dev/mroberts/claude-sbx:v1 image.tar
Plain ubi:regclient/regclient installs regbot rather than regctl; the matching
filter above picks the right asset. .gitea/workflows/build.yml does the same on tag
pushes and on main, authenticating with the automatic GITEA_TOKEN.
Pulls are unaffected by the Cloudflare limit, which caps uploads only.
Consuming
# ~/.config/mise/config.toml
[env]
AI_SBX_TEMPLATE = "git.mroberts.dev/mroberts/claude-sbx:v1"
The sandbox's Docker daemon pulls from the registry directly and does not share the host image store, so a local build is invisible to it.
Notes from building this
- Marketplace sources are already portable. The brief called for rewriting SSH
remotes to HTTPS. Unnecessary:
known_marketplaces.jsonrecords marketplaces as{source: github, repo: owner/name}or an HTTPS git URL, never as the SSH remote the checkout happens to use. claude-plugins-officialis not built in. A fresh sandbox knows no marketplaces at all, so it is added explicitly like any other, fromanthropics/claude-plugins-official.- Plugin installs need no authentication, but a marketplace on a non-GitHub host
needs a network policy rule —
git.mroberts.devis denied by default. - Baked plugin enablement does not survive. sbx recreates
~/.claude/settings.jsonat sandbox creation, droppingenabledPluginswhile leaving the plugin files. Re-enabling against a baked image costs ~1.3s per plugin because the marketplace clones are already present;ai:sbx configdoes this. ~/.claude/skillsis a mount point inside the sandbox, backed by sbx's shared skills store. It cannot be replaced by a copy; seed it withsbx skills import.