# Quickstart

Sandboxes let you run untrusted code (LLM-generated code, user submissions, AI agents, CI/CD pipelines) in isolated environments. Each sandbox is a Linux environment running against its own kernel rather than the host's. That closes the gap ordinary containers leave open: containers share the host kernel, so a kernel-level escape reaches the host. You get true isolation at container speed, and sandboxes boot in under a second.

On Northflank, sandboxes run on Northflank cloud or on your own infrastructure. You create them, run commands, access files, and destroy them programmatically with the JavaScript or Python SDK.

Isolation comes from microVMs for CPU workloads and gVisor for GPU workloads on Northflank cloud. On your own cloud, you select the runtime through cluster defaults or workload tags.

This guide takes you from an API token to a running sandbox you can execute commands in.

## Set up your environment

Start by creating an API token, a project, and an SDK client.

### Generate an API token

> [!note]
>
> [Click here](https://app.northflank.com/s/account/settings/api/tokens/new) to create an API token.

### Create a project

> [!note]
>
> [Click here](https://app.northflank.com/s/account/projects/new) to create a project.

Create a project on Northflank Cloud or select a BYOC cluster and note the project ID. Every call in this guide needs it. See [create a project](/docs/v1/application/getting-started/create-a-project) for the full walkthrough.

### Install an SDK

JavaScriptPython
```bash
npm install @northflank/js-client
```

```bash
pip install northflank
```

### Initialize the SDK

Create an `ApiClient` with your API token.

JavaScriptPython
```javascript
import {
  ApiClient,
  ApiClientInMemoryContextProvider,
} from "@northflank/js-client";

const contextProvider = new ApiClientInMemoryContextProvider();
await contextProvider.addContext({
  name: "context",
  token: process.env.NORTHFLANK_TOKEN,
});

const apiClient = new ApiClient(contextProvider, {
  throwErrorOnHttpErrorCode: true,
});
```

```python
import os

from northflank import ApiClient

client = ApiClient(api_token=os.environ["NF_API_TOKEN"])
```

## Create a sandbox

Deploy a container image as a deployment service. A service is Northflank's unit for a running workload, and each sandbox is one, which is why the calls below are service calls. The `sandboxId` is used as both the service name and the identifier in later calls.

JavaScriptPython
```javascript
const sandboxId = `sandbox-${crypto.randomUUID().split('-')[4]}`;

await apiClient.create.service.deployment({
  parameters: {
    projectId: 'your-project-id',
  },
  data: {
    name: sandboxId,
    billing: {
      // Sets CPU and memory. nf-compute-200 is 2 vCPU, 4GB RAM.
      deploymentPlan: 'nf-compute-200',
    },
    deployment: {
      instances: 1,
      docker: {
        // Base images exit immediately, so override the entrypoint with a
        // long-running command (or tail -f /dev/null) to keep the
        // container up to exec into.
        // Accepts: default, customEntrypoint, customCommand,
        // customEntrypointCustomCommand.
        configType: 'customEntrypointCustomCommand',
        customEntrypoint: '/bin/bash',
        customCommand: "-c 'sleep infinity'",
      },
      external: {
        // Any public or private container image.
        imagePath: 'ubuntu:22.04',
      },
      storage: {
        ephemeralStorage: {
          storageSize: 2048,
        },
      },
    },
    runtimeEnvironment: {
      // Injected into the container at runtime.
      MY_VAR: 'hello-world',
    },
  },
});
```

```python
import uuid

sandbox_id = f"sandbox-{uuid.uuid4().hex[:8]}"

client.create.service.deployment(
    project_id="your-project-id",
    data={
        "name": sandbox_id,
        # deploymentPlan sets CPU and memory. nf-compute-200 is 2 vCPU, 4GB RAM.
        "billing": {"deploymentPlan": "nf-compute-200"},
        "deployment": {
            "instances": 1,
            # Base images exit immediately, so override the entrypoint with a
            # long-running command (or tail -f /dev/null) to keep the
            # container up to exec into.
            # Accepts: default, customEntrypoint, customCommand,
            # customEntrypointCustomCommand.
            "docker": {
                "configType": "customEntrypointCustomCommand",
                "customEntrypoint": "/bin/bash",
                "customCommand": "-c 'sleep infinity'",
            },
            # Any public or private container image.
            "external": {"imagePath": "ubuntu:22.04"},
            "storage": {"ephemeralStorage": {"storageSize": 2048}},
        },
        # Injected into the container at runtime.
        "runtimeEnvironment": {"MY_VAR": "hello-world"},
    },
)
```

## Start the sandbox

Scale the service to one instance to boot it, then poll until its deployment status reaches `COMPLETED`.

JavaScriptPython
```javascript
await apiClient.scale.service({
  parameters: {
    projectId: 'your-project-id',
    serviceId: sandboxId,
  },
  data: {
    instances: 1,
  },
});

async function waitForReady() {
  while (true) {
    const svc = await apiClient.get.service({
      parameters: {
        projectId: 'your-project-id',
        serviceId: sandboxId,
      },
    });

    const status = svc.data?.status?.deployment?.status;
    // COMPLETED means the deployment rolled out and the container is
    // running. It does not mean the process has exited.
    if (status === 'COMPLETED') return;
    if (status === 'FAILED') throw new Error('Sandbox deployment failed');

    await new Promise((r) => setTimeout(r, 1000));
  }
}

await waitForReady();
```

```python
client.scale.service(
    project_id="your-project-id",
    service_id=sandbox_id,
    data={"instances": 1},
)

# Polls get.service until the deployment status is COMPLETED, then returns
# the service object. Raises TimeoutError if timeout_s elapses first, or
# RuntimeError if the deployment reports FAILED.
# COMPLETED means the deployment rolled out and the container is running.
# It does not mean the process has exited.
client.helpers.wait_for_service_ready(
    project_id="your-project-id",
    service_id=sandbox_id,
    timeout_s=300,
    poll_interval_s=1,
)
```

Sandboxes boot in under a second, so the wait normally returns on its first or second poll.

## Run commands in the sandbox

Run commands inside the running sandbox.

JavaScriptPython
```javascript
const handle = await apiClient.exec.execServiceSession(
  {
    projectId: 'your-project-id',
    serviceId: sandboxId,
  },
  {
    // shell picks the interpreter. Use bash -c for pipes, redirects, or
    // chained commands. Without it the command runs directly.
    shell: 'bash -c',
    command: "echo 'Hello from sandbox!' && ls /",
  }
);

const stdoutChunks = [];
const stderrChunks = [];

// Attach listeners before awaiting the result, or early output is missed.
handle.stdOut.on('data', (data) => stdoutChunks.push(data.toString()));
handle.stdErr.on('data', (data) => stderrChunks.push(data.toString()));

const result = await handle.waitForCommandResult();

console.log('Exit code:', result.exitCode);
console.log('Stdout:', stdoutChunks.join(''));
console.log('Stderr:', stderrChunks.join(''));
```

```python
# Blocks until the command exits, returning an ExecResult with
# exit_code, stdout, stderr and status. result.ok means exit_code == 0.
result = client.exec.run_service_command(
    project_id="your-project-id",
    service_id=sandbox_id,
    command="echo 'Hello from sandbox!' && ls /",
    # shell picks the interpreter. Use bash -c for pipes, redirects, or
    # chained commands. Without it the command runs directly.
    shell="/bin/bash -c",
)

print("Exit code:", result.exit_code)
print("Stdout:", result.stdout)
print("Stderr:", result.stderr)
```

### Interactive sessions

When you need to write to the process's stdin and read its output as it streams, open a session instead. In Python, `open_service_session` is a context manager so the WebSocket always closes:

```python
with client.exec.open_service_session(
    project_id="your-project-id", service_id=sandbox_id, command="cat"
) as session:
    session.resize(rows=40, columns=120)
    session.send("hello\n")
    # Read the output a send produces before closing stdin; calling
    # send_eof immediately after a send can race the proxy.
    for chunk in session:          # chunk.stream is "stdout" or "stderr"
        print(chunk.data, end="")
        if "hello" in chunk.data:
            break
    session.send_eof()             # close stdin
    result = session.wait()        # ExecResult once the process exits
```

See [execute commands](/docs/v1/api/execute-command) for streaming, TTY handling, and the equivalent CLI commands.

## Next steps

- [Sandbox examples: Deep dives into GPUs, volumes, exposed ports, and coding agents.](/v1/application/sandboxes/examples)
- [Configure and manage sandboxes: Pause, resume, scale, monitor, and delete your sandboxes.](/v1/application/sandboxes/configure-and-manage-sandboxes)
- [Sandboxes on Northflank cloud: Resource plans, GPU plans, regions, and billing on managed infrastructure.](/v1/application/sandboxes/sandboxes-on-northflank-cloud)
- [Sandboxes in your own cloud: Run sandboxes on your own AWS, GCP, Azure, or other cloud infrastructure.](/v1/application/sandboxes/sandboxes-in-your-own-cloud)
