I'm a developer & content creator, based in Ghent

Docker sandbox with Claude Code Max plan

Gist link

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-project

This 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_TOKEN environment variable to our token
  • Update ~/.claude.json to have hasCompletedOnboarding: 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 agent

Getting 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 🎉

Subscribe to my monthly newsletter

No spam, no sharing to third party. Only you and me.