Claude CLI Runtime in Docker

This is the pattern I would use if OpenClaw is already running in Docker and I want Claude in the loop without introducing a separate Anthropic API key.

The important detail is that this setup does not talk to Anthropic through a normal API-key provider path. Instead, OpenClaw uses the claude-cli runtime, and that runtime reuses the Claude Code login that already exists on the host.

That makes it a good fit for a personal deployment where Claude Code is already authenticated and the goal is to keep the setup simple.

It is not the pattern I would choose for shared team infrastructure, unattended production automation, or anything that depends on centrally managed service credentials.

The Short Version

I think this pattern is reasonable when the deployment is personal, already containerized, and the host is already logged into Claude Code.

I do not think it should be the default answer for “how do I run Claude in OpenClaw?”

The main reason is that this is really a runtime delegation trick, not a clean provider integration. It works because OpenClaw can call the CLI, and the CLI can already authenticate. That is useful, but it is a different operational shape from a standard API-backed provider.

When This Pattern Makes Sense

I would use this approach when all of the following are true:

  1. OpenClaw already runs in Docker.
  2. The host already has Claude Code authenticated.
  3. I want Claude models inside OpenClaw without managing a separate Anthropic API key.
  4. I can tolerate some extra latency from CLI startup.

If those conditions are not true, I would rather use a standard provider configuration with API keys and treat Claude CLI as a local convenience, not as the main runtime boundary.

Why I Would Use It Anyway

For a personal deployment, the appeal is straightforward:

  1. The login already exists.
  2. The image change is small.
  3. The OpenClaw config change is small.
  4. I still get a fallback path if I configure one.

That is the real value here. Not elegance. Just a decent trade when the surrounding environment is already personal and hands-on.

What Actually Changes

At a high level, the integration is small:

  1. Install Claude CLI in the Docker image.
  2. Mount the host’s ~/.claude directory into the container as read-only.
  3. Point the relevant OpenClaw agents at agentRuntime: { id: "claude-cli" }.
  4. Keep a fallback model if I do not want the whole agent path to depend on Claude CLI being available.

The shape looks like this:

Host
|- ~/.claude/              <- Claude Code OAuth state
'- ~/.openclaw/            <- OpenClaw config

Docker container
|- /usr/local/bin/claude   <- Claude CLI installed in image
|- /home/node/.claude      <- read-only mount from host
'- /home/node/.openclaw    <- normal OpenClaw config mount

The Minimum Shape

I would install the CLI in the image itself, not through an ad-hoc volume or a manual post-start step.

That keeps rebuilds reproducible and avoids the usual “it worked on the running container, but disappeared after the next deploy” problem.

Minimal image change:

ARG OPENCLAW_BASE_IMAGE=ghcr.io/openclaw/openclaw:latest
FROM ${OPENCLAW_BASE_IMAGE}

USER root

RUN npm install -g @anthropic-ai/claude-code

USER node

That is enough as long as the resulting claude binary ends up on the runtime PATH for the node user.

Minimal compose change:

services:
  openclaw-gateway:
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - /home/openclaw/.claude:/home/node/.claude:ro

  openclaw-cli:
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - /home/openclaw/.claude:/home/node/.claude:ro

The :ro part matters. The container should be able to read the OAuth state, not rewrite it.

That directory contains credentials. I would treat it like any other secret-bearing mount:

  1. Do not commit it.
  2. Do not dump it into logs.
  3. Do not make it writable from the container unless there is a very specific reason.

Minimal OpenClaw change:

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "anthropic/claude-sonnet-4-6",
        "fallbacks": ["google/gemini-3.1-flash-lite-preview"]
      },
      "agentRuntime": { "id": "claude-cli" }
    },
    "list": [
      {
        "id": "main",
        "default": true,
        "workspace": "/home/node/.openclaw/workspace",
        "model": {
          "primary": "anthropic/claude-sonnet-4-6",
          "fallbacks": ["google/gemini-3.1-flash-lite-preview"]
        },
        "agentRuntime": { "id": "claude-cli" }
      }
    ]
  }
}

If I had specialized agents, I would set the runtime per agent where needed instead of assuming every agent should inherit the same provider path forever.

One practical note from real usage: I would validate the actual response-time tradeoff before assuming the smaller Claude model is automatically the better runtime choice. In one setup, Haiku with adaptive thinking was slow enough that Sonnet was the cleaner default.

The Tradeoffs That Actually Matter

I think there are four of them.

1. The trust boundary is personal

This runtime depends on a user login that lives on the host.

That is fine for a personal box. It is not fine if the deployment is supposed to belong to a team, a service account, or a clearly owned production system.

2. Billing and ownership are less clean

With a normal API-key provider, ownership and billing are easy to explain.

With Claude CLI, you are depending on a host-side authenticated session instead. That is workable, but it is harder to reason about as an organizational control plane.

3. Startup latency is real

This runtime is usually slower than a direct API call.

The reasons are boring but important:

  1. OpenClaw has to launch the CLI subprocess.
  2. The CLI has to load config and auth state.

I would expect the first turn of a session to feel slower than an API-backed provider. If that is already unacceptable, this is the wrong path.

4. Cost visibility is worse

This matters if I am trying to reason about usage, not just make the model answer.

In the OpenClaw docs, cost visibility is cleaner when traffic goes through normal provider pricing metadata. With OAuth-backed CLI traffic, the runtime is useful, but the cost picture is not as neat. That matters if I am trying to do real cost control later. For more on that side, see Cost Optimization Actionables.

Validation I Would Actually Do

I would not trust this until four checks pass:

docker compose build
docker compose run --rm openclaw-cli config validate
docker compose exec openclaw-gateway claude --version
docker compose exec openclaw-gateway claude auth status

What I want to see:

  1. The compose build succeeds with Claude CLI baked into the image.
  2. config validate passes.
  3. claude --version works inside the gateway container.
  4. claude auth status shows a logged-in Claude Code session.

After that, I would send one real message through the normal channel and confirm that the gateway logs show the Claude model path I expect.

If the deployment has fallbacks configured, I would also simulate one failure case and confirm that the agent really does fall back instead of just stalling.

What OpenClaw Is Really Doing

When OpenClaw handles a message through the Claude CLI runtime, it spawns the CLI as a subprocess.

The exact invocation can vary, but the important shape is this:

claude -p \
  --output-format stream-json \
  --include-partial-messages \
  --model claude-sonnet-4-6 \
  --input-format stream-json

The process reads the mounted Claude credentials and uses that authenticated session to talk to Anthropic.

That is the whole trick: OpenClaw is not magically gaining native Claude access. It is delegating model execution to the CLI runtime that already knows how to authenticate.

Operational Notes

Token refresh follows the host login

If the host-side Claude Code login refreshes its OAuth state, the container sees the updated files through the bind mount.

That is convenient, but it also means the health of this runtime depends on a personal login remaining valid on the host.

Fallbacks are worth keeping

If I care about graceful degradation, I would keep a fallback provider configured.

That gives the deployment a way to keep answering if Claude CLI breaks, the login expires, or the runtime stops behaving the way I expect.

When I Would Not Use This

I would avoid this pattern in three cases:

  1. A team or shared deployment where a personal Claude Code login is the wrong trust boundary.
  2. An environment that needs clear service-account style ownership and billing.
  3. Any workflow where startup latency is already a problem.

In those cases, a normal provider integration is the cleaner long-term choice.

If I were writing guidance for a team instead of for my own box, I would lead with the normal provider path and only mention this pattern as a deliberate exception.