v1

Sandboxes /

Create sandboxes with SDK

Create and manage sandboxes programmatically using the Northflank JS SDK.

Generate an API token

Click here to create an API token.

Create a project

Click here to create a project.

Create a project on Northflank Cloud or select a BYOC cluster. Note the project ID for SDK calls.

Install and initialize the SDK

Install the Northflank JS SDK and authenticate with your API token:

$ npm install @northflank/js-client
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,
});

Create the sandbox service

Deploy a container image as a service. The service starts with instances: 0 so you can attach a volume before booting it.

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

await apiClient.create.service.deployment({
  parameters: {
    projectId: 'your-project-id',
  },
  data: {
    name: sandboxId,
    billing: {
      deploymentPlan: 'nf-compute-200', // 2 vCPU, 4GB RAM
    },
    deployment: {
      instances: 0, // don't start yet — attach volume first
      external: {
        imagePath: 'ubuntu:22.04',
      },
      storage: {
        ephemeralStorage: {
          storageSize: 2048,
        },
      },
    },
    runtimeEnvironment: {
      MY_VAR: 'hello-world',
    },
  },
});

Attach a persistent volume

Create a volume and mount it into the sandbox. This persists data across restarts and pauses.

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

Start the sandbox

Scale the service to 1 instance and poll until it's ready.

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

// Poll until the service is running
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;
    if (status === 'COMPLETED') return;
    if (status === 'FAILED') throw new Error('Sandbox deployment failed');

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

await waitForReady();

Execute commands inside the sandbox

Use the exec API to run commands. This opens a session into the running container and streams stdout/stderr.

const handle = await apiClient.exec.execServiceSession(
  {
    projectId: 'your-project-id',
    serviceId: sandboxId,
  },
  {
    shell: 'bash -c',
    command: "echo 'Hello from the sandbox!' && ls /workspace",
  }
);

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

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(''));

Expose a port (optional)

If your sandbox runs a web server, you can expose a port publicly.

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

Pause or destroy the sandbox

Pause the sandbox to stop billing while keeping the volume intact, or destroy it entirely.

// Pause — scales to 0, volume data persists
await apiClient.scale.service({
  parameters: {
    projectId: 'your-project-id',
    serviceId: sandboxId,
  },
  data: {
    instances: 0,
  },
});

// Destroy — removes the service entirely
await apiClient.delete.service({
  parameters: {
    projectId: 'your-project-id',
    serviceId: sandboxId,
  },
});

// Delete the volume
await apiClient.delete.volume({
  parameters: {
    projectId: 'your-project-id',
    volumeId: volumeId,
  },
});

© 2026 Northflank Ltd. All rights reserved.

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