Founder Guide
Everything about how SandboxDrop works, from architecture to deployment.
01 Project Overview
SandboxDrop is a zero-signup ephemeral hosting platform for AI agents and developers. The core idea:
- One API request deploys code to a live URL with a real Docker container behind it
- Payment-as-auth via the x402 protocol (Coinbase) — no accounts, no API keys, no signup
- Auto-expiring sandboxes with configurable TTL (1 hour to 7 days)
- Built for AI agents that need to deploy autonomously (Claude Code, Cursor, Devin)
Target market: AI coding agents, developers needing quick previews, CI/CD preview environments, coding education.
Revenue model: Per-sandbox pricing in USDC. Static sites at $0.25 flat, compute sandboxes from $0.005/hr to $0.08/hr depending on resources.
02 Architecture
Key design decisions
- Single Hono app — all routes under one
new Hono()to avoid body-stream consumption issues with nested routers - Host networking (dev mode) — containers listen directly on random host ports (10000-60000). Production should use bridge networking with iptables.
- Single base image — all runtimes use
oven/bun:1.1-alpine. Runtime-specific behavior is injected via server scripts at container creation time. No custom images to build. - Manual JSON parsing on deploy endpoint — avoids Hono's
zValidatorbody-stream issues in certain middleware chains
03 Tech Stack
| Component | Technology | Why |
|---|---|---|
| Runtime | Bun 1.x | Fast startup, native TypeScript, built-in test runner, HTTP server |
| Framework | Hono 4.x | Lightweight, Bun-native, middleware system, Web Standards API |
| ORM | Drizzle 0.38+ | Type-safe, no code generation needed, schema-as-code |
| Database | PostgreSQL 15+ | JSONB for flexible data, reliable, excellent Drizzle support |
| Containers | Docker via dockerode | Industry standard, good API client for Node/Bun |
| Payments | x402 (@x402/hono) | Coinbase's payment-as-auth protocol, gasless USDC on Base |
| Validation | Zod | TypeScript-first schema validation, integrates with Hono |
| IDs | nanoid | URL-safe, collision-resistant, short IDs with semantic prefixes |
04 Container Provisioning
Every sandbox gets its own Docker container. The provisioning flow:
- API receives deploy request with files, runtime, and tier
- Docker container created from
oven/bun:1.1-alpinebase image - Resource limits applied (CPU, memory, PIDs) based on tier
- Files injected via tar archive into
/appdirectory - Build command executed (if provided) via
docker exec - Container started with runtime-specific CMD
- Host port registered in proxy mapping
Runtime behavior
| Runtime | How it works |
|---|---|
| static | Injects a __server.js Bun.serve() static file server. User files go under /app/public/. |
| nodejs20 | Files injected at /app/. Default CMD: bun run /app/index.js. |
| python312 | Files injected at /app/. Requires start_cmd to specify entry point. |
| bun | Files injected at /app/. Default CMD: bun run /app/index.ts. |
Security hardening
Note: Bridge networking (with explicit port bindings) is used instead of host networking. Host networking doesn't work on Docker Desktop for macOS/Windows since containers run inside a VM. Each container listens on port 3000 internally, mapped to a random host port (10000–60000).
File injection
Files are injected into containers using a custom tar archive builder. The system builds a proper POSIX tar archive in memory:
- Creates directory entries for nested paths
- Sets proper permissions (0644 for files, 0755 for directories)
- Computes checksum per tar header block
- Uses
container.putArchive()to inject into/app
Hot file updates (PATCH /sandbox/:id/files) use the same tar injection for new/updated files, and docker exec rm -f for deleted files.
05 Database Schema
6 tables managed by Drizzle ORM, plus 4 enums:
| Table | Purpose | Key columns |
|---|---|---|
| sandboxes | Core sandbox state | id (sb_), status, url, runtime, tier, container_id, container_port, cost_usdc, expires_at |
| sandbox_files | File contents per sandbox | sandbox_id, path, content |
| sandbox_logs | System event log | sandbox_id, level, message, timestamp |
| credits | Prepaid credit balances | id (cr_), token (sdt_live_), balance_usdc, refund_address |
| webhooks | Registered webhook URLs | credit_token, url, events[] |
| newsletter_subscribers | Email signups from landing page | email, subscribed_at |
Enums
sandbox_status: provisioning, running, stopped, terminatedruntime: static, nodejs20, python312, buntier: micro, small, medium, largeaccess_mode: token, ip_whitelist, open
ID format
sb_— sandbox IDs (10-char nanoid)cr_— credit IDssdt_live_— credit Bearer tokens (32-char)ak_— sandbox access tokens
06 Payment Flow
x402 Protocol (Coinbase)
SandboxDrop uses x402 V2 for payment-as-auth on Base network (USDC). The flow:
- Client sends
POST /v1/deploywithout payment headers - x402 middleware returns
402 Payment RequiredwithPAYMENT-REQUIREDheader - Client wallet signs an ERC-3009
transferWithAuthorization(EIP-712 typed data) - Client retries with
PAYMENT-SIGNATUREheader - Facilitator verifies signature and settles payment (pays gas on behalf of buyer)
- Server returns resource with
PAYMENT-RESPONSEheader
Key detail: The x402 bypass in index.ts checks for Authorization: Bearer sdt_live_... headers. If present, the request skips x402 entirely and goes through the credit deduction path instead.
| Config | Value |
|---|---|
| Network | Base Sepolia (testnet): eip155:84532 |
| USDC contract | 0x036CbD53842c5426634e7929541eC2318f3dCF7e |
| Facilitator | https://x402.org (free testnet) |
| Production | Base mainnet eip155:8453, Coinbase CDP facilitator |
Credit system
Alternative to x402. User purchases credits via POST /credits/purchase, receives a sdt_live_ Bearer token.
- Credit deduction on deploy and extend operations
- Prorated refund on early termination:
refund = (unused_hours / total_hours) * cost - Minimum transaction: $0.10 USDC
07 API Routes
All routes are mounted under /v1 in a single Hono app. The middleware chain:
Deploy flow (POST /deploy)
- Parse and validate JSON body with Zod schema
- Check credit balance (if Bearer token) or let x402 handle payment
- Deduct credits from balance
- Insert sandbox record in DB (status: provisioning)
- Store files in sandbox_files table
- Call
createContainer()to provision Docker container - Update DB with container_id and container_port (status: running)
- Register port mapping in reverse proxy
- Fire
sandbox.readywebhook (non-blocking) - Return sandbox details to client
On failure: marks sandbox as terminated, refunds credits, returns error detail.
08 Reverse Proxy
A separate Bun.serve() instance runs on port 4001 (configurable via PROXY_PORT). It routes sandbox URLs to containers.
- Extracts sandbox ID from Host header:
sb-XXXX.sandboxdrop.dev→sb_XXXX - Looks up host port in in-memory map (fast path)
- Falls back to DB lookup if not in memory
- Proxies request to
http://127.0.0.1:{port} - Adds
X-Sandbox-Idheader to response
Important: The API server (port 4000) and the proxy (port 4001) are separate servers. To access a sandbox via its URL locally, always use the proxy port:
In production, this sits behind Caddy or Traefik for TLS termination and wildcard certificate handling.
09 Background Workers
Expiry worker
Runs every 30 seconds. Two jobs:
- Expire sandboxes: Finds sandboxes where
expires_at < nowandstatus != terminated. Terminates Docker container, updates DB, firessandbox.terminatedwebhook. - Expiry warnings: Finds running sandboxes expiring within 10 minutes. Fires
sandbox.expiringwebhook.
10 Webhook Delivery
When a lifecycle event occurs, the system:
- Looks up the sandbox's credit token
- Finds all webhooks registered to that credit token matching the event type
- Delivers payload via POST with
Content-Type: application/json - Retries up to 3 times with exponential backoff (1s, 2s, 4s delays)
Events
| Event | Trigger |
|---|---|
sandbox.ready | Container provisioned and running |
sandbox.expiring | 10 minutes before TTL expires |
sandbox.terminated | Sandbox stopped (expired, deleted, or failed) |
11 Testing & CI
31 tests across 4 files, all passing:
| File | Tests | What it covers |
|---|---|---|
pricing.test.ts | 10 | Cost calculation per runtime/tier, refund math, edge cases |
ids.test.ts | 6 | ID generators: prefix format, length, uniqueness |
api.test.ts | 11 | Integration tests: health, credits, newsletter, webhooks, sandbox lifecycle |
container.test.ts | 4 | Module exports, proxy port mapping, webhook/expiry modules |
CI Pipeline
GitHub Actions workflow (.github/workflows/ci.yml):
- PostgreSQL service container
- Bun install + schema push
- Unit tests:
bun test tests/pricing.test.ts tests/ids.test.ts - Integration tests: starts server, runs
bun test tests/api.test.ts - TypeScript check:
bunx tsc --noEmit
12 Development Setup
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | Yes | — | PostgreSQL connection string |
PORT | No | 4000 | API server port |
PROXY_PORT | No | 4001 | Reverse proxy port |
X402_RECEIVER_ADDRESS | For x402 | 0x0... | Wallet to receive USDC payments |
X402_FACILITATOR_URL | For x402 | https://x402.org | x402 facilitator endpoint |
X402_NETWORK | For x402 | eip155:84532 | CAIP-2 network ID |
Debugging containers
Sandbox containers use labels prefixed with sandboxdrop. — use the full label key when filtering:
13 Domains & Infrastructure
| Domain | Purpose | Status |
|---|---|---|
sandboxdrop.com | Landing page + docs | Live |
api.sandboxdrop.dev/v1 | REST API | Ready |
sb-{id}.sandboxdrop.dev | Sandbox URLs (via proxy) | Ready |
sb-{id}.ssh.sandboxdrop.dev:2222 | SSH access | Planned |
mcp.sandboxdrop.dev/sse | MCP server for AI tools | Planned |
14 Feature Status
| Feature | Status |
|---|---|
| REST API (all 10 endpoints) | Complete |
| PostgreSQL + Drizzle ORM schema | Complete |
| x402 payment middleware (Base Sepolia) | Complete |
| Credit system (purchase, deduct, refund) | Complete |
| Docker container provisioning | Complete |
| Resource limits & security hardening | Complete |
| Reverse proxy (sandbox URL routing) | Complete |
| Real-time log streaming (SSE) | Complete |
| Hot file injection into containers | Complete |
| TTL expiry worker (30s interval) | Complete |
| Webhook delivery with retries | Complete |
| Test suite (31 tests) | Complete |
| GitHub Actions CI | Complete |
| Landing page with newsletter signup | Complete |
| User docs page (/docs) | Complete |
| SSH bastion access | Planned |
| Dynamic x402 pricing per tier | Planned |
| MCP server | Planned |
| SDK & CLI packages (npm/pypi) | Planned |
| Production deployment (TLS, bridge networking) | Planned |
| Content scanning & abuse prevention | Planned |
15 Roadmap
Next priorities
- Production deployment — TLS via Caddy/Traefik, bridge networking with iptables, proper DNS wildcard
- SSH bastion — SSH access to running containers on port 2222 via bastion host
- MCP server — Enable AI tools (Claude Code, Cursor) to deploy directly
- Dynamic x402 pricing — Price x402 payments per tier instead of flat $0.25
- SDK packages — Publish
sandboxdropto npm and pypi - Multipart deploy — Accept zip/tar.gz uploads alongside JSON file maps
- Content scanning — Automated abuse detection and content policy enforcement
- Dashboard — Optional web UI for credit token holders to manage sandboxes