Skip to content
Migrate · Northflank

Migrate from Northflank Sandboxes to Sandboxer

Updated October 27, 2026 .mdSandboxer vs Northflank

Short answer: Replace Northflank's service calls with sandboxer-sdk. create.service.deployment plus a wait becomes one create call, run_service_command becomes exec, pause and resume become stop and resume, and public ports become hosted HTTPS URLs. Stay on Northflank for GPUs, your own cloud or managed databases.

This page covers Northflank Sandboxes: the microVMs you create through Northflank's API to run agent code (Northflank docs). It doesn't cover the rest of Northflank's platform. These Northflank features have no Sandboxer equivalent, so keep them on Northflank if you rely on them:

  • GPUs. Northflank runs GPU sandboxes (H100 and RTX PRO 6000 in its examples) with gVisor isolation (examples). Sandboxer has no GPU sizes (Machines).
  • Bring your own cloud. Northflank sandboxes can run in your AWS, GCP, Azure or Oracle account, or on bare metal (own cloud). Sandboxer is hosted only, on servers we operate.
  • Services, jobs and managed databases. Northflank is a general platform. Sandboxer runs sandboxes only. Your Northflank databases and services can stay where they are and be reached from a sandbox over the internet.
  • Very large machines. Northflank lists up to 288 vCPU and 2 TB of memory per sandbox (product page). Sandboxer has four fixed sizes (Machines).

Concept map#

Facts about Northflank come from its sandbox docs and the pages linked below, checked on 2026-10-27.

Northflank Sandboxer
Sandbox: a deployment service in a project, a microVM for CPU work (gVisor for GPU) Sandbox: a Cloud Hypervisor microVM with its own kernel (Machines)
Project (project_id on every call), region fixed at creation No project. An API key, plus an optional organization (Organizations). Each sandbox's region is set at create with region (regions)
Service name as the id (sandbox-1a2b3c4d) Generated id (sb_…), plus an optional name
Any container image (external.imagePath), with an entrypoint that keeps it up Ubuntu 24.04 VM with systemd and Docker; add software with a setup script or a named snapshot
Deployment plan (nf-compute-10 to nf-compute-2000) Four sizes: small, default, large, xlarge
runtimeEnvironment env={...} per sandbox, or an environment for every sandbox (Environments)
Pause: compute billing stops; processes end; ephemeral storage is wiped Stop: billing stops; memory, processes and disk are saved; resume brings them back on a machine with the same CPU type, and otherwise boots from disk with files kept (Snapshots)
Volume (create.volume, volumesToAttach), billed while it exists The sandbox's own disk, kept across stop and resume; named snapshots to share a starting point
Public port (update.service.ports, public: true) with a DNS name Hosted port (sb.host(port)): https://<slug>-<port>.box.sandboxer.com (Hosting)
Private port, reachable from services in the same project Private hosted URL with a _token (the default)
TCP and UDP ports SSH over a forwarded IPv4 port, and the sandbox's public IPv6 address (SSH)
API token (NF_API_TOKEN) API key (SANDBOXER_API_KEY) (API keys)
JavaScript and Python clients, the northflank CLI Python and TypeScript SDKs (sandboxer-sdk), the sbx CLI and the REST API

API and SDK mapping#

Northflank's calls are from its Python client as shown in its sandbox docs and manage page. Ours are from sandboxer-sdk 0.1.1 (Python SDK), which we ran on prod for this page.

