> ## Documentation Index
> Fetch the complete documentation index at: https://docs.porter.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandboxes Getting Started

> Enable Porter Sandboxes on a cluster and launch your first sandbox with the Sandbox SDK

Porter Sandboxes are isolated container workloads that you launch from code running inside your Porter cluster. Use them for code interpreters, agent tools, batch fan-out, and other on-demand workloads that may need persistent storage through volumes.

<Warning>
  Sandboxes are in a private beta. Please reach out to us at [support@porter.run](mailto:support@porter.run) or over Slack if you are interested in joining.
</Warning>

## Prerequisites

* A Porter project
* An AWS cluster where you want to run sandboxes. We recommend creating a new AWS cluster for sandboxes so sandbox workloads are isolated from your other running workloads.

<Note>
  A dedicated cluster is not required, it's a defense-in-depth recommendation. Sandboxes often run untrusted code (end-user submissions, LLM agent output), and running them in their own cluster isolates that code from your main workloads as much as possible. Sharing a cluster with your other workloads is reasonable for development and testing; for production workloads that run untrusted code, we recommend the separate cluster.
</Note>

## Enable sandboxes

<Steps>
  <Step title="Enable sandboxes from the Sandbox tab">
    Sandboxes can only be enabled on AWS clusters.

    In the Porter Dashboard, navigate to the Sandbox tab for the AWS cluster where you want to run sandboxes and click **Enable sandboxes**.

    <img src="https://mintcdn.com/porter/6TPN4-7r0xlBqsgK/images/sandboxes/enable-sandboxes.webp?fit=max&auto=format&n=6TPN4-7r0xlBqsgK&q=85&s=ded29e0ca341fef6f30de1562cd86896" alt="Sandbox tab with the enable sandboxes button" width="3596" height="2084" data-path="images/sandboxes/enable-sandboxes.webp" />
  </Step>

  <Step title="Install a Sandbox SDK in your application">
    Add either the Python or TypeScript Sandbox SDK to the application that will create and manage sandboxes.

    <img src="https://mintcdn.com/porter/6TPN4-7r0xlBqsgK/images/sandboxes/sandbox-list.webp?fit=max&auto=format&n=6TPN4-7r0xlBqsgK&q=85&s=883521b92dbf67b87459c5d13b3f0051" alt="Sandbox tab after sandboxes are enabled" width="3596" height="2084" data-path="images/sandboxes/sandbox-list.webp" />
  </Step>

  <Step title="Write your first sandbox call">
    In your application code, create a sandbox, execute a command, read the output, and terminate the sandbox when the work is done. The examples below show the smallest end-to-end flow.
  </Step>

  <Step title="Deploy your application to the sandbox cluster">
    For now, we recommend deploying the application that uses the SDK as a Porter Application in the same cluster where you want to run sandboxes.
  </Step>
</Steps>

## Calling from outside the cluster

The SDK connects to the in-cluster Sandbox API automatically when your application runs as a Porter Application in the same cluster where sandboxes are enabled.

To invoke sandboxes from anywhere else, authenticate with a Porter API token and tell the SDK which cluster to target:

```bash theme={null}
export PORTER_SANDBOX_API_KEY="<porter-api-token>"
export PORTER_CLUSTER_ID="<cluster-id>"
```

The SDK reads the project from the token and routes through the Porter API at `dashboard.porter.run` automatically. Replace `<cluster-id>` with the cluster where sandboxes are enabled. You can copy the prefilled snippet from the cluster's Sandbox tab, or look up the ID with the [Porter CLI](/cli/basic-usage):

```bash theme={null}
$ porter cluster list
ID  NAME              RESOURCE_NAME     SERVER
2   my-sandbox-cluster ...
```

You can create an API token from **Settings > API tokens** in the Porter Dashboard. Creating API tokens requires admin permissions.

To point the SDK at a specific URL instead, set `PORTER_SANDBOX_BASE_URL` or pass `base_url` (Python) / `baseUrl` (TypeScript) to the client constructor. These take precedence over everything above.

## Python quickstart

Install the SDK in your application image:

```bash theme={null}
pip install porter-sandbox
```

Create a sandbox, run a command, print the output, and terminate it:

```python theme={null}
from porter_sandbox import Porter

with Porter() as porter:
    sandbox = porter.sandboxes.create(
        image="python:3.12-slim",
        name="getting-started-python",
        tags={"example": "getting-started"},
    )

    result = sandbox.exec(["python", "-c", "print(2 + 2)"])
    print(result.stdout)

    logs = sandbox.logs(limit=100)
    print(logs)

    sandbox.terminate()
```

## TypeScript quickstart

Install the SDK in your application image:

```bash theme={null}
npm install porter-sandbox
```

Create a sandbox, run a command, print the output, and terminate it:

