A polyglot dev container (like a VS Code dev container) that runs a project
and hosts a Claude Code instance inside it, driveable from the host by
sax.
Toolchain baked in: Node 22, Go 1.22, Rust (rustup), plus git, ripgrep, fd, jq, build-essential, python3.
CI publishes a multi-arch image (amd64 + arm64/Apple Silicon) to GHCR:
docker pull ghcr.io/asctheone/cuc:latestRun it directly (mount a project + your Claude creds, keep it alive for exec):
docker run -d --name cuc-dev \
-v "$PWD:/workspace" \
-v "$HOME/.claude:/host-claude:ro" \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
ghcr.io/asctheone/cuc:latest sleep infinity
docker exec -it -u dev cuc-dev bash -lc 'cd /workspace && claude'To use the published image via compose instead of building locally, set
image: ghcr.io/asctheone/cuc:latest and drop the build: block in
docker-compose.yml.
| Question | Choice | Why |
|---|---|---|
| How to run Claude as root? | Non-root dev user by default, claude-unleashed as opt-in fallback |
claude-unleashed patches the CLI's JS source, but the current CLI (v2.1.102+) is a compiled binary — the patches no longer apply. Running as a normal user sidesteps the root check entirely and works today. |
| Where does sax live? | Host, driving docker exec |
Keeps sax's daemon + MCP server on your machine; the container stays lean. |
| Auth | Mount ~/.claude read-only, copy creds into the container; ANTHROPIC_API_KEY fallback |
No secrets baked into the image. |
⚠️ macOS auth caveat. On macOS the Claude OAuth token lives in the Keychain, not in a mountable~/.claude/.credentials.json.scripts/cuc-up.shauto-runsscripts/cuc-login-sync.sh, which extracts the Keychain token into the container and sets the account + onboarding flag (needed by the interactive TUI, not just-p). The login persists in a named volume (cuc-claude) across container removal/rebuild — re-run login-sync only to refresh an expired token, or setANTHROPIC_API_KEYin.envinstead.
.devcontainer/
Dockerfile polyglot image; INSTALL_UNLEASHED build arg
devcontainer.json VS Code "Reopen in Container" path
post-create.sh seeds creds + symlinks ~/.claude.json into the volume
docker-compose.yml host-driven path; named volume cuc-claude persists auth
.env.example CUC_PROJECT / INSTALL_UNLEASHED / ANTHROPIC_API_KEY
scripts/
cuc-up.sh start (+ --build) + chown volume + seed + login-sync
cuc-claude.sh docker exec a Claude instance (non-root, default)
cuc-login-sync.sh transpose host Keychain login into the container
cuc-unleashed.sh opt-in root/bypass fallback
cuc-down.sh stop
sax/
sax-claude.sh run the container's Claude as a sax session (detach/reattach)
services.example.json merge into ~/.claude/services.json
cp .env.example .env # set CUC_PROJECT + ANTHROPIC_API_KEY if needed
scripts/cuc-up.sh # build image, start cuc-dev, seed creds
scripts/cuc-claude.sh # launch Claude inside the container
scripts/cuc-down.sh # stop when doneMount a different project (not this repo) by setting CUC_PROJECT to its host
path in .env — it lands at /workspace inside the container.
Open this folder in VS Code → Reopen in Container. devcontainer.json
builds the same image, mounts your creds, and runs post-create.sh.
sax runs the container's Claude as a first-class host session — so you get detach/reattach and the full scripting + MCP surface:
sax/sax-claude.sh # create + attach "cuc-claude"
sax --tail cuc-claude 30 # peek at output
sax --send cuc-claude $'/help\n' # drive itFrom a Claude agent on the host, the same via MCP:
sax_exec(name="cuc-claude", command="docker exec -it -u dev cuc-dev bash -lc 'cd /workspace && claude'")
sax_tail(name="cuc-claude", lines=30)
Merge sax/services.example.json into ~/.claude/services.json so "start the
cuc claude instance" resolves without rediscovery.
Only if you specifically need root and are on a legacy/pinned JS CLI:
INSTALL_UNLEASHED=true docker compose build
scripts/cuc-unleashed.shExpect this to fail on the current compiled CLI — that's why non-root is the default.