Docker sandbox with Claude Code Max plan
Anthropic unfortunately didn't make it easy for us developers to set up Claude Code with a Max plan in a headless way. The first time you open Claude Code you'll always be prompted to log in, unless you use a pay-per-token API key.
This is especially difficult when you set up Docker sandboxes to isolate Claude Code in a small VM using:
docker sandbox run claude ~/my-projectThis is a great feature, but every time you open up a new sandbox (eg. in a new worktree), you'd get prompted to authenticate, which is not ideal.
In anticipation of an easier way for us to use our Max plan in sandboxes, I figured out a quick & dirty workaround.
The hack
After some investigation I found out Claude is able to detect an environment variable called CLAUDE_CODE_OAUTH_TOKEN, but only when hasCompletedOnboarding is set to true in ~/.claude.json
So the idea is we do these steps:
- Set the
CLAUDE_CODE_OAUTH_TOKENenvironment variable to our token - Update
~/.claude.jsonto havehasCompletedOnboarding: true - Run Claude
I didn't want to create custom Docker Sandbox images (as the docs advise against this), so I ended up swizzling the Claude binary to modify the ~/.claude.json file right before we run Claude.
The system will still see claude as a binary, but this is actually a shell script that calls claude-real after setting the correct flag for us - just in time.
Dockerfile
We can use custom templates for your sandbox, and with a small hack we can swizzle the Claude binary to be wrapped with a shell script that runs setup logic before handing off to the real binary.
By wrapping the binary at a system path we can inject configuration just-in-time.
FROM docker/sandbox-templates:claude-code
USER root
# ... Install extra dependencies
# Configure JIT injection of the 'hasCompletedOnboarding' flag so Claude can pick up the env var.
RUN CLAUDE_PATH=$(which claude) \
&& mv "$CLAUDE_PATH" "${CLAUDE_PATH}.real" \
&& printf '#!/bin/bash\ntest -f /home/agent/.claude.json || echo "{}" > /home/agent/.claude.json\njq ".hasCompletedOnboarding = true" /home/agent/.claude.json > /tmp/.claude.json && mv /tmp/.claude.json /home/agent/.claude.json\nexec "%s.real" "$@"\n' "$CLAUDE_PATH" > "$CLAUDE_PATH" \
&& chmod +x "$CLAUDE_PATH"
USER agentGetting a token
On your host, run:
claude setup-token
Follow the instructions and export this environment variable in your ~/.zshrc
export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-...
Restart Docker desktop to make sure it loads the environment variables.
Build the sandbox image
docker build -t custom-sandbox .
Run the sandbox, this will now be authenticated
docker sandbox run --load-local-template -t custom-sandbox:latest claude .
If all went well, Claude should open in the sandbox and not prompt you to authenticate 🎉
No spam, no sharing to third party. Only you and me.