# Migrate from Northflank Sandboxes to Sandboxer

> 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.

Source: https://sandboxer.com/migrate/northflank. Updated 2026-10-27.

**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](https://northflank.com/docs/v1/application/sandboxes)). 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](https://northflank.com/docs/v1/application/sandboxes/examples)). Sandboxer has no GPU sizes ([Machines](/docs/machines)).
- **Bring your own cloud.** Northflank sandboxes can run in your AWS, GCP, Azure or Oracle account, or on bare metal ([own cloud](https://northflank.com/docs/v1/application/sandboxes/sandboxes-in-your-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](https://northflank.com/product/sandboxes)). Sandboxer has four fixed sizes ([Machines](/docs/machines)).

## Concept map

Facts about Northflank come from its [sandbox docs](https://northflank.com/docs/v1/application/sandboxes) 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](/docs/machines)) |
| Project (`project_id` on every call), region fixed at creation | No project. An API key, plus an optional organization ([Organizations](/docs/organizations)). Each sandbox's region is set at create with `region` ([regions](/docs/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](/docs/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](/docs/snapshots#resume)) |
| 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](/docs/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](/docs/ssh)) |
| API token (`NF_API_TOKEN`) | API key (`SANDBOXER_API_KEY`) ([API keys](/docs/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](https://northflank.com/docs/v1/application/sandboxes) and [manage page](https://northflank.com/docs/v1/application/sandboxes/configure-and-manage-sandboxes). Ours are from `sandboxer-sdk` 0.1.1 ([Python SDK](/docs/sdk-python)), 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](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):

```python
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:

```python
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:

```text
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:

```python
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="")
```

```text
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](https://northflank.com/docs/v1/api/copy-files)). Send them to the new sandbox with `sbx scp`:

```bash
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](/docs/long-running#setup-scripts), [Snapshots](/docs/snapshots#named-snapshots-templates)). 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](/docs/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](/docs/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](https://northflank.com/docs/v1/application/sandboxes/configure-and-manage-sandboxes)). 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](/docs/snapshots#resume)).
- **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](/docs/machines), [Desktop](/docs/desktop)).
- **Agent tooling.** Coding agents are preinstalled, with a prompt API and an MCP server ([Agents](/docs/agents), [MCP](/docs/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](https://northflank.com/product/sandboxes)). 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](/docs/billing#plans)). 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](/docs/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](/docs/security)).

## Pricing

Northflank's rates are from its [pricing page](https://northflank.com/pricing), 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](/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](/docs/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](/docs/sdk-typescript)).

## Related

- [Sandboxer vs Northflank Sandboxes](/compare/northflank-sandboxes)
- [Quickstart](/docs/quickstart)
- [Python SDK](/docs/sdk-python)
- [Snapshots](/docs/snapshots)
