# Examples

Worked examples for common sandbox patterns: persistent storage, GPUs, exposed ports, and coding agents.

Each example builds on the [quickstart](/docs/v1/application/sandboxes/quickstart) and assumes you already have an initialised `apiClient` and a project ID.

## Create a sandbox with persistent storage

Ephemeral storage is wiped whenever a sandbox restarts or pauses. Attach a volume when you need files to survive.

Create the volume first, then attach it as the service is created. Volumes are attached at creation time through `createOptions.volumesToAttach`.

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

await apiClient.create.volume({
  parameters: {
    projectId: 'your-project-id',
  },
  data: {
    name: volumeName,
    mounts: [
      {
        containerMountPath: '/workspace',
      },
    ],
    spec: {
      accessMode: 'ReadWriteMany',
      storageClassName: 'nf-multi-rw',
      storageSize: 10240, // 10 GiB
    },
  },
});

await apiClient.create.service.deployment({
  parameters: {
    projectId: 'your-project-id',
  },
  data: {
    name: storageSandboxId,
    billing: {
      deploymentPlan: 'nf-compute-200',
    },
    deployment: {
      instances: 1,
      docker: {
        configType: 'customCommand',
        customCommand: 'sleep infinity',
      },
      external: {
        imagePath: 'ubuntu:22.04',
      },
      storage: {
        ephemeralStorage: {
          storageSize: 2048,
        },
      },
    },
    createOptions: {
      volumesToAttach: [volumeName],
    },
  },
});
```

```python
import uuid

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

volume = client.create.volume(
    project_id="your-project-id",
    data={
        "name": volume_name,
        "mounts": [{"containerMountPath": "/workspace"}],
        "spec": {
            "accessMode": "ReadWriteMany",
            "storageClassName": "nf-multi-rw",
            "storageSize": 10240,  # 10 GiB
        },
    },
)

# Read the id back rather than assuming it matches the name you asked
# for. Use this id to attach, detach, or delete the volume.
volume_id = (volume.data or {}).get("id") or volume_name

client.create.service.deployment(
    project_id="your-project-id",
    data={
        "name": storage_sandbox_id,
        "billing": {"deploymentPlan": "nf-compute-200"},
        "deployment": {
            "instances": 1,
            "docker": {
                "configType": "customEntrypointCustomCommand",
                "customEntrypoint": "/bin/bash",
                "customCommand": "-c 'sleep infinity'",
            },
            "external": {"imagePath": "ubuntu:22.04"},
            "storage": {"ephemeralStorage": {"storageSize": 2048}},
        },
        "createOptions": {"volumesToAttach": [volume_id]},
    },
)
```

Files written to `/workspace` now survive pause and resume. A volume is billed for as long as it exists, including while the sandbox is paused.

See [add a volume](/docs/v1/application/databases-and-persistence/add-a-volume) for access modes and storage classes.

## Create a GPU sandbox

Run compute-intensive workloads with GPU acceleration. The project must be in a GPU-enabled region.

On Northflank cloud, GPU sandboxes are isolated with gVisor rather than the microVMs used for CPU sandboxes. You do not configure this.

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

await apiClient.create.service.deployment({
  parameters: {
    projectId: 'your-project-id', // must be in a GPU-enabled region
  },
  data: {
    name: sandboxId,
    billing: {
      deploymentPlan: 'nf-gpu-a100-80-1g', // 1x A100 80GB GPU
    },
    deployment: {
      instances: 1,
      external: {
        imagePath: 'quay.io/jupyter/pytorch-notebook:cuda12-2026-02-09',
      },
      docker: {
        configType: 'default',
      },
      gpu: {
        enabled: true,
        configuration: {
          // Must match the GPU in the deployment plan above.
          gpuType: 'a100-80',
          gpuCount: 1,
          // true shares one GPU across workloads; false is dedicated.
          timesliced: false,
        },
      },
      storage: {
        ephemeralStorage: {
          storageSize: 256000,
        },
        // Shared memory. Size generously: PyTorch dataloaders fail with
        // confusing errors when this is too small.
        shmSize: 174080,
      },
    },
    ports: [
      {
        name: 'app',
        internalPort: 8888,
        public: true,
        protocol: 'HTTP',
      },
    ],
  },
});
```

