Docs

Examples

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

Each example builds on the 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.

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],
    },
  },
});

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 for access modes and storage classes.

Create a GPU sandbox

Create a Jupyter PyTorch sandbox with one or eight H100 or RTX PRO 6000 GPUs. The examples use us-central, which supports these configurations. See GPU plans and regions before choosing another region.

Your account needs GPU access. GPU sandboxes on Northflank cloud use gVisor isolation automatically.

Choose a configuration below. The plan ID, GPU type, and GPU count must match. The API sets ephemeral storage automatically from the GPU type and count.

Shared memory (shmSize) is optional. If omitted, the API sets it to the GPU plan's system RAM. Smaller explicit values are preserved. Values above that limit are capped. These examples request 32 GiB for PyTorch. See GPU storage defaults for allocation details.

import { randomUUID } from 'node:crypto';
import {
  ApiClient,
  ApiClientInMemoryContextProvider,
} from '@northflank/js-client';

const contextProvider = new ApiClientInMemoryContextProvider();
await contextProvider.addContext({
  name: 'default',
  token: process.env.NORTHFLANK_API_TOKEN,
});
const apiClient = new ApiClient(contextProvider, {
  throwErrorOnHttpErrorCode: true,
});

const gpuConfigs = {
  h100_1x: {
    deploymentPlan: 'nf-gpu-h100-80-1g',
    gpuType: 'h100-80',
    gpuCount: 1,
  },
  h100_8x: {
    deploymentPlan: 'nf-gpu-h100-80-8g',
    gpuType: 'h100-80',
    gpuCount: 8,
  },
  rtxPro6000_1x: {
    deploymentPlan: 'nf-gpu-rtx-pro-6000-96-1g',
    gpuType: 'rtx_pro_6000-96',
    gpuCount: 1,
  },
  rtxPro6000_8x: {
    deploymentPlan: 'nf-gpu-rtx-pro-6000-96-8g',
    gpuType: 'rtx_pro_6000-96',
    gpuCount: 8,
  },
};
const gpu = gpuConfigs.h100_1x;

const project = await apiClient.create.project({
  data: {
    name: `gpu-${randomUUID().slice(0, 8)}`,
    region: 'us-central',
  },
});
const projectId = project.data.id;

const sandbox = await apiClient.create.service.deployment({
  parameters: { projectId },
  data: {
    name: `jupyter-${randomUUID().slice(0, 8)}`,
    billing: { deploymentPlan: gpu.deploymentPlan },
    deployment: {
      instances: 1,
      external: {
        imagePath: 'quay.io/jupyter/pytorch-notebook:cuda12-2026-02-09',
      },
      docker: { configType: 'default' },
      gpu: {
        enabled: true,
        configuration: {
          gpuType: gpu.gpuType,
          gpuCount: gpu.gpuCount,
          timesliced: false,
        },
      },
      storage: {
        shmSize: 32768,
      },
    },
    ports: [
      { name: 'app', internalPort: 8888, public: true, protocol: 'HTTP' },
    ],
  },
});
console.log({ projectId, sandboxId: sandbox.data.id });

Wait for the sandbox to run. Open the public URL for port app, then use the Jupyter token shown in the container logs.

Pause the sandbox when you finish to stop GPU usage charges. Ephemeral files do not survive container replacement. Attach a persistent volume for files that must survive replacement. The next example stores model weights.

GPU sandbox with persistent storage

Serve Qwen3.5-4B with SGLang on one H100. Store downloaded model weights on a persistent volume so they survive sandbox replacement.

Use an authenticated client from the quickstart and a project in us-central. The project needs GPU access and at least 100 GiB of volume allowance. See GPU plans and regions for other configurations.

Set MODEL_API_KEY in your local environment before running the example. SGLang requires this key for requests to the public endpoint. The example pins the CUDA 12 SGLang image. See SGLang installation for image requirements.

Ephemeral storage is automatic. The examples explicitly request 32 GiB of shared memory for SGLang. If you omit shmSize, the API sets it to the GPU plan's system RAM. Smaller explicit values are preserved. Values above that limit are capped. The persistent volume size is separate from both values.

import { randomUUID } from 'node:crypto';

const projectId = 'your-gpu-project-id';
const modelApiKey = process.env.MODEL_API_KEY;
if (!modelApiKey) {
  throw new Error('Set MODEL_API_KEY before creating the sandbox');
}

const sandboxId = `sglang-${randomUUID().slice(0, 8)}`;
const volumeName = `models-${sandboxId}`;

await apiClient.create.volume({
  parameters: { projectId },
  data: {
    name: volumeName,
    spec: {
      accessMode: 'ReadWriteMany',
      storageClassName: 'nf-multi-rw',
      storageSize: 102400,
    },
    mounts: [{ containerMountPath: '/root/.cache/huggingface' }],
  },
});

await apiClient.create.service.deployment({
  parameters: { projectId },
  data: {
    name: sandboxId,
    billing: { deploymentPlan: 'nf-gpu-h100-80-1g' },
    runtimeEnvironment: { MODEL_API_KEY: modelApiKey },
    deployment: {
      instances: 1,
      external: { imagePath: 'lmsysorg/sglang:v0.5.19-cu129' },
      docker: {
        configType: 'customEntrypointCustomCommand',
        customEntrypoint: '/bin/sh',
        customCommand: `-c 'exec python3 -m sglang.launch_server --model-path Qwen/Qwen3.5-4B --host 0.0.0.0 --port 30000 --context-length 8192 --api-key "$MODEL_API_KEY"'`,
      },
      gpu: {
        enabled: true,
        configuration: {
          gpuType: 'h100-80',
          gpuCount: 1,
          timesliced: false,
        },
      },
      storage: {
        shmSize: 32768,
      },
    },
    ports: [
      {
        name: 'api',
        internalPort: 30000,
        public: true,
        protocol: 'HTTP',
      },
    ],
    createOptions: { volumesToAttach: [volumeName] },
  },
});

Wait until SGLang finishes downloading and loading the model. Read the container logs for startup progress. Set SGLANG_URL to the public URL for port api, then send a request. This request disables thinking mode so the model returns a short answer:

curl --fail "$SGLANG_URL/v1/chat/completions" \
  -H "Authorization: Bearer $MODEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"Qwen/Qwen3.5-4B","messages":[{"role":"user","content":"Explain what a GPU does in one sentence."}],"max_tokens":128,"chat_template_kwargs":{"enable_thinking":false}}'

To use RTX PRO 6000, change the plan to nf-gpu-rtx-pro-6000-96-1g and the GPU type to rtx_pro_6000-96. The API selects its ephemeral storage allocation automatically. To use eight GPUs, select the matching eight-GPU configuration from the Jupyter example. For a model that supports eight-way tensor parallelism, add --tp 8 to split it across eight GPUs. Larger models need enough volume space for their weights.

Pause the sandbox between runs to stop GPU usage charges. The volume remains billed while the sandbox is paused. Keep the volume attached when you resume or recreate the sandbox to reuse the downloaded weights.

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.

// 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);

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.

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.

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,
    },
  },
});

Drive the agent with exec sessions, 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.

Store real credentials in a secret group rather than reading them from your shell environment.

© 2026 Northflank Ltd. All rights reserved.

northflank.com / Terms / Privacy / feedback@northflank.com