Tips · Oct 9, 2026 · 15 min read
Claude Agent SDK Hosting: How to Deploy It in Production

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 and 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.
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. 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 withstartup(). - 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-sdk0.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=optionaldrops the TypeScript CLI. The binary is an optional dependency. Install without that flag, or setpathToClaudeCodeExecutable.- 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 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_turnsin Python) to cap turns. - Set
maxBudgetUsd(max_budget_usd) to stop a run at a cost limit. - Cap each subagent with
maxTurnsin itsAgentDefinition. - 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 byentry.uuidinappend. - Retention is yours. The SDK never deletes from your store.
- Limits throw.
query()throws after anerror_max_turnsorerror_max_budget_usdresult. 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 ofsettingSources; - give it its own
CLAUDE_CONFIG_DIRand an explicitcwdon everyquery(); - 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 inautomode, which drops broad allow rules. - Fix the tool surface. Pair
allowedToolswithpermissionMode: "dontAsk"to deny calls that would otherwise need approval. Calls that need no approval, including some read-only Bash commands, can still run.allowedToolsdoesn't hide other tools; use bare tool names indisallowedToolsto remove them. - Avoid
bypassPermissions. It approves every tool that no deny rule or hook blocks, whateverallowedToolssays, and refuses to start as root outside a recognized sandbox. - Use hooks for universal checks. A
PreToolUsehook 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 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 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_EXPORTERandOTEL_TRACES_EXPORTER, withCLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1for traces;OTEL_EXPORTER_OTLP_PROTOCOLandOTEL_EXPORTER_OTLP_ENDPOINT.
In production:
- Never use the
consoleexporter. 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_NAMEper agent, and add each user and tenant throughOTEL_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=1plusANTHROPIC_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 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 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 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 | 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 | 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 | Firecracker microVM | 45 min Hobby, 24 h Pro | $0.128 per active-CPU hour | $0.0212 per GB-hour | Persistent sandboxes, snapshots |
| Cloudflare Sandbox | microVM container | Not stated | $0.072 per active vCPU-hour | $0.009 per GiB-hour | Backups, workspace reuse |
| Fly Sprites | Dedicated microVM | No cap published | $0.03825 per CPU-hour | $0.021875 per GB-hour | Persistent disk, checkpoints |
| Daytona | 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 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. 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 (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 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.
How to choose
- One team, modest traffic: Docker on a VM, plus a
SessionStorefrom 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 comparison covers the hosted options.
To try the runtime approach, see the camelRun product page or the quickstart.