Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Run a container or microVM

A compute workload runs a long-lived server — a container image or a microVM — behind a route, next to your static content and Wasm handlers. Use it when a Wasm handler is not enough: an existing container image, a language runtime, or code that needs a full OS. For the choice between a handler, a container, and a microVM, see Compute: handlers vs containers vs microVMs.

Compute backends are Linux-only and capability-detected at startup: a container backend where the host allows it, and a microVM backend where /dev/kvm exists. Enable compute by adding a compute: section to boatramp.cfg (see the schema).

Provision a kernel

Every workload boots in a microVM, which needs a kernel as well as a root filesystem. Supply a Firecracker-compatible uncompressed Linux kernel (vmlinux) — build one, or use a released microVM kernel — provisioned once and shared across every workload.

--kernel (like --tar / --rootfs) accepts any of three forms: a local file, a URL, or a blob hash already in the store. Point it straight at a file or URL and the CLI uploads it for you:

boatramp compute build web --image nginx:1.27 --kernel ./vmlinux --port 80
# or a URL:
boatramp compute build web --image nginx:1.27 \
  --kernel https://example.com/vmlinux-6.1 --port 80

To upload a kernel once and reuse its hash across commands, use blob put:

boatramp blob put ./vmlinux
1a2b3c4d…    # the content-address; pass it as --kernel 1a2b3c4d…

The kernel and its trust

You do not have to pass --kernel on every workload. A node has a fleet default kernel — a dynamic setting you change without a restart. boatramp distributes a first-party signed microVM kernel (boatramp-vmlinux); set it up once by uploading the released vmlinux as a blob and pointing the default kernel at that content hash:

# 1. fetch the signed release (kernel + its .sha256 + .sig) and upload the kernel as a blob
base=https://github.com/BoatRamp/boatramp-vmlinux/releases/latest/download
curl -fsSLO "$base/boatramp-vmlinux-x86_64"
curl -fsSLO "$base/boatramp-vmlinux-x86_64.sha256"
curl -fsSLO "$base/boatramp-vmlinux-x86_64.sig"
boatramp blob put boatramp-vmlinux-x86_64        # prints the blob hash == its sha256

# 2. point the fleet default at it (source = the blob hash; sha256 + sig from the release
#    artifacts, so this stays correct across releases)
boatramp config set compute.default_kernel "{
  \"source\": \"$(cat boatramp-vmlinux-x86_64.sha256)\",
  \"sha256\": \"$(cat boatramp-vmlinux-x86_64.sha256)\",
  \"sig\":    \"$(cat boatramp-vmlinux-x86_64.sig)\"
}"

source is the blob hash the backend stages (not the release URL). A workload that omits --kernel uses this default. Changing it retargets new microVMs and reboots; in-flight guests keep their kernel until they cycle.

The kernel is verified before boot, scaled by the security posture:

  • Always: the kernel bytes must hash to the pinned sha256 — a mismatch never boots.
  • multi-tenant (strict): the hash must be on the static [compute].kernel_allowed_hashes allow-list and carry a signature verifying against a static [compute].kernel_signing_pubkeys key. So an admin token can only select a kernel the host operator pre-vetted and signed — never introduce a new one.
  • single-tenant / dev: a verified hash pin suffices.

boatramp ships a first-party signing public key built in, so the signed default kernel it distributes verifies out of the box. boatramp security explain shows the resolved kernel-trust bar.

Kernels are per guest-arch (macOS vmm-vz)

The guest kernel matches the backend’s guest architecture: the Linux/KVM embedded VMM boots an x86_64 vmlinux, while the macOS Virtualization.framework backend (vmm-vz, Apple silicon) boots a raw arm64 Image. An x86_64 kernel can’t boot an arm64 VM, so [compute].kernel_allowed_hashes is arch-scoped — an Apple-silicon node trusts only boatramp-vmlinux-aarch64 releases, an x86_64 node only the x86_64 ones — and --kernel / compute.default_kernel on macOS must point at an arm64 kernel (the release’s boatramp-vmlinux-aarch64 asset, or any uncompressed arm64 Image). Everything else — --kernel, the fleet default, verify-before-boot — is identical. Under single-tenant / dev the content-hash pin alone suffices, so vmm-vz runs with any operator-supplied arm64 kernel; the strict posture on Apple silicon needs the signed boatramp-vmlinux-aarch64 release (its hash is baked into the arch-scoped allow-list on release). An operator-supplied arm64 kernel must enable the generic PCIe host + virtio-pci (CONFIG_PCI, CONFIG_PCI_HOST_GENERIC, CONFIG_VIRTIO_PCI): Virtualization.framework presents its virtio disk/net/console over a PCIe host bridge, so a CONFIG_PCI-off kernel finds no devices and never boots. The boatramp-vmlinux-aarch64 release is built this way.

Deploy a container image

compute build takes an OCI image reference, builds an ext4 root filesystem from it, uploads it, and registers the workload in one step. It needs the mke2fs tool (e2fsprogs) on the host and a kernel blob provisioned once.

