Skip to content

Production Setup

This guide covers everything you need to configure Runra Sandbox for production workloads. Follow these steps to ensure reliable, secure, and cost-effective sandbox operations.

API keys are managed through the Runra Dashboard. Each key is scoped to an organization and can have fine-grained permissions.

Terminal window
# Always load your API key from environment variables
export RUNRA_API_KEY="rsk_live_xxxxxxxxxxxxxxxxxxxx"
  • Use separate keys per environment: Dev, staging, and production should each have their own API key.
  • Rotate keys regularly: Generate new keys and deactivate old ones on a schedule.
  • Never commit keys: Use a secrets manager or environment variables.
  • Scope keys by sandbox type: Create restricted keys that can only create sandboxes with specific resources.

If you manage sandboxes for different applications or tenants, use separate Runra clients:

const appA = new Runra({
sandbox: { provider: "runra-sandbox", apiKey: process.env.APP_A_KEY },
});
const appB = new Runra({
sandbox: { provider: "runra-sandbox", apiKey: process.env.APP_B_KEY },
});
VariableDescriptionExample
RUNRA_API_KEYYour Runra API keyrsk_live_...
RUNRA_API_URLAPI base URLhttps://api.box.runra.dev
VariableDescriptionExample
RUNRA_DEFAULT_IMAGEDefault sandbox imagenode:22
RUNRA_DEFAULT_CPUDefault CPU allocation2
RUNRA_DEFAULT_MEMORY_MBDefault memory allocation4096
RUNRA_DEFAULT_TIMEOUT_MSDefault sandbox timeout300000

Prefer programmatic configuration for more control:

import { Runra } from "@runra/sdk";
const runra = new Runra({
sandbox: {
provider: "runra-sandbox",
apiKey: process.env.RUNRA_API_KEY!,
apiUrl: process.env.RUNRA_API_URL ?? "https://api.box.runra.dev",
defaults: {
image: "node:22",
resources: {
cpu: 2,
memoryMb: 4096,
diskMb: 10240,
},
timeoutMs: 300_000,
idleTimeoutMs: 120_000,
},
},
observability: {
provider: "axiom",
token: process.env.AXIOM_TOKEN,
dataset: "runra-production",
},
});

Choose resources based on your workload:

WorkloadCPUMemoryDiskTypical use
Light scripting22048 MB5120 MBSimple scripts, API calls
Web development24096 MB10240 MBnpm install, build, test
Data processing48192 MB20480 MBPython data pipelines
Full-stack agent48192 MB20480 MBClaude Code, Codex agents
Heavy ML/compilation816384 MB51200 MBModel training, large builds

This is the most important production planning step. Determine your peak concurrent sandbox count:

concurrent_sandboxes = peak_requests_per_second × average_sandbox_lifetime_seconds

Example: If you handle 5 agent requests per second, each lasting 120 seconds on average:

5 req/s × 120s = 600 concurrent sandboxes
TierMax concurrent sandboxesSuitable for
Starter50Prototyping, small teams
Pro500Production SaaS applications
EnterpriseCustomHigh-scale platforms

Contact sales@runra.dev to discuss enterprise capacity.

Implement exponential backoff to handle rate limits gracefully:

async function createSandboxWithRetry(
runra: Runra,
config: SandboxConfig,
maxRetries = 5
): Promise<Sandbox> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await runra.sandboxes.create(config);
} catch (error) {
if (error.code === "RATE_LIMITED" && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
console.warn(`Rate limited. Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
throw new Error("Max retries exceeded");
}

Manage a pool of available sandboxes for better performance:

class SandboxPool {
private available: Sandbox[] = [];
private inUse = new Set<string>();
constructor(
private runra: Runra,
private poolSize: number,
private config: SandboxConfig
) {}
async initialize() {
this.available = await Promise.all(
Array.from({ length: this.poolSize }, () =>
this.runra.sandboxes.create(this.config)
)
);
}
async acquire(): Promise<Sandbox> {
if (this.available.length === 0) {
// Pool exhausted — create on demand
return this.runra.sandboxes.create(this.config);
}
const sandbox = this.available.pop()!;
this.inUse.add(sandbox.id);
return sandbox;
}
async release(sandbox: Sandbox) {
this.inUse.delete(sandbox.id);
// Reset sandbox to clean state
await sandbox.exec("rm -rf /workspace/*");
this.available.push(sandbox);
}
}
// Usage
const pool = new SandboxPool(runra, 20, {
image: "node:22",
resources: { cpu: 2, memoryMb: 4096 },
});
await pool.initialize();
// Acquire and release during request handling
const sandbox = await pool.acquire();
try {
await sandbox.exec("npm test");
} finally {
await pool.release(sandbox);
}

Runra provides pre-built templates for common environments:

// Use a named template
const sandbox = await runra.sandboxes.create({
template: "node-typescript",
});
// Or specify an image directly
const sandbox = await runra.sandboxes.create({
image: "node:22",
});
TemplateImageIncludes
node-basicnode:22Node.js 22, npm, npx
node-typescriptnode:22Node.js + TypeScript, ts-node, tsx
python-basicpython:3.12Python 3.12, pip
python-mlpython:3.12Python + numpy, pandas, scikit-learn
fullstacknode:22Node.js + Python + git + curl
ubuntu-baseubuntu:24.04Clean Ubuntu with build-essential

For custom environments, use a Dockerfile and specify the image:

const sandbox = await runra.sandboxes.create({
image: "my-registry/my-custom-image:latest",
// Custom images must be pre-loaded; contact support for setup
});
Error codeDescriptionAction
RATE_LIMITEDToo many requestsImplement backoff
RESOURCE_EXHAUSTEDNo capacity availableRetry or reduce concurrency
INVALID_CONFIGBad sandbox configurationCheck your create options
SANDBOX_TIMEOUTSandbox exceeded timeoutIncrease timeout or optimize workload
IMAGE_NOT_FOUNDTemplate/image doesn’t existVerify image name
QUOTA_EXCEEDEDAccount quota hitUpgrade plan or terminate idle sandboxes
async function safeCreateSandbox(config: SandboxConfig): Promise<Sandbox | null> {
try {
return await runra.sandboxes.create(config);
} catch (error) {
switch (error.code) {
case "RESOURCE_EXHAUSTED":
// Try a smaller sandbox
return runra.sandboxes.create({
...config,
resources: { cpu: 2, memoryMb: 2048 },
});
case "RATE_LIMITED":
// Queue for later
await enqueueSandboxRequest(config);
return null;
default:
throw error;
}
}
}