---
title: 'Claude Agent SDK Hosting: How to Deploy It in Production'
published date: '2026-10-09'
author: 'Illiana Reed'
title-image: 'https://camelai.com/blog/assets/images/covers/how-to-host-claude-agent-sdk.png'
---

**TL;DR:** Claude Agent SDK hosting means running long-lived processes, not a stateless API. Each session is a `claude` CLI subprocess with its own shell, working directory and transcript on local disk. Run it in a container, starting at 1 GiB RAM, 5 GiB disk and 1 CPU for each concurrent session. Mirror transcripts to a `SessionStore`, isolate tenants, and keep credentials behind an egress proxy. Docker on a VM is the simplest host. Sandbox providers fit per-task isolation, and Kubernetes with gVisor or Kata fits platform teams. Claude Managed Agents fits teams that would rather have Anthropic run the loop.

---

This guide follows Anthropic's [Hosting the Agent SDK](https://code.claude.com/docs/en/agent-sdk/hosting) and [secure deployment](https://code.claude.com/docs/en/agent-sdk/secure-deployment) docs, as checked in October 2026, and adds the details that trip teams up when they deploy the Claude Agent SDK. Every code block is a starting point, not a tested production config. If you haven't yet decided whether you want a ready-made harness at all, start with [how to build an AI agent](https://camelai.com/blog/how-to-build-an-ai-agent).

## How the Agent SDK runs

When your code calls `query()`, the SDK starts the `claude` CLI and talks to it over stdio. One session is one subprocess, which owns a shell, a working directory and a JSONL transcript. The subprocess never listens on the network. Your app exposes HTTP or WebSocket and calls the SDK internally.

By default, three kinds of state live on the container's disk. A Docker container's writable layer survives stopping and starting that same container, but is [lost when the container is destroyed](https://docs.docker.com/engine/storage/). Replacements, scale-downs and moves to another node therefore need explicit persistence:

| State | Default location | What keeps it |
|---|---|---|
| Session transcripts | `~/.claude/projects/`, or `projects/` under `CLAUDE_CONFIG_DIR` | A `SessionStore` adapter |
| `CLAUDE.md` memory | `~/.claude/CLAUDE.md` and the working directory | A volume or object-store sync |
| Files the agent writes | The session's working directory | A volume or object-store sync |

## Step 1: Choose a session pattern

Anthropic describes four patterns, which differ in how long a container lives compared with the sessions it serves:

| Pattern | Container lifetime | Good for | You must add |
|---|---|---|---|
| **Ephemeral** | One container per task | Bug fixes, invoice extraction, translation | Fast cold starts and a turn cap |
| **Long-running** | Persistent containers, many SDK processes each | Chat bots, high-volume message streams | Memory for peak concurrency and sticky routing |
| **Hybrid** | Ephemeral containers that load from a `SessionStore` | Work with long idle gaps between turns | The store (required) and tuned idle timeouts |
| **Multi-agent** | Several SDK processes in one container | Agents that collaborate closely | A separate working directory and settings per agent |

For long-running servers:
- **TypeScript:** add turns to a live session with `streamInput()`, and pay subprocess start-up before traffic arrives with `startup()`.
- **Python:** hold a session open with `ClaudeSDKClient`.

## Step 2: Build a Claude Agent SDK Docker image

The container needs Python 3.10+ or Node.js 18+. Both SDKs bundle a native Claude Code binary, so the CLI doesn't need a separate Node install. Three things to know:

- **Python on Alpine gets no CLI.** `claude-agent-sdk` 0.2.163 publishes Linux wheels only for glibc (manylinux). On musl, pip installs the source distribution, which bundles no binary. Use a Debian-based image.
- **`npm ci --omit=optional` drops the TypeScript CLI.** The binary is an optional dependency. Install without that flag, or set `pathToClaudeCodeExecutable`.
- **Updating the SDK updates the CLI.** The binary is pinned to the SDK version. Anthropic suggests taking patch releases as they ship and reading the changelog before a minor release.

Before building, exclude `.env` files, credentials and other private files from the build context with `.dockerignore`; the `COPY . .` below otherwise includes them in the image.

```
# Starting point, not a tested production config.
# Debian-based (glibc) so the Python wheel's bundled CLI is used.
FROM python:3.12-slim
# Add the CLIs the agent's Bash tool needs here (git, etc.).
RUN useradd --create-home --uid 1000 agent
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt   # pins claude-agent-sdk
COPY . .
ENV CLAUDE_CONFIG_DIR=/data/claude
RUN mkdir -p /data/claude /work && chown -R agent:agent /data /work
USER agent
EXPOSE 8000
CMD ["python", "server.py"]
```

The SDK reads `ANTHROPIC_API_KEY` from its process environment and doesn't load `.env` files. From other languages, run the CLI with `-p` and `--output-format json` instead; our guide to [running Claude Code headless in Docker](https://camelai.com/blog/claude-code-headless-docker) covers that route.

## Step 3: Size hosts and route sessions

**Measure, then divide.** The 1 GiB / 5 GiB / 1 CPU figure is a floor for a fresh agent, and memory grows with session length and tool activity. Run a representative session to your target length and record its peak RSS. Then use Anthropic's formula: agents per host = (host RAM − overhead) ÷ per-session ceiling. For example, a 16 GiB host with 2 GiB of overhead and a 1.75 GiB measured peak holds 8 sessions.

**Pin sessions.** Put long-running containers behind a load balancer and use consistent hashing on the session ID, so each turn reaches the same subprocess.

**Bound every session.** Sessions have no timeout of their own:
- Set `maxTurns` (`max_turns` in Python) to cap turns.
- Set `maxBudgetUsd` (`max_budget_usd`) to stop a run at a cost limit.
- Cap each subagent with `maxTurns` in its `AgentDefinition`.
- Recycle long-lived subprocesses to contain memory growth.

**Optimize tokens first.** Anthropic puts a minimally provisioned container at roughly $0.05 per hour and says token cost typically dominates container cost by an order of magnitude or more.

## Step 4: Persist sessions with a SessionStore

**Choose the session ID yourself.** Both SDKs accept a UUID you generate (`sessionId`, `session_id`). Store it with your conversation, pass it as `sessionId` on the first turn and as `resume` afterwards.

**Mirror transcripts.** A `SessionStore` needs only `append` and `load`. Both SDK repos include reference adapters for S3, Redis and Postgres, plus a conformance suite. The adapters aren't published packages, so copy one into your project.

```
// Starting point. S3SessionStore is copied from the SDK repo's
// examples/session-stores/s3 adapter; it is not a published package.
import { query } from "@anthropic-ai/claude-agent-sdk";
import { S3Client } from "@aws-sdk/client-s3";
import { S3SessionStore } from "./S3SessionStore";

type Conversation = { id: string; sessionId: string; started: boolean };
declare function reportMirrorError(sessionId: string): void;

const sessionStore = new S3SessionStore({
  bucket: "agent-transcripts",
  prefix: "prod",
  client: new S3Client({ region: "us-east-1" }),
});

export async function runTurn(conv: Conversation, prompt: string) {
  let result;
  for await (const message of query({
    prompt,
    options: {
      cwd: `/work/${conv.id}`, // keep identical across hosts: the store key derives from it
      sessionStore,
      ...(conv.started ? { resume: conv.sessionId } : { sessionId: conv.sessionId }),
      maxTurns: 40,
      maxBudgetUsd: 5,
    },
  })) {
    if (message.type === "system" && message.subtype === "mirror_error") {
      reportMirrorError(conv.sessionId); // a transcript batch did not reach the store
    }
    if (message.type === "result") result = message;
  }
  return result;
}
```

What to plan around:
- **Transcripts only.** Sessions persist the conversation, not files, so mount a volume or sync the working directory separately.
- **Same working directory.** The store key encodes the `cwd`, so recreate the same path before resuming on a new host.
- **Best-effort writes.** If a batch still fails after up to three attempts, the SDK drops it and emits `mirror_error`. Alert on it, and deduplicate by `entry.uuid` in `append`.
- **Retention is yours.** The SDK never deletes from your store.
- **Limits throw.** `query()` throws after an `error_max_turns` or `error_max_budget_usd` result. Catch it, and resume with a higher limit if you want the run to continue.

## Step 5: Isolate tenants and set permissions

By default, the SDK reads settings and memory files from disk, which in a shared container can leak one tenant's context into another's session. For each tenant:
- pass `settingSources: []` (`setting_sources=[]` in Python);
- set `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`, because auto memory loads regardless of `settingSources`;
- give it its own `CLAUDE_CONFIG_DIR` and an explicit `cwd` on every `query()`;
- apply its own egress rules at your proxy.

Nobody is there to approve tool calls, so set permissions in code:
- **Set the mode.** If you omit `permissionMode`, a session can start in `auto` mode, which drops broad allow rules.
- **Fix the tool surface.** Pair `allowedTools` with `permissionMode: "dontAsk"` to deny calls that would otherwise need approval. Calls that need no approval, including some read-only Bash commands, can still run. `allowedTools` doesn't hide other tools; use bare tool names in `disallowedTools` to remove them.
- **Avoid `bypassPermissions`.** It approves every tool that no deny rule or hook blocks, whatever `allowedTools` says, and refuses to start as root outside a recognized sandbox.
- **Use hooks for universal checks.** A `PreToolUse` hook runs on every call.

```
// Starting point: one tenant's turn inside a shared container.
import { query } from "@anthropic-ai/claude-agent-sdk";

declare const tenantId: string;
declare const prompt: string;
const root = `/tenants/${tenantId}`;

for await (const message of query({
  prompt,
  options: {
    cwd: `${root}/work`,
    settingSources: [],          // no user, project or local settings
    permissionMode: "dontAsk",   // deny calls that would otherwise prompt
    allowedTools: ["Read", "Glob", "Grep", "Edit", "Bash(npm test *)"],
    disallowedTools: ["WebFetch", "WebSearch"],
    env: {
      ...process.env,            // in TypeScript, env replaces the subprocess environment
      CLAUDE_CONFIG_DIR: `${root}/config`,
      CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
    },
  },
})) {
  // stream to the client
}
```

These settings separate loaded configuration, but sessions running as the same OS user can still access each other's files. Give mutually untrusted tenants separate containers or stronger isolation. Claude Code's [Bash sandbox](https://code.claude.com/docs/en/sandboxing) can add shell restrictions, but its default read access is broad, and file tools, MCP servers and hooks run outside it. Anthropic's [hosting cookbook](https://github.com/anthropics/claude-cookbooks/tree/main/claude_agent_sdk/hosting) drops the `Read` tool from its hosted agent for a related reason: `Read` could reach other sessions' transcripts and the environment holding the API key.

## Step 6: Put credentials and egress behind a proxy

**Allow only what the agent needs.** That is HTTPS to `api.anthropic.com` (or your Bedrock or Google Cloud endpoint), plus your MCP servers and tool endpoints. On inbound, authenticate at a gateway that forwards only the caller's own session IDs; the cookbook server has no authentication by default.

**Keep the model key out of the container.** Point `ANTHROPIC_BASE_URL` at a proxy that adds the real key, so the container holds at most a gateway token in `ANTHROPIC_AUTH_TOKEN`. A Bash call can read anything in the agent's environment.

**Credentials for other services need a different route.** With `HTTPS_PROXY`, the proxy only sees an encrypted tunnel, so it can allowlist domains but can't inject credentials. Either run a TLS-terminating proxy with its CA certificate in the container, or wrap the service as a custom tool or MCP server that runs outside the boundary. On Node 24+, set `NODE_USE_ENV_PROXY=1` for built-in `fetch()` to honor proxy variables; older runtimes need compatible proxy support. Anthropic names Envoy, mitmproxy, Squid and LiteLLM as proxy options.

```
# Starting point adapted from Anthropic's secure-deployment guide.
docker network create --internal agent-net   # no route out except via attached proxies

docker run -d --name agent --network agent-net \
  --cap-drop ALL --security-opt no-new-privileges \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m \
  --tmpfs /home/agent:rw,nosuid,size=500m,uid=1000,gid=1000 \
  -v agent-data:/data -v agent-work:/work \
  --memory 4g --cpus 2 --pids-limit 256 \
  --user 1000:1000 \
  -e ANTHROPIC_BASE_URL=http://egress-proxy:8080 \
  -e ANTHROPIC_AUTH_TOKEN="$AGENT_GATEWAY_TOKEN" \
  -e CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
  my-agent:1.0
```

Attach your gateway and egress proxy to `agent-net`, and never mount `~/.ssh` or `~/.aws`. `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` stops background version checks and telemetry that would fail on a locked-down network.

**Harden beyond containers when needed.** Containers share the host kernel. **gVisor** (`--runtime=runsc`) intercepts syscalls in user space. It costs CPU-bound work almost nothing, but heavy open/close file I/O can run 10 to 200 times slower. **Firecracker** microVMs boot in under 125 ms and route traffic through vsock to a host proxy.

## Step 7: Wire up observability

The CLI exports OpenTelemetry itself, configured by environment variables you set at the container level:
- `CLAUDE_CODE_ENABLE_TELEMETRY=1`;
- `OTEL_METRICS_EXPORTER`, `OTEL_LOGS_EXPORTER` and `OTEL_TRACES_EXPORTER`, with `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` for traces;
- `OTEL_EXPORTER_OTLP_PROTOCOL` and `OTEL_EXPORTER_OTLP_ENDPOINT`.

In production:
- **Never use the `console` exporter.** Standard output is the SDK's message channel.
- **Surface export errors** with `CLAUDE_CODE_OTEL_DIAG_STDERR=1`. They fail silently otherwise.
- **Lower the export intervals for ephemeral containers.** Metrics export every 60 seconds and traces and logs every 5 seconds by default.
- **Name and tag.** Set `OTEL_SERVICE_NAME` per agent, and add each user and tenant through `OTEL_RESOURCE_ATTRIBUTES` (percent-encoded) for a per-user audit trail.
- **Alert on `mirror_error`,** and log each result's subtype and cost.

Prompt text and tool inputs stay out of exports unless you opt in.

## Step 8: Follow the auth and branding rules

- **API keys only.** Unless Anthropic has approved it, a product built on the SDK may not offer claude.ai login or claude.ai rate limits.
- **Providers.** You can use an Anthropic key, Amazon Bedrock (`CLAUDE_CODE_USE_BEDROCK=1`), Claude Platform on AWS (`CLAUDE_CODE_USE_ANTHROPIC_AWS=1` plus `ANTHROPIC_AWS_WORKSPACE_ID`), Google Cloud's Agent Platform (`CLAUDE_CODE_USE_VERTEX=1`) or Microsoft Foundry (`CLAUDE_CODE_USE_FOUNDRY=1`).
- **Terms.** SDK use falls under Anthropic's Commercial Terms of Service, including when it powers your customers' products.
- **Branding.** "Claude Agent" and "YourAgentName Powered by Claude" are allowed. "Claude Code", "Claude Code Agent" and Claude Code-style ASCII art are not.

## Claude Agent SDK hosting options compared

Anthropic's [hosting cookbook](https://github.com/anthropics/claude-cookbooks/blob/main/claude_agent_sdk/07_Hosting_the_agent.ipynb) deploys the same agent image three ways: local Docker, Modal, and Kubernetes with one pod per session. The hosting page suggests judging a host on who runs it, cold-start latency, persistent storage, pricing model and networking controls. Here is how the main options compare as places to run the SDK:

| Host | Who operates it | Isolation | Session length | State between runs | Best fit |
|---|---|---|---|---|---|
| Docker on a VM | You | Shared kernel; add gVisor or a VM per tenant | No cap | VM disk or volumes | Long-running, multi-agent |
| Cloud Run / Fargate | Cloud provider | Provider-managed | Cloud Run: 60 min per request, 168 h per job; Fargate: no documented cap | Ephemeral disk; use a store | Ephemeral, hybrid |
| Sandbox providers | Provider | microVM or gVisor per sandbox | Often 24 h max; some reset on resume | Snapshots, volumes, pause | Per-task or per-user sandboxes |
| Kubernetes + agent-sandbox | Your platform team | gVisor or Kata | No cap | Persistent volumes, pause/resume | Many tenants on an existing cluster |
| Claude Managed Agents | Anthropic runs the loop | Anthropic cloud or self-hosted sandbox | Long-running, resumable | Server-side | Teams that want no hosting work |

### Docker on a VM

This is the simplest option: the cookbook's first tier. You own patching, scaling and failover. Add a `SessionStore` before you add a second VM, and use `--runtime=runsc` when the agent processes untrusted content.

### Cloud Run and AWS Fargate

[Cloud Run](https://docs.cloud.google.com/run/docs/configuring/request-timeout) services cap a request at 60 minutes, and Google advises handling reconnects past 15 minutes. That suits the hybrid pattern with a store. Cloud Run jobs run for up to 168 hours, which suits ephemeral tasks. [Fargate](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/fargate-security-considerations.html) isolates each task, documents no task time limit and bills per second. It doesn't allow privileged containers, so Docker-in-Docker agents won't run there.

### Sandbox providers

| Provider | Isolation | Session length | Compute | Memory | Persistence |
|---|---|---|---|---|---|
| [Modal Sandboxes](https://modal.com/docs/guide/sandbox) | gVisor or VM runtime | 5 min default, 24 h max | $0.1413 per physical core-hour | $0.024 per GiB-hour | Volumes, filesystem snapshots |
| [E2B](https://e2b.dev/pricing) | Linux VM | 1 h Hobby, 24 h Pro | $0.0504 per vCPU-hour | $0.0162 per GiB-hour | Pause keeps files and memory |
| [Vercel Sandbox](https://vercel.com/docs/sandbox/pricing) | Firecracker microVM | 45 min Hobby, 24 h Pro | $0.128 per active-CPU hour | $0.0212 per GB-hour | Persistent sandboxes, snapshots |
| [Cloudflare Sandbox](https://developers.cloudflare.com/containers/pricing/) | microVM container | Not stated | $0.072 per active vCPU-hour | $0.009 per GiB-hour | Backups, workspace reuse |
| [Fly Sprites](https://fly.io/sprites) | Dedicated microVM | No cap published | $0.03825 per CPU-hour | $0.021875 per GB-hour | Persistent disk, checkpoints |
| [Daytona](https://www.daytona.io/pricing) | Containers or Linux VMs | Not stated | $0.0504 per vCPU-hour | $0.0162 per GiB-hour | Volumes, snapshots |

*Pricing verified October 2026. Units differ (physical core vs vCPU, active vs provisioned), so rows aren't directly comparable. Pricing and limits change often; check each vendor's pricing page before deciding.*

What sets each provider apart:
- **Modal** is the cookbook's second tier.
- **E2B** keeps paused sandboxes indefinitely.
- **Vercel**'s Active CPU meter excludes time spent waiting on model calls; memory is still billed.
- **Cloudflare**'s [Claude Code guide](https://developers.cloudflare.com/sandbox/coding-agents/) keeps the API token out of the sandbox.
- **Sprites** stop compute billing while asleep.
- **Daytona** still runs as a hosted service but moved to a private codebase in June 2026.

When camelAI gave every user a persistent Linux environment, we tried Modal, Cloudflare Containers and Sprites before [building our own container service](https://camelai.com/blog/we-tried-every-container-service-then-built-our-own). That was early 2026, and providers have shipped persistence features since.

### Kubernetes with agent-sandbox

The cookbook's third tier runs one pod per session behind a gateway. [agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox) (Apache-2.0) adds a `Sandbox` resource with stable identity, persistent storage, warm pools, pause/resume, and gVisor or Kata isolation. GKE offers it as Agent Sandbox. Choose this if you already run a cluster.

### Claude Managed Agents

This isn't a place to run `query()`. It is Anthropic's hosted harness, configured through its API, so moving there means re-expressing your agent. Tools run in Anthropic's cloud sandbox or in a [self-hosted sandbox](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes) where your worker executes them. It costs $0.08 per running session-hour plus tokens. It is in beta, runs Claude models only, and isn't currently eligible for ZDR or HIPAA BAA coverage. See our [Claude Managed Agents alternatives](https://camelai.com/blog/claude-managed-agents-alternatives).

### How to choose

- **One team, modest traffic:** Docker on a VM, plus a `SessionStore` from day one.
- **Bursty one-off tasks:** Cloud Run jobs or a sandbox provider.
- **Per-user workspaces that sleep between visits:** E2B, Sprites or Vercel persistent sandboxes, plus a store.
- **Many tenants on infrastructure you already run:** Kubernetes with agent-sandbox.
- **No hosting at all, Claude models only:** Claude Managed Agents.

## Do you need the SDK at all?

The Agent SDK gives you Claude Code's harness: file and shell tools, subagents, hooks and skills. Everything above is what it costs to run. That trade is worth it when the agent's job is shell and file work, such as running tests, using git, calling CLIs or installing packages.

Many product agents mostly call your own APIs instead. For those, a hosted runtime such as camelRun is a different, simpler architecture. It is not a host for the SDK: no `query()` call or `claude` subprocess runs there. You define each agent's model, instructions, tools and channels. Tools are functions in your TypeScript or Python code, or services over HTTP, MCP or OpenAPI, and they run with your credentials. camelRun runs the loop, keeps each agent's history and files, and wakes agents for messages, schedules, channels, webhooks and human approvals. Agents are keyed by your own IDs.

```
import { Agents, schema, tool } from "@camelai/run";

const agents = new Agents(); // reads CAMELAI_API_KEY

// A tool is an ordinary function: it runs here, in your process.
const weather = tool({
  description: "Today's weather in a city",
  input: schema.Object({ city: schema.String() }),
  execute: ({ city }) => ({ city, forecast: "sunny", highC: 24 }),
});

// The same key is the same agent, with its history, every time you run this.
const agent = await agents.upsert("quickstart", {
  instructions: "You are a concise assistant.",
  tools: { weather },
});

const run = await agent.run("Should I bring an umbrella in Lisbon today?");
console.log(run.text);

await agents.close();
```

Against the checklist above:
- **Nothing to host.** There's no session store to run and no containers to size.
- **Billing:** $0.01 per agent-hour of active run time, plus model usage. Sleeping agents cost only storage.
- **Models:** any model.
- **Open source:** the runtime is AGPL-3.0, and you can self-host it with one Docker container and Postgres.

The limits:
- **JavaScript only.** The model's code sandbox has no shell, no Python, no package installs and no network. The Python SDK lets your application run tools written in Python; it doesn't add model-written Python execution to the sandbox.
- **No exactly-once side effects.** camelRun never re-sends a lost tool call. The model is told the outcome is unknown, and your tools get an idempotency key.

If your agent needs Claude Code's file and shell tools, keep the SDK on a host above. Our [camelRun vs Claude Managed Agents](https://camelai.com/blog/camelrun-vs-claude-managed-agents) comparison covers the hosted options.

To try the runtime approach, see the [camelRun product page](https://camelai.com/run) or the [quickstart](https://camelai.com/docs/camelrun/quickstart).
