Skip to main content

bsdkrun (Python SDK)

A Python SDK for bsdkrun — a Firecracker-style microVM launcher for BSD and Linux guests on macOS and Linux, built on libkrun. Boot and drive microVMs programmatically, inspired by the Vercel and Deno Sandbox SDKs.

It's a thin wrapper that shells out to the bsdkrun binary, so it has zero runtime dependencies — stdlib only, Python 3.10+.

from bsdkrun import Sandbox

box = Sandbox.create(os="linux", image="alpine")

# exec argv directly, with env / stdin / a PTY / a working dir:
print(box.exec(["uname", "-a"]).text())
box.exec(["apk", "add", "curl"])
box.run_command("curl", ["-fsSL", "https://example.com"])

box.stop()

Install

uv add bsdkrun     # or: pip install bsdkrun

Or from this repo:

uv add ./sdk/python     # or: pip install sdk/python

The bsdkrun binary

You need the bsdkrun binary itself. The SDK finds it via, in order:

  1. set_binary_path("/path/to/bsdkrun")
  2. the BSDKRUN_BIN environment variable
  3. bsdkrun on your PATH
  4. an in-repo target/release/bsdkrun or target/debug/bsdkrun build

See the bsdkrun README for installing the binary (Homebrew on macOS, or build from source on Linux/KVM). This SDK assumes libkrun is already provisioned — it does not auto-install it.

Creating a sandbox

Sandbox.create is keyed on os — the options change per guest kind:

# Linux OCI image (docker run-style)
Sandbox.create(
    os="linux",
    image="ghcr.io/owner/name:tag",
    cpus=2,
    mem=1024,
    volume="web",  # persistent CoW rootfs
    mounts=["~/project:/src", "~/data:/data:ro"],
    net={"ports": ["8080:80", "2222:22"]},
    command=["node", "server.js"],  # args after `--`
)

# FreeBSD (EFI on macOS, PVH on Linux/amd64)
Sandbox.create(os="freebsd", version="14.3", mem=2048)

# NetBSD (direct-kernel boot everywhere)
Sandbox.create(os="netbsd", version="10.1", volume="db")

# Boot a raw disk through its UEFI loader
Sandbox.create(os="firmware", firmware="KRUN_EFI.fd", disk="disk.raw")

# Boot a kernel directly, no bootloader
Sandbox.create(os="kernel", kernel="netbsd", format="elf", disk="root.raw")

Every create runs the machine detached and returns a Sandbox handle.

Running commands

Pass an argv list (no shell parsing), or a program name plus args:

box.exec(["ls", "-la", "/etc"])

box.exec(
    "node",
    args=["-e", "print(1)"],
    env={"X": "hi"},
    cwd="/app",
    stdin="data on stdin",
    tty=True,  # allocate a PTY
    throw_on_error=True,  # raise CommandFailed on a non-zero exit (default: False)
)

# Vercel-Sandbox-style alias:
result = box.run_command("uname", ["-a"])
print(result.stdout, result.exit_code)

exec returns a Result with .stdout, .stderr, .exit_code, .ok, and helpers .text(), .json(), .lines(), .throw_if_failed().

Lifecycle & inventory

box = Sandbox.create(os="linux", image="alpine", command=["sleep", "300"])
same = Sandbox.get(box.id)  # reconnect (prefix ok)
rows = Sandbox.list(all=True)  # list[SandboxInfo]

box.status()  # SandboxInfo | None
box.is_running()  # bool
box.logs()  # console log (str)
box.shell()  # interactive shell (inherits the terminal)
box.stop()  # BSD guests clean-poweroff; Linux SIGTERM
box.start()  # restart in place — resumes its own disk/rootfs (data persists)
box.update(cpus=4, mem=2048)  # applies on next start
box.remove(force=True)

Host-level namespaces:

from bsdkrun import images, volumes, networks, system

