Self-hosting quickstart
There’s no hosted sandkiln service — every instance is self-hosted. This page is a complete, step-by-step path from a bare Linux host to a real daemon booting real microVMs, including the specific failures people actually hit and how to fix them. For the full section-by-section reference (every environment variable, the networking internals, running as a persistent systemd service), see SELF_HOSTING.md in the repository — this page is the condensed, hands-on version of it.
1. Check your host actually supports this
Section titled “1. Check your host actually supports this”- Linux, x86_64. Nothing here is built or verified for aarch64.
/dev/kvm, readable and writable by your user. Bare metal or a VM with nested virtualization enabled both work — check with:If it’s not there at all, your host (or your cloud VM’s instance type) doesn’t have KVM/nested virtualization enabled — this is a hard requirement, not something to work around. If it’s there but you can’t read/write it:Terminal window ls -la /dev/kvmTerminal window sudo usermod -aG kvm $USER# then log out and back in -- group membership doesn't apply to an already-open sessionsudo, for one-time host setup only. The daemon itself never runs as root once it’s running — see TAP devices and bridge networking for exactly why.- Rust (via rustup), plus the musl target for the guest agent:
Terminal window rustup target add x86_64-unknown-linux-muslsudo apt install musl-tools # Debian/Ubuntu; adjust for your distro
Run scripts/preflight-check.sh at any point — it reads the same SANDKILN_* environment variables the daemon itself uses and tells you exactly what’s missing, before you try to start anything.
2. Clone and build
Section titled “2. Clone and build”git clone https://github.com/SumitKumar-17/sandkiln.gitcd sandkiln/corecargo build --release --workspaceThis builds sandkilnd (at core/target/release/sandkilnd) and the libraries it depends on. It does not build the guest agent yet — that happens automatically in the next step, cross-compiled for the guest’s musl target.
3. One command: build the kernel/rootfs, wire up networking, start the daemon
Section titled “3. One command: build the kernel/rootfs, wire up networking, start the daemon”cd .. # back to the repo rootscripts/setup.shscripts/sandkilnd-ctl.sh startsetup.sh is idempotent (safe to re-run) and does everything that used to be a dozen manual steps with paths that had to match by hand: fetches a known-good kernel and a small test rootfs, injects the guest agent into it, creates a persistent tap device pool for sandbox networking, and grants the daemon CAP_NET_ADMIN — the one Linux capability it needs, not root.
That’s a real, working daemon end to end — but booting from the small Firecracker CI test image (~300MiB, missing ca-certificates and any language runtime, fine for proving the stack works, not for real workloads). For a production image with Ubuntu, current Node.js/Python, and common tooling (needs sudo, ~8GiB free disk, and several minutes):
scripts/setup.sh --production3b. Remote storage mounts (optional)
Section titled “3b. Remote storage mounts (optional)”Skip this unless you want sandboxes to mount an S3-compatible bucket into their own filesystem (POST /sandboxes/:id/mounts — see Remote storage mounts). Nothing above sets it up, and the feature needs two extra pieces.
A guest kernel built with CONFIG_FUSE_FS. Firecracker’s own default/CI kernel builds — including the one setup.sh fetches — don’t enable FUSE, and guest kernels can’t load modules at runtime, so it has to be compiled in:
sudo apt-get install -y build-essential flex bison libelf-dev bcimages/build-guest-kernel.sh 5.10.223 ~/sandkiln-tools/images/vmlinux-5.10.223-fuseIt takes a few minutes. Point the daemon at the result with SANDKILN_KERNEL_PATH=~/sandkiln-tools/images/vmlinux-5.10.223-fuse — that replaces the default kernel for every sandbox, not just ones using mounts, so there’s no reason not to use the FUSE-enabled one everywhere once you’ve built it.
rclone and fusermount3 baked into the rootfs, injected as binaries the same way the guest agent is (rclone mount is what actually talks to the bucket from inside the guest). Download a static rclone build for your architecture, then:
scripts/dev.sh inject-rclone <path-to-a-static-rclone-binary> [rootfs-path]rootfs-path defaults the same way the guest-agent injection does. fusermount3 comes along from this host’s own /bin/fusermount3 — install the fuse3 package here if it’s missing. rclone’s Linux FUSE backend always execs it to perform the mount, even though the guest agent runs as root, so it isn’t optional.
There’s no preflight check for either piece: the mount routes are always registered, so a host missing them fails at mount time with a 400 carrying rclone’s own error output.
4. Verify it worked
Section titled “4. Verify it worked”curl http://127.0.0.1:7777/healthz# ok
curl -X POST http://127.0.0.1:7777/sandboxes -d '{}'# {"id": "..."}Got an id back? The daemon is real and working. Move on to your language of choice: JS/TS, Python, or the CLI.
5. Before exposing this beyond localhost
Section titled “5. Before exposing this beyond localhost”Set an auth token — with none set, every route except /healthz and /metrics is completely open to anyone who can reach the port (create sandboxes, read/write files inside them, delete persistent drives). The daemon logs a startup warning specifically so this is never silent.
SANDKILN_AUTH_TOKEN=$(openssl rand -hex 32) scripts/sandkilnd-ctl.sh restartEvery client (both SDKs, the CLI, raw HTTP) sends this back as Authorization: Bearer <token> — see Auth. This is a single shared secret, not a multi-tenant identity system — fine for a self-hosted, single-operator daemon, not a substitute for per-caller scoping (not implemented yet).
When something goes wrong
Section titled “When something goes wrong”setup.shfails with “a terminal is required to read the password” — it ran non-interactively (e.g. overssh host 'cmd') and asudostep had no TTY to prompt on. Run it from a real interactive shell, orsudo -vfirst to cache credentials before running it non-interactively./dev/kvm: permission denied—sudo usermod -aG kvm $USER, then log in again.- “Operation not permitted” on any network call — you rebuilt the daemon and are running it manually (not via
sandkilnd-ctl.sh, which handles this) and need to re-grantCAP_NET_ADMIN:sudo scripts/host-setup/grant-net-admin.sh core/target/release/sandkilnd. tap devices missing: [...]at startup —SANDKILN_TAP_POOL_PREFIX/SANDKILN_TAP_POOL_SIZEdon’t match what was actually created, or the pool was created for a different user than the daemon runs as.- Sandboxes create fine but
exec/read/write time out or fail with a vsock error — the guest agent isn’t baked into whatever rootfs is configured. Runsudo -E scripts/preflight-check.sh --root-checksto confirm. - Sandboxes boot but have no outbound network — check
ip route show defaultpicked the right interface, and confirmscripts/host-setup/start-dns-proxy.shis actually running (DNS and raw IP connectivity are independent failure modes — check both separately). preflight-check.sh --root-checksreports missing binaries you know are installed — you ran it with plainsudo, which resets$HOMEto/rootand breaks every~/sandkiln-tools/...default path. Usesudo -Einstead.
The repository’s SELF_HOSTING.md covers more (production image build failures, jailer setup, running as a systemd service, the full environment-variable reference) if you hit something not listed here.