Generate an API token
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,
},
});