Run Claude Code (Anthropic's agentic coding tool) as an HTTP API inside a Unikraft unikernel — a lightweight, single-address-space VM that boots in milliseconds.
A minimal Node.js HTTP server that wraps claude -p (Claude Code's programmatic/pipe mode) and exposes it as an async job API. The server runs inside a Unikraft unikernel on Unikraft Cloud.
Architecture:
Client → HTTPS → Unikraft Gateway → HTTP :8080 → server.js → claude -p → Anthropic API
- kraft CLI (
brew install unikraft/cli/kraftkit) - Unikraft Cloud account + token
- Anthropic API key
export UKC_TOKEN="your-unikraft-token"
export ANTHROPIC_API_KEY="your-anthropic-key"
kraft cloud deploy \
--metro dal \
-p 443:8080 \
-M 2048 \
--name claude-sandbox \
-e "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" \
.# Health check
curl https://your-instance.dal.unikraft.app/health
# Submit a prompt (returns immediately with job ID)
curl -X POST https://your-instance.dal.unikraft.app/prompt \
-H "Content-Type: application/json" \
-d '{"prompt": "What is 2+2?", "max_turns": 1}'
# Poll for results
curl https://your-instance.dal.unikraft.app/job/<job-id>
# List all jobs
curl https://your-instance.dal.unikraft.app/jobs| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check, returns {"status": "ok", "runtime": "unikraft"} |
POST |
/prompt |
Submit a prompt. Body: {"prompt": "...", "max_turns": N}. Returns 202 with job_id |
GET |
/job/:id |
Get job status and output |
GET |
/jobs |
List all jobs with summary |
{
"id": "uuid",
"status": "completed",
"exit_code": 0,
"duration_ms": 1376,
"output": "{\"type\":\"result\",\"result\":\"4\", ...}",
"stderr": ""
}The output field contains Claude Code's JSON output (when using --output-format json), which includes the result text, token usage, cost, and session metadata.
This project was an exploration of running Claude Code in a unikernel environment. Here's what we discovered through ~12 deployment iterations:
-
It works. Claude Code runs successfully inside a Unikraft unikernel with full tool use (file read/write, bash execution). Response times are comparable to running locally (~1-7 seconds for simple prompts).
-
IS_SANDBOX=1 is required. Claude Code refuses
--dangerously-skip-permissionswhen running as root (which Unikraft always does). SettingIS_SANDBOX=1in the environment bypasses this check — it's the same mechanism Anthropic's own sandbox infrastructure uses. -
stdin must be /dev/null. Using
spawn()withstdio: ["ignore", "pipe", "pipe"]is critical. Without explicitly closing stdin, Claude Code hangs indefinitely after initialization (gets stuck after "Policy limits: Fetched successfully" in the debug log). -
Alpine base works, slim doesn't.
node:22-alpineproduces a ~350MB image that fits in Unikraft's memory.node:22-slimwas ~544MB and OOM'd even with 2GB RAM (initramfs unpacking requires roughly 2x image size). -
Unikraft ignores Dockerfile USER directives. Everything runs as root. The
USERinstruction in the Dockerfile has no effect. -
HOME must be set explicitly. Unikraft overrides
HOMEto/regardless of the DockerfileENVdirective. Must setHOME=/rootin the spawn environment. -
ToS must be pre-accepted. Claude Code requires onboarding acceptance. Pre-writing
{"hasCompletedOnboarding":true,"hasAcknowledgedMdmPolicy":true}to~/.claude.jsonin the Dockerfile handles this. -
Gateway timeout is ~60s. Unikraft's HTTP gateway times out long-running requests, requiring the async job pattern (POST returns job ID, poll for results).
-
2048MB RAM is the sweet spot. 1024MB OOMs during initramfs unpacking. 2048MB works reliably.
- No SSH/exec access. All debugging must happen via HTTP endpoints or
kraft cloud instance logs. - No persistent storage. Each instance boots fresh. Files written during a session are lost on restart.
- Cold start penalty. First Claude Code invocation initializes Statsig, fetches policy settings, etc. (~2s overhead). Subsequent calls use cached config.
- Scale-to-zero breaks long jobs. If the instance scales to zero mid-job, the job is lost. Use
scale_to_zero.policy: "off"in the Kraftfile. - Storage quota is tight. 1GB image storage quota means you need to delete old images before deploying new ones.
| Platform | Boot Time | I/O Speed | Claude Code? | Price |
|---|---|---|---|---|
| Unikraft | ~30ms | Native | Yes (this project) | ~$0.06/hr |
| E2B | ~200ms (warm) | tmpfs fast, rootfs slow | Untested | $0.014/hr |
| Vercel | 1-5s | Very slow (EROFS) | Broken (I/O) | $0.18/hr |
| Docker | ~500ms | Native | Yes | Varies |
Unikraft's advantage is the unikernel architecture — minimal attack surface, millisecond boot times, and near-native I/O performance since there's no overlay filesystem.
| File | Description |
|---|---|
server.js |
HTTP server wrapping Claude Code with async job management |
Dockerfile |
Alpine-based image with Claude Code pre-installed and ToS pre-accepted |
Kraftfile |
Unikraft Cloud deployment configuration |
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY |
Yes | Anthropic API key for Claude |
These are set automatically by the server:
| Variable | Value | Why |
|---|---|---|
IS_SANDBOX |
1 |
Bypasses root check for --dangerously-skip-permissions |
HOME |
/root |
Unikraft overrides HOME to /; Claude Code needs a real home dir |
- Authentication. The API is currently unauthenticated — add API key validation or mTLS.
- Job cleanup. Jobs accumulate in memory indefinitely. Add TTL-based eviction.
- Streaming. Support Server-Sent Events for real-time output streaming.
- Multi-instance. Use Unikraft's auto-scaling for parallel job execution.
- Persistent workspaces. Mount Unikraft volumes for project files that survive restarts.
- MCP server support. Configure Claude Code with MCP servers for extended tool access.