← ALL POSTS

Tips · Oct 9, 2026 · 15 min read

Claude Agent SDK Hosting: How to Deploy It in Production

IR
Illiana Reed
CEO & Product, camelAI
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:

StateDefault locationWhat keeps it
Session transcripts~/.claude/projects/, or projects/ under CLAUDE_CONFIG_DIRA SessionStore adapter
CLAUDE.md memory~/.claude/CLAUDE.md and the working directoryA volume or object-store sync
Files the agent writesThe session's working directoryA 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:

PatternContainer lifetimeGood forYou must add
EphemeralOne container per taskBug fixes, invoice extraction, translationFast cold starts and a turn cap
Long-runningPersistent containers, many SDK processes eachChat bots, high-volume message streamsMemory for peak concurrency and sticky routing
HybridEphemeral containers that load from a SessionStoreWork with long idle gaps between turnsThe store (required) and tuned idle timeouts
Multi-agentSeveral SDK processes in one containerAgents that collaborate closelyA 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 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 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_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 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:

HostWho operates itIsolationSession lengthState between runsBest fit
Docker on a VMYouShared kernel; add gVisor or a VM per tenantNo capVM disk or volumesLong-running, multi-agent
Cloud Run / FargateCloud providerProvider-managedCloud Run: 60 min per request, 168 h per job; Fargate: no documented capEphemeral disk; use a storeEphemeral, hybrid
Sandbox providersProvidermicroVM or gVisor per sandboxOften 24 h max; some reset on resumeSnapshots, volumes, pausePer-task or per-user sandboxes
Kubernetes + agent-sandboxYour platform teamgVisor or KataNo capPersistent volumes, pause/resumeMany tenants on an existing cluster
Claude Managed AgentsAnthropic runs the loopAnthropic cloud or self-hosted sandboxLong-running, resumableServer-sideTeams 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

ProviderIsolationSession lengthComputeMemoryPersistence
Modal SandboxesgVisor or VM runtime5 min default, 24 h max$0.1413 per physical core-hour$0.024 per GiB-hourVolumes, filesystem snapshots
E2BLinux VM1 h Hobby, 24 h Pro$0.0504 per vCPU-hour$0.0162 per GiB-hourPause keeps files and memory
Vercel SandboxFirecracker microVM45 min Hobby, 24 h Pro$0.128 per active-CPU hour$0.0212 per GB-hourPersistent sandboxes, snapshots
Cloudflare SandboxmicroVM containerNot stated$0.072 per active vCPU-hour$0.009 per GiB-hourBackups, workspace reuse
Fly SpritesDedicated microVMNo cap published$0.03825 per CPU-hour$0.021875 per GB-hourPersistent disk, checkpoints
DaytonaContainers or Linux VMsNot stated$0.0504 per vCPU-hour$0.0162 per GiB-hourVolumes, 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 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 comparison covers the hosted options.

To try the runtime approach, see the camelRun product page or the quickstart.

FROM THE TEAM BEHIND CAMELAI

Try what we're building.