Northflank (Python) Sandboxer (Python) sbx CLI
ApiClient(api_token=os.environ["NF_API_TOKEN"]) Sandboxer() (reads SANDBOXER_API_KEY) sbx login or SANDBOXER_API_KEY
client.create.service.deployment(project_id=..., data={...}) client.create(type="small", env={...}) sbx new --type small -e KEY=value
client.helpers.wait_for_service_ready(...) Not needed: create returns a ready sandbox sbx new --json emits a ready event
client.exec.run_service_command(..., command=..., shell="/bin/bash -c") sb.exec("...") (a shell string or a list) sbx exec <id> "..."
result.exit_code, result.stdout, result.stderr run.exit_code, run.stdout, run.stderr exit code of sbx exec
client.exec.open_service_session(...) sb.exec_stream(...), or SSH sbx ssh <id>
northflank upload service file --localPath ... --remotePath ... sb.files.write(path, data) sbx scp ./file <id>:/home/user/
northflank download service file ... sb.files.read(path), sb.files.download(path) sbx scp <id>:/home/user/file .
client.update.service.ports(...) with public: True, then read dns sb.host(8080, public=True).url sbx host <id> 8080 --public
client.pause.service(...), wait_for_service_paused(...) sb.stop() sbx stop <id>
client.resume.service(..., data={}) sb.resume() sbx resume <id>
client.scale.service(..., data={"deploymentPlan": "nf-compute-400"}) Pick a size at create; grow it on resume (a size change boots from disk; sizes can't shrink) sbx resume <id> --type large
client.delete.service(...) sb.delete() sbx delete <id> --yes

Two habits change. Northflank addresses every call by project and service id; ours is a method on the sandbox object. And a Northflank pause ends processes and wipes ephemeral storage, while ours saves them, so you can drop the volume you attached only to survive a pause.

Before and after#

From Northflank's docs: https://northflank.com/docs/v1/application/sandboxes (the Python examples for create, wait and run, checked on 2026-10-27; comments, the storage block and the separate scale call removed to fit):

client = ApiClient(api_token=os.environ["NF_API_TOKEN"])
client.create.service.deployment(project_id="your-project-id", data={
    "name": 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"}},
    "runtimeEnvironment": {"MY_VAR": "hello-world"},
})
client.helpers.wait_for_service_ready(project_id="your-project-id", service_id=sandbox_id, timeout_s=300)
result = client.exec.run_service_command(project_id="your-project-id", service_id=sandbox_id,
    command="echo 'Hello from sandbox!' && ls /", shell="/bin/bash -c")
print("Exit code:", result.exit_code)
print("Stdout:", result.stdout)

The same steps on Sandboxer. nf-compute-200 is 2 vCPU and 4 GB, which matches our small. Install with pip install sandboxer-sdk and set SANDBOXER_API_KEY. There is no project id, image entrypoint or readiness poll:

from sandboxer import Sandboxer

client = Sandboxer()
sb = client.create(type="small", name="aeo-migrate-nf-1", env={"MY_VAR": "hello-world"})

result = sb.exec("echo 'Hello from sandbox!' && echo $MY_VAR && ls /")

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

print(sb.id, sb.delete().status)

We ran it on prod on 2026-10-27. The whole script, from creating the VM to deleting it, took 3.8 seconds in that one run:

Exit code: 0
Stdout: Hello from sandbox!
hello-world
bin
bin.usr-is-merged
boot
…
usr
var

Stderr: 
sb_seiy9ykbw0 pending

delete() returns a deletion operation that finishes in the background, so its status is pending at first.

Ports, pause and resume#

A Northflank sandbox often serves a port and gets paused between tasks. This script does both on Sandboxer. We ran it on prod:

import time, urllib.request
from sandboxer import Sandboxer

client = Sandboxer()
sb = client.create(type="small", ttl_seconds=1800, name="aeo-migrate-nf-2")
print("created", sb.id, sb.state)

sb.files.write("workspace/notes.txt", "kept on disk\n")
sb.start("python3 -m http.server 8080", cwd="workspace")
time.sleep(1)
h = sb.host(8080, public=True)
print("hosted:", h.url)
print("ls:", sb.exec("ls -l workspace; ss -ltn | grep 8080").stdout, end="")
def get(path, tries=10):
    for i in range(tries):
        try:
            return urllib.request.urlopen(h.url + path, timeout=10).read().decode()
        except Exception as e:
            print("  try", i + 1, e); time.sleep(2)
print("GET /notes.txt:", get("/notes.txt"), end="")
print("pid before:", sb.exec("pgrep -f 'http.server 8080'").stdout, end="")

t = time.time(); sb.stop(); print("stopped", sb.state, f"{time.time()-t:.1f}s")
t = time.time(); sb.resume(); print("resumed", sb.state, f"{time.time()-t:.1f}s")
print("pid after:", sb.exec("pgrep -f 'http.server 8080'").stdout, end="")
print("file after:", sb.files.read("workspace/notes.txt"), end="")
print("GET after resume:", get("/notes.txt"), end="")
print("docker:", sb.exec("docker --version").stdout, end="")
created sb_k8jbeua7ua ready
hosted: https://icy-chief-reef-8080.box.sandboxer.com
ls: total 4
-rw-r--r-- 1 user user 13 Oct 27 03:06 notes.txt
LISTEN 0      5            0.0.0.0:8080      0.0.0.0:*
GET /notes.txt: kept on disk
pid before: 549
stopped archived 0.6s
resumed ready 0.6s
pid after: 549
file after: kept on disk
GET after resume: kept on disk
docker: Docker version 29.9.0, build f415da8

What this shows:

  • The file was written to the sandbox's own disk, with no volume attached, and was still there after stop() and resume(). On Northflank, a file on ephemeral storage is wiped by a pause.
  • The web server kept the same PID (549) across the stop and resume, and its URL answered right after. On Northflank, a pause ends running processes.
  • The stop and the resume each took 0.6 seconds in this run.
  • host() doesn't replace other ports. Each call sets one port.
  • In an earlier run of a version without the get retry, the first request right after host() returned 404. The retry helper is there for that window.
  • Docker is installed, so a container image you ran on Northflank can run inside the sandbox.

Move your data and environment#

Files. Copy files out of the Northflank sandbox with northflank download service file (it needs tar in the sandbox), or read them from the attached volume (copy files). Send them to the new sandbox with sbx scp:

tar czf proj.tgz proj
sbx scp ./proj.tgz sb_k8jbeua7ua:/home/user/                        # exit 0
sbx exec sb_k8jbeua7ua "tar xzf proj.tgz && python3 proj/main.py"   # hi from my northflank project
sbx scp sb_k8jbeua7ua:proj/main.py ./back.py                        # and back out, exit 0

For a directory, use sbx scp -r. The SDK's files.write and files.read take files up to 5 MiB, and files.download up to 50 MiB.

Volumes. A Northflank volume exists to keep data through pauses and restarts. On Sandboxer the sandbox's disk already survives stop and resume, so data under /home/user stays. To start many sandboxes with the same data, put it in a named snapshot and create them from_= it. Our docs don't describe a volume shared by several running sandboxes, so a ReadWriteMany volume has no direct match: keep shared data in object storage or a database and fetch it.

Images. Northflank runs any container image. Sandboxer runs a full Ubuntu 24.04 VM with Docker installed, so you have two paths. Run your image inside the sandbox with docker run. Or turn the Dockerfile into a shell script (RUN apt-get install -y x becomes sudo apt-get install -y x), pass it as setup_script= (up to 64 KB), then save the result once with sb.snapshot("my-stack") and start later sandboxes with client.create(from_="my-stack") (Setup scripts, Snapshots). You don't need a sleep infinity entrypoint: the VM stays up until it stops.

Environment variables and secrets. Move runtimeEnvironment to env={...} on create. For values every sandbox should get, use an environment: sbx env set-var base KEY=value, or POST /secrets for secret files (Environments).

Ports. Northflank's port update replaces the whole port list. Here each port is hosted on its own with sb.host(port) or sbx host <id> <port>, at any time. URLs are private with a _token unless you pass public=True, and they keep their name across stop and resume. UDP ports have no hosted equivalent; use the sandbox's IPv6 address after sudo ufw allow <port> (Hosting).

Plans and scaling. Northflank's scale.service changes the plan and restarts the sandbox. On Sandboxer, stop the sandbox and grow it on resume with sbx resume <id> --type large; a size change boots from disk, so files stay and running processes don't. Moving to a smaller size isn't supported yet: to go smaller, create a new sandbox of that size and copy your files.

What's different#

What you gain:

  • Pause keeps memory. Northflank's pause ends running processes and wipes ephemeral storage (manage page). In our run, the web server kept the same PID across stop() and resume(), and the file was still on disk. Memory is restored when the resume lands on a machine with the same CPU type; otherwise the sandbox boots from disk and reports memoryRestored: false (Snapshots).
  • Fewer calls. One create returns a ready VM. You don't poll for COMPLETED, pick an entrypoint, or pass a project id.
  • A full machine. Every sandbox has root, systemd, Docker, /dev/kvm for nested VMs and a streamed desktop with Chrome (Machines, Desktop).
  • Agent tooling. Coding agents are preinstalled, with a prompt API and an MCP server (Agents, MCP).

What you lose or must change:

  • GPUs, your own cloud, databases and very large machines, as listed at the top of this page.
  • Any image as the root filesystem. On Northflank the image is the sandbox. Here the base is Ubuntu, and your image runs in Docker inside it.
  • Concurrency and start rate. Northflank lists 50,000+ concurrent sandboxes (product page). Our plans run 100 to 2,000 at once and limit starts, from 12 a minute and 200 a day on the $20 plan (Billing). Check with sbx limits.
  • Session length during the trial. Northflank sandboxes run until deleted. Ours stop after ttl_seconds (default 1 hour); on a paid plan you can set ttl_seconds=None to turn auto-stop off, and the trial allows up to 8 hours (Long-running).
  • UDP and project-private networking. Northflank exposes TCP and UDP ports, and private ports reach other services in a project. We host TCP ports over HTTPS and don't document private networking between sandboxes; our docs say sandboxes can't reach each other (Security).

Pricing#

Northflank's rates are from its pricing page, checked on 2026-10-27: $0.01667 per vCPU-hour and $0.00833 per GB-hour on Northflank Cloud, billed per second, with ephemeral storage included. Persistent volumes are $0.11657 per GiB-month. Ours are from /pricing.

Machine Northflank Sandboxer
4 vCPU, 8 GB, one hour nf-compute-400: $0.13/hour listed (about $0.1333 at the per-unit rates) default: $0.036/hour
2 vCPU, 4 GB, one hour nf-compute-200: $0.06667/hour small: $0.018/hour
Paused or stopped Compute stops; an attached volume is still billed Not billed; counts toward included storage (50 GB on the $20 plan)

One 4 vCPU, 8 GB sandbox running 8 hours a day for 22 days is 176 hours: about $22.88 on Northflank at the plan rate, and $6.34 on Sandboxer. Northflank also has a free tier with up to $20 a month of resources, no card needed, and BYOC billed at $0.02083 per node vCPU-hour; its GPU prices have no counterpart here. Sandboxer plans start at $20 a month, paid back as machine time, with a 7-day trial of 25 hours (card required).

FAQ#

Can I keep Northflank for GPUs and databases and move only CPU sandboxes?#

Yes. Sandboxer has no GPUs, managed databases or BYOC, so keep those on Northflank. A Sandboxer sandbox has outbound internet, so it can call a Northflank service or database that you expose to it.

Do I still need a volume to keep files when I pause?#

No. A Sandboxer stop saves the disk and memory. Resume brings both back on a machine with the same CPU type; otherwise it boots from disk, so files stay and running processes don't. Use a named snapshot if many new sandboxes should start with the same files.

Can I run my existing container image?#

Yes, inside the sandbox: Docker is installed in every VM, so docker run your-image works. The VM itself is always Ubuntu 24.04. To skip the image pull on every start, pull it once and save a named snapshot.

What replaces the project id?#

Nothing. The API key identifies your account. To bill and share sandboxes as a team, create an organization and pass org= to the client (Organizations).

Is there a JavaScript client?#

Yes. sandboxer-sdk on npm has the same calls in camelCase, like sandboxer.create({ type: "small" }) and sandbox.exec(...) (TypeScript SDK).

All migration guides