```python
import uuid

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

client.create.service.deployment(
    project_id="your-project-id",  # must be in a GPU-enabled region
    data={
        "name": sandbox_id,
        "billing": {"deploymentPlan": "nf-gpu-a100-80-1g"},
        "deployment": {
            "instances": 1,
            "external": {
                "imagePath": "quay.io/jupyter/pytorch-notebook:cuda12-2026-02-09"
            },
            "docker": {"configType": "default"},
            "gpu": {
                "enabled": True,
                "configuration": {
                    # Must match the GPU in the deployment plan above.
                    "gpuType": "a100-80",
                    "gpuCount": 1,
                    # True shares one GPU across workloads; False is dedicated.
                    "timesliced": False,
                },
            },
            "storage": {
                "ephemeralStorage": {"storageSize": 256000},
                # Shared memory. Size generously: PyTorch dataloaders fail
                # with confusing errors when this is too small.
                "shmSize": 174080,
            },
        },
        "ports": [
            {
                "name": "app",
                "internalPort": 8888,
                "public": True,
                "protocol": "HTTP",
            }
        ],
    },
)
```

See [GPU workloads](/docs/v1/application/gpu-workloads/gpus-on-northflank) for images, drivers, and optimisation.

## GPU sandbox with persistent storage

Model weights, datasets, and notebooks are expensive to re-download. Attach a volume so they survive restarts.

Mount the volume at every path the image writes to. For the Jupyter PyTorch image that means the notebook home directory as well as your working directory.

JavaScriptPython
```javascript
const gpuStorageSandboxId = `training-${crypto.randomUUID().split('-')[4]}`;
const volumeName = `data-${gpuStorageSandboxId}`;

await apiClient.create.volume({
  parameters: {
    projectId: 'your-project-id',
  },
  data: {
    name: volumeName,
    spec: {
      accessMode: 'ReadWriteMany',
      storageClassName: 'nf-multi-rw',
      storageSize: 102400, // 100 GiB
    },
    mounts: [
      { containerMountPath: '/home/jovyan' },
      { containerMountPath: '/workspace' },
    ],
  },
});

await apiClient.create.service.deployment({
  parameters: {
    projectId: 'your-project-id',
  },
  data: {
    name: gpuStorageSandboxId,
    billing: {
      deploymentPlan: 'nf-gpu-a100-80-1g',
    },
    deployment: {
      instances: 1,
      external: {
        imagePath: 'quay.io/jupyter/pytorch-notebook:cuda12-2026-02-09',
      },
      docker: {
        configType: 'default',
      },
      gpu: {
        enabled: true,
        configuration: {
          gpuType: 'a100-80',
          gpuCount: 1,
          timesliced: false,
        },
      },
      storage: {
        ephemeralStorage: {
          storageSize: 256000,
        },
        shmSize: 174080,
      },
    },
    ports: [
      {
        name: 'app',
        internalPort: 8888,
        public: true,
        protocol: 'HTTP',
      },
    ],
    createOptions: {
      volumesToAttach: [volumeName],
    },
  },
});
```

```python
import uuid

gpu_storage_sandbox_id = f"training-{uuid.uuid4().hex[:8]}"
volume_name = f"data-{gpu_storage_sandbox_id}"

volume = client.create.volume(
    project_id="your-project-id",
    data={
        "name": volume_name,
        "spec": {
            "accessMode": "ReadWriteMany",
            "storageClassName": "nf-multi-rw",
            "storageSize": 102400,  # 100 GiB
        },
        "mounts": [
            {"containerMountPath": "/home/jovyan"},
            {"containerMountPath": "/workspace"},
        ],
    },
)

volume_id = (volume.data or {}).get("id") or volume_name

client.create.service.deployment(
    project_id="your-project-id",
    data={
        "name": gpu_storage_sandbox_id,
        "billing": {"deploymentPlan": "nf-gpu-a100-80-1g"},
        "deployment": {
            "instances": 1,
            "external": {
                "imagePath": "quay.io/jupyter/pytorch-notebook:cuda12-2026-02-09"
            },
            "docker": {"configType": "default"},
            "gpu": {
                "enabled": True,
                "configuration": {
                    "gpuType": "a100-80",
                    "gpuCount": 1,
                    "timesliced": False,
                },
            },
            "storage": {
                "ephemeralStorage": {"storageSize": 256000},
                "shmSize": 174080,
            },
        },
        "ports": [
            {
                "name": "app",
                "internalPort": 8888,
                "public": True,
                "protocol": "HTTP",
            }
        ],
        "createOptions": {"volumesToAttach": [volume_id]},
    },
)
```

