Open account

ProxyCodeClaudePrice claude vs Our Uncensored Proxy API

Claude Code Proxy in Docker: Compose File and Settings

A claude code proxy docker setup puts the translation layer in a container, so Claude Code on your host can talk to an OpenAI-compatible API without installing Python tools locally. Below is a working Compose file, the config it mounts and the settings that break most often.

Last updated

Isometric glass shipping container holding a glowing translation core between two cables, picturing a Dockerised proxy

Summary

The layout

Create a project folder with three files:

claude-proxy/
  docker-compose.yml
  config.yaml
  .env

The container runs a gateway that accepts Anthropic-style calls from Claude Code and forwards them in OpenAI format to https://api.proxycodeclaude.com/v1. The API serves one model, ID uncensored. Get a key on the API key page; new accounts can claim $0.50 of trial credit, which is plenty to test this setup.

The environment file

Compose reads .env from the project folder automatically. Put both secrets there:

BACKEND_KEY=YOUR_KEY
LITELLM_MASTER_KEY=sk-local-change-me

BACKEND_KEY is the key for the API. LITELLM_MASTER_KEY is a password you invent; Claude Code will present it to the proxy. Add .env to .gitignore. If the real key ever leaks, reissue it on the key page at any time. The old key stops working at once, and you only have to edit this one line and restart.

The proxy config

Save this as config.yaml. It is mounted read-only into the container:

model_list:
  - model_name: uncensored
    litellm_params:
      model: openai/uncensored
      api_base: https://api.proxycodeclaude.com/v1
      api_key: os.environ/BACKEND_KEY
      max_parallel_requests: 8

litellm_settings:
  drop_params: true
  num_retries: 2

The master key is not in this file. LiteLLM is documented to read the LITELLM_MASTER_KEY environment variable on its own; check the current LiteLLM docs if your version behaves differently. For a trial key, which allows 2 concurrent requests and 60 per minute, change max_parallel_requests to 2.

The Compose file

services:
  claude-proxy:
    image: ghcr.io/berriai/litellm:main-stable
    command: ["--config", "/app/config.yaml", "--port", "4000"]
    ports:
      - "127.0.0.1:4000:4000"
    volumes:
      - ./config.yaml:/app/config.yaml:ro
    environment:
      BACKEND_KEY: ${BACKEND_KEY}
      LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
    restart: unless-stopped
    healthcheck:
      test:
        - CMD
        - python
        - -c
        - "import urllib.request; urllib.request.urlopen('http://localhost:4000/health/liveliness')"
      interval: 30s
      timeout: 5s
      retries: 3

A few choices worth explaining:

Start it and test it

docker compose up -d
docker compose logs -f claude-proxy

When the log shows the server listening, send a test message using the Anthropic-style endpoint, which is what Claude Code will call:

curl http://localhost:4000/v1/messages \
  -H "x-api-key: sk-local-change-me" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"uncensored","max_tokens":100,"messages":[{"role":"user","content":"Reply with ready."}]}'

Then launch Claude Code on the host with the proxy as its endpoint:

export ANTHROPIC_BASE_URL="http://localhost:4000"
export ANTHROPIC_AUTH_TOKEN="sk-local-change-me"
export ANTHROPIC_MODEL="uncensored"
claude

Claude Code also has a setting for a smaller background model. Its variable name has changed over versions, so set it to uncensored using the name in the current docs, or the proxy will see a model name it does not list.

Running Claude Code inside Docker too

If you run Claude Code in a dev container, it must reach the proxy over the Compose network. Put both services in the same Compose project and use the service name as host:

export ANTHROPIC_BASE_URL="http://claude-proxy:4000"

Using localhost there fails, because inside the dev container it refers to the dev container itself. When the proxy runs on the host and the client runs in a container, use host.docker.internal on Docker Desktop, or add extra_hosts: ["host.docker.internal:host-gateway"] to the client service on Linux. Do not remove the 127.0.0.1 binding to make this work; the Compose network route is safer.

Confirm the container is healthy

The health check in the Compose file runs on its own, but you should know how to read it. Run docker compose ps a minute after start: the status column should show the service as healthy, not just running. If it stays in a starting state, the probe is failing, and the log will usually say why. The usual causes are a typo in config.yaml, an empty BACKEND_KEY, or a changed health endpoint name in your image version.

The container does no model work itself. The model runs on the API side, so the proxy needs very little memory or CPU, and a small laptop or a cheap server is enough. What it does need is a clean network path to https://api.proxycodeclaude.com/v1. If the container cannot resolve or reach that host, requests fail with an upstream error even though the container looks healthy. Test it from inside:

docker compose exec claude-proxy python -c "import urllib.request as u; print(u.urlopen('https://api.proxycodeclaude.com/v1/models').status)"

That call has no key, so a 401 error from the API is the expected answer and proves the route works. A timeout or a name-resolution error points at Docker networking, a corporate proxy or a firewall rule, not at LiteLLM or at your key.

One more habit worth having: keep a second, working copy of config.yaml and .env.example with the key blanked out. When a change breaks the setup, you can diff against a known good file instead of guessing.

Updating, logs and cleanup

Changing the key or the config is a restart, not a rebuild. After editing .env or config.yaml, run:

docker compose up -d --force-recreate
docker compose logs --tail 50 claude-proxy

To move to a newer image, pull it first and then recreate the container. Do this between work sessions, not in the middle of one, because a restart drops any stream in progress. Keep the log level at its default on shared machines; verbose debug output from a gateway may include request content, and prompts are better kept out of log files.

When you are done with the setup, docker compose down stops and removes the container and leaves your three files untouched. Because the key lives only in .env, you can delete that file and be sure nothing secret remains in the project. The API keeps your balance on the account, so nothing is lost when the container goes away.

Troubleshooting table

SymptomLikely causeFix
401 from the proxyToken does not match the master keyAlign ANTHROPIC_AUTH_TOKEN with LITELLM_MASTER_KEY, then restart the container
Error mentioning the upstreamEmpty or wrong BACKEND_KEYCheck .env; test the key with a direct call to https://api.proxycodeclaude.com/v1/chat/completions
429 with code concurrencyMore than 8 requests at once on a full key (2 on trial)Lower max_parallel_requests; a stream holds its slot until it ends
429 with code rate_limitedOver 300 requests per minute (60 on trial)Slow down or wait
429 with Retry-After: 5Temporary overloadRetry after five seconds; num_retries helps
Config change has no effectContainer still has the old file loadeddocker compose restart claude-proxy
Replies arrive in one blockA reverse proxy in front is bufferingTurn buffering off for the route

Remember what the backend is: one open-weight model, not Claude. Tool use and long agent loops behave differently, and the context window is 100,000 tokens. Costs are $0.15 per 1M input tokens and $0.50 per 1M output tokens; the figures in Price claude vs Our Uncensored Proxy API and on the pricing page show what that means per session. The documentation lists every request field.

Common questions

Do I need Docker to proxy Claude Code?

No. You can run the same gateway with pip on the host. Docker keeps Python dependencies out of your system and makes the setup easy to repeat.

Where should the API key go in a Compose setup?

In a .env file next to docker-compose.yml, passed to the container as an environment variable and read by the config as os.environ/BACKEND_KEY. Keep the file out of git.

Why can my dev container not reach localhost:4000?

Inside a container localhost is the container itself. Use the Compose service name, such as claude-proxy, or host.docker.internal if the proxy runs on the host.

Can the container run more than one backend?

Yes, by adding more entries to model_list. This API provides one model with the ID uncensored, so each entry would point at a different provider.

Generate your key now

Sign up, copy your API key, and update the base URL. That is all you need.

Open account