system.probe()  # toolchain sanity check
images.list()  # list[ImageInfo]
volumes.list()  # list[VolumeInfo]
volumes.remove("web", force=True)
networks.list()  # list[NetworkInfo]
system.fetch_image("freebsd", version="14.3")
system.versions("netbsd")

Global networks — reach machines by name

Opt machines into a shared network so they get distinct IPs on one subnet and reach each other by IP and by name (docker-compose style), with internal DNS:

from bsdkrun import Sandbox, networks

networks.create("devnet")

db = Sandbox.create(os="linux", image="postgres", name="db", net={"network": "devnet"})
api = Sandbox.create(os="linux", image="myapi", name="api", net={"network": "devnet"})

# `api` resolves `db` to its IP on devnet and pings it by name:
api.exec(["ping", "-c1", "db"], throw_on_error=True)

# inspect + manage
networks.list()  # list[NetworkInfo]
networks.members("devnet")  # list[SandboxInfo] on the network
info = db.status()  # info.network == "devnet", info.net_ip set

# edit membership (applies on next start — a VM's NIC is fixed at boot)
api.connect_network("devnet")  # or networks.connect(api.id, "devnet")
api.disconnect_network()
api.start()  # re-joins with the new membership

networks.sync("devnet")  # refresh members' /etc/hosts (fixes NetBSD name lookup)
networks.remove("devnet", force=True)

Names resolve on Linux and FreeBSD via the network's DNS; NetBSD resolves via a synced /etc/hosts block — joins auto-sync, and networks.sync refreshes an existing network without restarting members.

SSH & Tailscale

# agent-managed key-based SSH
box.ssh_setup()  # install local ~/.ssh/*.pub keys
box.ssh_setup(user="tsiry", key="~/.ssh/work.pub")

# put a guest on your tailnet
box.tailscale_up(authkey="tskey-auth-...", hostname="web")

Errors

All errors extend BsdkrunError:

  • BinaryNotFound — the bsdkrun binary wasn't found.
  • CommandFailed — a command exited non-zero (carries exit_code, stdout, stderr). Raised by exec when throw_on_error=True, by the lifecycle methods, and by the agent helpers.
  • SandboxNotFoundSandbox.get matched no machine.

Try it interactively

uv run console.py

Starts IPython with the SDK preloaded — Sandbox, the images / volumes / networks / system namespaces, and a ps() shorthand for Sandbox.list(all=True). Pass --bin ../../target/release/bsdkrun to drive a locally built binary for the session. Falls back to the stdlib REPL if IPython isn't installed.

Development

The SDK is developed with uv. From sdk/python:

uv sync            # create .venv and install the dev group
uv run pytest      # tests
uv run ruff check  # lint
uv run ruff format # format
uv run mypy        # type-check (strict)

The package itself has no runtime dependenciespytest, ruff, and mypy live in the dev dependency group and are never installed for consumers.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bsdkrun-0.1.0.tar.gz (49.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bsdkrun-0.1.0-py3-none-any.whl (17.7 kB view details)

Uploaded Python 3

File details

Details for the file bsdkrun-0.1.0.tar.gz.

File metadata

  • Download URL: bsdkrun-0.1.0.tar.gz
  • Upload date:
  • Size: 49.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for bsdkrun-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b6a0739061cf70dffbfe1e58ee734dc6106db0cebce14a2534b5b6d07513e0e4
MD5 7c2876477b3e9661507943bed9236380
BLAKE2b-256 6477be4bf5c0bab6a5c03ba58de0a3dd6a6a8e5d640a72721bf8541d7e993322

See more details on using hashes here.

File details

Details for the file bsdkrun-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: bsdkrun-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for bsdkrun-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9e37dbee7effd00fd1c829184fdbbd0bcfe37e397b36f5bcc254458cd5f880e5
MD5 ade4284efa83f007ad5ae7af137da442
BLAKE2b-256 eaca1173aff61d3df3e42203c77b97f31c9b23190673a1f50792bc716970b5a9

See more details on using hashes here.

Release history Release notifications | RSS feed

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page