You can now pause the sandbox between training runs to stop GPU charges, and resume with your weights and notebooks still in place. The volume is billed while the sandbox is paused.

## Expose a web server

If your sandbox runs a web server or any other network service, expose a port to make it reachable. Northflank provisions a public DNS name automatically for publicly exposed ports.

JavaScriptPython
```javascript
// Replaces the whole port list rather than appending, so include
// every port you want to keep.
await apiClient.update.service.ports({
  parameters: {
    projectId: 'your-project-id',
    serviceId: sandboxId,
  },
  data: {
    ports: [
      {
        name: 'http',
        internalPort: 8080,
        public: true,
        protocol: 'HTTP',
      },
    ],
  },
});

// Retrieve the public DNS
const ports = await apiClient.get.service.ports({
  parameters: {
    projectId: 'your-project-id',
    serviceId: sandboxId,
  },
});

const publicUrl = ports.data.ports.find((p) => p.internalPort === 8080)?.dns;
console.log('Public URL:', publicUrl);
```

```python
# Replaces the whole port list rather than appending, so include
# every port you want to keep.
client.update.service.ports(
    project_id="your-project-id",
    service_id=sandbox_id,
    data={
        "ports": [
            {
                "name": "http",
                "internalPort": 8080,
                "public": True,
                "protocol": "HTTP",
            }
        ]
    },
)

svc = client.get.service(project_id="your-project-id", service_id=sandbox_id)

for port in (svc.data or {}).get("ports") or []:
    print(port["name"], port["internalPort"], port.get("dns") or "pending DNS")
```

You can also expose a port at creation time by passing the same `ports` list in the create payload, which avoids a second call.

Set `public: false` if the port should only be reachable by other services in the same project. The `protocol` field accepts `HTTP`, `HTTP/2`, `TCP`, and `UDP`.

You can also attach your own domain to a publicly exposed port. See [add a domain](/docs/v1/application/domains/add-a-domain-to-your-account).

## Run a coding agent in a sandbox

A sandbox makes a good runtime for a coding agent: the agent can install packages, run builds, and execute whatever it writes, without any of it touching your machine.

Pass the agent's credentials as runtime variables rather than baking them into the image.

JavaScriptPython
```javascript
await apiClient.create.service.deployment({
  parameters: {
    projectId: 'your-project-id',
  },
  data: {
    name: `claude-agent-${crypto.randomUUID().split('-')[4]}`,
    billing: {
      deploymentPlan: 'nf-compute-200',
    },
    deployment: {
      instances: 1,
      external: {
        imagePath: 'ubuntu:22.04',
      },
      docker: {
        configType: 'customCommand',
        customCommand: 'sleep infinity',
      },
      storage: {
        ephemeralStorage: {
          storageSize: 2048,
        },
      },
    },
    runtimeEnvironment: {
      ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
    },
  },
});
```

```python
import os
import uuid

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

client.create.service.deployment(
    project_id="your-project-id",
    data={
        "name": sandbox_id,
        "billing": {"deploymentPlan": "nf-compute-200"},
        "deployment": {
            "instances": 1,
            "external": {"imagePath": "ubuntu:22.04"},
            "docker": {
                "configType": "customEntrypointCustomCommand",
                "customEntrypoint": "/bin/bash",
                "customCommand": "-c 'sleep infinity'",
            },
            "storage": {"ephemeralStorage": {"storageSize": 2048}},
        },
        "runtimeEnvironment": {
            "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"],
        },
    },
)
```

Drive the agent with [exec sessions](/docs/v1/api/execute-command), and attach a volume if its work should survive a restart.

For a managed alternative that handles agent authentication, repository connection, and local SSH access for you, see [Cloud Harness](/docs/v1/application/cloud-harness/quickstart).

Store real credentials in a [secret group](/docs/v1/application/secure/inject-secrets) rather than reading them from your shell environment.

## Next steps

- [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)