boatramp compute build web \
  --image nginx:1.27 \
  --kernel <vmlinux-blob-hash> \
  --port 80 \
  --vcpus 1 --mem-mib 256 --replicas 2
built ext4 rootfs from nginx:1.27 (1024 MiB) — blob sha256:1a2b…
workload web set: 2 replicas, port 80, isolation trusted

The scheduler places the replicas on nodes that advertise compute capacity and reconciles them toward the desired count. Check status:

boatramp compute ls
NAME  REPLICAS  PORT  ISOLATION  STATE
web   2/2       80    trusted    Healthy

Choose the isolation level

--isolation decides which backend may run the workload:

--isolationRuns onUse for
trusted (default)a container (shared kernel) or a microVMyour own images
untrusteda microVM only (never a shared kernel)third-party or tenant code
boatramp compute build tenant-app --image ghcr.io/acme/app:1.4 \
  --kernel <vmlinux-blob-hash> --port 8080 --isolation untrusted

Under the strict multi-tenant security posture, shared-kernel (container) compute is disabled, so every workload runs in a microVM regardless of --isolation. See Choose a security posture.

Set a workload from an existing source

compute set registers a workload from a root-filesystem source — exactly one of, matched to the substrate you want:

  • --image <ref> — an OCI image reference the runtime pulls (docker / cloudflare).
  • --tar <hash|file|url> — a tar rootfs archive the native container runtime unpacks.
  • --rootfs <hash|file|url> — a rootfs filesystem image (a block device; ext4 by default) the firecracker micro-VM attaches.
# A registry image on the docker backend (e.g. a database):
boatramp compute set pg --image pgvector/pgvector:pg16 --port 5432 --env POSTGRES_PASSWORD=pw

# A pre-built ext4 rootfs + kernel on the micro-VM backend:
boatramp compute set api \
  --rootfs <rootfs-blob-hash> --kernel <vmlinux-blob-hash> \
  --port 8080 --replicas 3 \
  --entrypoint /usr/bin/api --env LOG=info

Inspect a workload’s desired state:

boatramp compute get api

Docker workloads: read-only root, writable root, and volumes

A docker (or native-container) workload runs hardened by default: a read-only root filesystem, all Linux capabilities dropped, no privilege escalation, and a PID cap. The idiomatic path for app writes is a persistent volume, not a writable root — attach one (in-guest mount → named backing) via the API or a project.cfg manifest, and the data persists across restarts.

For an image that insists on writing outside a declared volume, --writable-root relaxes only the read-only-root default (every other hardening stays on):

boatramp compute set legacy-app --image acme/legacy:1 --port 8080 --writable-root

--writable-root is honored only under the single-tenant security posture — the strict multi-tenant guard forces the hardened read-only root back on (and, being shared-kernel, won’t place the workload on docker at all). See Choose a security posture.

How the docker backend stores a volume is set by [compute].docker_volume_mode: named (default) uses a daemon-managed docker volume (portable across daemons and Docker Desktop / macOS); bind uses a host directory under <data_dir>/compute/volumes/<name> (local daemon only). Either way the volume is node-local — it is not part of the blob-snapshot durability story the microVM backend’s volumes get, and does not follow a workload across nodes.

Running a stock image that needs privileges (e.g. a database)

Because every capability is dropped, a stock image whose entrypoint runs as root and then chowns a data dir and drops to its own user (the classic postgres / mysql init) can’t initialize on the shared-kernel backends out of the box — the chown needs CAP_CHOWN/CAP_FOWNER and the privilege-drop needs CAP_SETUID/CAP_SETGID. Two ways to make it work, cleanest first:

Run it rootless (preferred). Point the entrypoint at the image’s own DB user with --user, backed by a persistent volume boatramp pre-owns for that uid — the entrypoint then skips both the chown and the privilege-drop, so it needs no capabilities and works under any posture:

boatramp compute set pg --image postgres:16 --port 5432 \
    --user 999:999 --volume pgdata:/var/lib/postgresql/data

Add back the capabilities (fallback). For an image that won’t run rootless, --cap-add grants specific capabilities on top of the dropped-ALL default. It is honored only under the single-tenant posture (the multi-tenant guard strips it, same as --writable-root); on the native-container backend the caps are bounded by the workload’s user namespace:

boatramp compute set pg --image postgres:16 --port 5432 \
    --cap-add CHOWN --cap-add DAC_OVERRIDE --cap-add FOWNER \
    --cap-add SETUID --cap-add SETGID \
    --volume pgdata:/var/lib/postgresql/data

Managed databases do this for you. When a handler sql binding is sourced from a database boatramp runs (see Managed SQL), boatramp applies a privilege strategy automatically — no --user/--cap-add needed. The strategy is [compute].managed_db_privilege: rootless (the default — run as the image’s DB user against its pre-owned volume, no capabilities, any posture) or caps (add the minimal set; single-tenant only).

Next steps