Skip to content
Cloudflare Docs

Environment variables

Pass configuration, secrets, and runtime settings to your sandboxes using environment variables.

Three ways to set environment variables

The Sandbox SDK provides three methods for setting environment variables, each suited for different use cases:

1. Sandbox-level with setEnvVars()

Set environment variables globally for all commands in the sandbox:

TypeScript
const sandbox = getSandbox(env.Sandbox, "my-sandbox");
// Set once, available for all subsequent commands
await sandbox.setEnvVars({
DATABASE_URL: env.DATABASE_URL,
API_KEY: env.API_KEY,
});
await sandbox.exec("python migrate.py"); // Has DATABASE_URL and API_KEY
await sandbox.exec("python seed.py"); // Has DATABASE_URL and API_KEY

Use when: You need the same environment variables for multiple commands.

2. Per-command with exec() options

Pass environment variables for a specific command:

TypeScript
await sandbox.exec("node app.js", {
env: {
NODE_ENV: "production",
PORT: "3000",
},
});
// Also works with startProcess()
await sandbox.startProcess("python server.py", {
env: {
DATABASE_URL: env.DATABASE_URL,
},
});

Use when: You need different environment variables for different commands, or want to override sandbox-level variables.

3. Session-level with createSession()

Create an isolated session with its own environment variables:

TypeScript
const session = await sandbox.createSession({
env: {
DATABASE_URL: env.DATABASE_URL,
SECRET_KEY: env.SECRET_KEY,
},
});
// All commands in this session have these vars
await session.exec("python migrate.py");
await session.exec("python seed.py");

Use when: You need isolated execution contexts with different environment variables running concurrently.

Common patterns

Pass Worker secrets to sandbox

Securely pass secrets from your Worker to the sandbox. First, set secrets using Wrangler:

Terminal window
wrangler secret put OPENAI_API_KEY
wrangler secret put DATABASE_URL

Then pass them to your sandbox:

TypeScript
interface Env {
Sandbox: DurableObjectNamespace;
OPENAI_API_KEY: string;
DATABASE_URL: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandbox = getSandbox(env.Sandbox, "user-sandbox");
// Option 1: Set globally for all commands
await sandbox.setEnvVars({
OPENAI_API_KEY: env.OPENAI_API_KEY,
DATABASE_URL: env.DATABASE_URL,
});
await sandbox.exec("python analyze.py");
// Option 2: Pass per-command
await sandbox.exec("python analyze.py", {
env: {
OPENAI_API_KEY: env.OPENAI_API_KEY,
},
});
return Response.json({ success: true });
},
};

Combine default and specific variables

TypeScript
const defaults = { NODE_ENV: "production", LOG_LEVEL: "info" };
await sandbox.exec("npm start", {
env: { ...defaults, PORT: "3000", API_KEY: env.API_KEY },
});

Multiple isolated sessions

Run different tasks with different environment variables concurrently:

TypeScript
// Production database session
const prodSession = await sandbox.createSession({
env: { DATABASE_URL: env.PROD_DATABASE_URL },
});
// Staging database session
const stagingSession = await sandbox.createSession({
env: { DATABASE_URL: env.STAGING_DATABASE_URL },
});
// Run migrations on both concurrently
await Promise.all([
prodSession.exec("python migrate.py"),
stagingSession.exec("python migrate.py"),
]);

Bucket mounting credentials

When mounting S3-compatible object storage, the SDK uses s3fs-fuse under the hood, which requires AWS-style credentials. For R2, generate API tokens from the Cloudflare dashboard and provide them using AWS environment variable names:

Get R2 API tokens:

  1. Go to R2 > Overview in the Cloudflare dashboard
  2. Select Manage R2 API Tokens
  3. Create a token with Object Read & Write permissions
  4. Copy the Access Key ID and Secret Access Key

Set credentials as Worker secrets:

Terminal window
wrangler secret put AWS_ACCESS_KEY_ID
# Paste your R2 Access Key ID
wrangler secret put AWS_SECRET_ACCESS_KEY
# Paste your R2 Secret Access Key

Mount buckets with automatic credential detection:

TypeScript
interface Env {
Sandbox: DurableObjectNamespace;
AWS_ACCESS_KEY_ID: string;
AWS_SECRET_ACCESS_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandbox = getSandbox(env.Sandbox, 'data-processor');
// Credentials automatically detected from environment
await sandbox.mountBucket('my-r2-bucket', '/data', {
endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com'
});
// Access mounted bucket using standard file operations
await sandbox.exec('python', { args: ['process.py', '/data/input.csv'] });
return Response.json({ success: true });
}
};

The SDK automatically detects AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from your Worker's environment when you call mountBucket() without explicit credentials.

Pass credentials explicitly (if using custom secret names):

TypeScript
await sandbox.mountBucket('my-r2-bucket', '/data', {
endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com',
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY
}
});

See Mount buckets guide for complete bucket mounting documentation.

Environment variable precedence

When the same variable is set at multiple levels, the most specific level takes precedence:

  1. Command-level (highest) - Passed to exec() or startProcess() options
  2. Sandbox or session-level - Set with setEnvVars()
  3. Container default - Built into the Docker image with ENV
  4. System default (lowest) - Operating system defaults

Example:

TypeScript
// In Dockerfile: ENV NODE_ENV=development
// Sandbox-level
await sandbox.setEnvVars({ NODE_ENV: "staging" });
// Command-level overrides all
await sandbox.exec("node app.js", {
env: { NODE_ENV: "production" }, // This wins
});