If you want to run claude-oauth-proxy with containers, Docker Compose is the supported path in this repository.
Use the example in examples/docker-compose/docker-compose.yml as your starting point.
The example defaults to the stable latest image tag. You can switch to another channel in .env:
CLAUDE_OAUTH_PROXY_IMAGE_TAG=latest
Available image channels:
latestor a release number like1.2.3for published releasesnightlyfor the latest build frommaindevelopfor the latest build from a non-mainbranch pipeline
Copy the example files into your own deployment directory:
mkdir -p claude-oauth-proxy-compose
cp examples/docker-compose/docker-compose.yml claude-oauth-proxy-compose/
cp examples/docker-compose/.env.example claude-oauth-proxy-compose/.env
cd claude-oauth-proxy-compose
mkdir -p runtimeRun a one-time headless login inside the container:
docker compose run --rm claude-oauth-proxy login --no-browserThat command prints a Claude login URL, prompts for the pasted ?code=... value, and writes tokens.json into the mounted ./runtime directory.
Start the proxy:
docker compose up -dThe proxy then listens on:
http://127.0.0.1:9999
If you already have a working host login at:
~/.config/claude-oauth-proxy/tokens.json
you can mount that existing directory instead of bootstrapping inside Compose.
Replace the example volume with:
volumes:
- ${HOME}/.config/claude-oauth-proxy:/configThe container then reuses your existing Claude session and skips the login flow on startup.
If you use the Claude CLI (claude) and have already authenticated, you can reuse those credentials directly. The proxy understands the Claude CLI's credential format (~/.claude/.credentials.json) and converts it automatically.
Mount the file read-only as a seed, and let the proxy write refreshed tokens to a separate volume:
services:
claude-oauth-proxy:
image: ghcr.io/bonztm/claude-oauth-proxy:${CLAUDE_OAUTH_PROXY_IMAGE_TAG:-latest}
container_name: claude-oauth-proxy
restart: unless-stopped
command:
- serve
ports:
- "9999:9999"
environment:
CLAUDE_OAUTH_PROXY_API_KEY: ${CLAUDE_OAUTH_PROXY_API_KEY:-sk-proxy-local-key}
CLAUDE_OAUTH_PROXY_SEED_FILE: /config/credentials.json
volumes:
- ~/.claude/.credentials.json:/config/credentials.json:ro
- claude-oauth-data:/var/lib/claude-oauth-proxy
volumes:
claude-oauth-data:How this works:
- on first start, the proxy reads your Claude CLI credentials from the read-only seed file
- after the first token refresh, refreshed tokens are saved to
/var/lib/claude-oauth-proxy/tokens.jsonon the named volume - your host
~/.claude/.credentials.jsonis never modified - refreshed tokens persist across container restarts via the named volume
- if the volume is removed, the proxy falls back to the seed again
The example Compose setup expects a local API key. By default the example uses:
sk-proxy-local-key
If you want to pin a specific release instead of following latest, set for example:
CLAUDE_OAUTH_PROXY_IMAGE_TAG=1.2.3
Point OpenAI-compatible clients at:
export OPENAI_BASE_URL="http://127.0.0.1:9999/v1"
export OPENAI_API_KEY="sk-proxy-local-key"Useful checks after startup:
curl -sf http://127.0.0.1:9999/healthz
curl -sf http://127.0.0.1:9999/readyzTo enable theoretical cost tracking, add the environment variable to your Compose file:
environment:
CLAUDE_OAUTH_PROXY_COST_TRACKING: "true"The proxy fetches model pricing from the OpenRouter API on first cost lookup and attaches a cost breakdown to responses where pricing is available. See the configuration reference for details.
- keep the mounted token directory writable so refreshes can be saved
- do not bake live OAuth tokens into the image
- treat the mounted runtime directory as sensitive data
- when cost tracking is enabled, the container needs outbound HTTPS access to
openrouter.aibefore the first priced request
See also: README · Kubernetes / Helm deployment · Configuration reference · Prompt caching