```typescript theme={null}
import { Porter } from "porter-sandbox";

const porter = new Porter();

const sandbox = await porter.sandboxes.create({
  image: "python:3.12-slim",
  name: "getting-started-typescript",
  tags: { example: "getting-started" },
});

const result = await sandbox.exec(["python", "-c", "print(2 + 2)"]);
console.log(result.stdout);

const logs = await sandbox.logs({ limit: 100 });
console.log(logs);

await sandbox.terminate();
porter.close();
```

## Keep a sandbox alive for exec

A sandbox lives as long as its main process: the image's default entrypoint, or the command you pass at create time. When that process exits, the sandbox moves to `succeeded` (exit code `0`) or `failed` (nonzero), and it stops accepting exec calls.

An image whose default command exits immediately reaches `succeeded` within a few seconds of starting. If you want to create a sandbox first and exec into it later, give it a long-running main process, then terminate it when the work is done:

<CodeGroup>
  ```python Python theme={null}
  sandbox = porter.sandboxes.create(
      image="alpine:3.20",
      name="worker",
      command=["sleep", "3600"],
  )

  result = sandbox.exec(["echo", "ok"])
  print(result.stdout)

  sandbox.terminate()
  ```

  ```bash CLI theme={null}
  porter sandbox create alpine:3.20 --name worker -- sleep 3600
  porter sandbox exec worker -- echo ok
  porter sandbox terminate worker
  ```
</CodeGroup>

## Set environment variables

Pass environment variables at create time. The SDKs take an `env` map, and the CLI takes a repeatable `--env KEY=VALUE` flag:

<CodeGroup>
  ```python Python theme={null}
  sandbox = porter.sandboxes.create(
      image="alpine:3.20",
      env={"API_KEY": "secret", "LOG_LEVEL": "debug"},
  )
  ```

  ```typescript TypeScript theme={null}
  const sandbox = await porter.sandboxes.create({
    image: "alpine:3.20",
    env: { API_KEY: "secret", LOG_LEVEL: "debug" },
  });
  ```

  ```bash CLI theme={null}
  porter sandbox create alpine:3.20 --env API_KEY=secret --env LOG_LEVEL=debug
  ```
</CodeGroup>

## Use custom images

Sandboxes run any container image, so you can bake in the tools your workload needs (language runtimes, CLIs, agents) instead of installing them at runtime.

Private images are supported. Push your image to the ECR registry in the AWS account associated with your sandbox cluster, and sandboxes on that cluster can pull it without extra credential configuration.

## Run an agent inside a sandbox

A sandbox can host an entire agent, not just execute tools for an agent that runs elsewhere. Two patterns are common:

* **Sandbox as a tool**: your agent's control loop and model calls run in your application, and the agent uses the sandbox to execute code or commands as a tool. This is the exec flow shown above.
* **Agent in the sandbox**: the whole agent, its control loop and its model calls, runs inside the sandbox. Bake your harness into a custom image, inject the model API key at create time, and run it as the sandbox's main process. Sandboxes reach the public internet by default, so an in-sandbox agent calls a hosted model API directly.

The second pattern is a custom image plus an environment variable. The agent runs as the sandbox's main process, so the sandbox stays alive as long as the agent is running (see [Sandbox lifetime](/sandboxes/overview#sandbox-lifetime)):

<CodeGroup>
  ```python Python theme={null}
  sandbox = porter.sandboxes.create(
      image="ghcr.io/acme/agent:latest",
      name="research-agent",
      env={"MODEL_API_KEY": model_api_key},
  )

  # Follow the agent's output while it runs.
  print(sandbox.logs(limit=100))

  # Terminate when you're done, or let the agent's process exit on its own.
  sandbox.terminate()
  ```

  ```typescript TypeScript theme={null}
  const sandbox = await porter.sandboxes.create({
    image: "ghcr.io/acme/agent:latest",
    name: "research-agent",
    env: { MODEL_API_KEY: modelApiKey },
  });

  // Follow the agent's output while it runs.
  console.log(await sandbox.logs({ limit: 100 }));

  // Terminate when you're done, or let the agent's process exit on its own.
  await sandbox.terminate();
  ```

  ```bash CLI theme={null}
  porter sandbox create ghcr.io/acme/agent:latest --name research-agent --env MODEL_API_KEY=$MODEL_API_KEY
  ```
</CodeGroup>

## Use tags to identify sandboxes

Tags make it easier to find sandboxes created by a workflow:

```python theme={null}
sandbox = porter.sandboxes.create(
    image="python:3.12-slim",
    tags={"workflow": "agent-run", "run": "2026-06-17"},
)
```

## Next steps

* Use [Sandbox Networking](/sandboxes/networking) to serve sandboxes over HTTPS on your own domains.
* Use [the sandbox CLI guide](/sandboxes/cli) to list, inspect logs, exec into, and terminate sandboxes.
* Use the [Python Sandbox SDK quickstart](/sandbox/sdk/python/quickstart) for Python services.
* Use the [TypeScript Sandbox SDK quickstart](/sandbox/sdk/typescript/quickstart) for Node.js and TypeScript services.
