Installation
Gondolin runs on macOS and Linux, on arm64 and x86_64. A working setup has three parts:
- the
@earendil-works/gondolinnpm package (SDK andgondolinCLI), which needs Node.js - the Gondolin daemon (
gondolind), a static binary that runs the VM; it is installed with the npm package as an optional platform package - a VM backend: QEMU, and optionally the krun runner (libkrun), which is the default wherever it can run
Guest images are not part of the installation: the default image is downloaded and cached on first use (see Images).
Requirements
| macOS | Linux | |
|---|---|---|
| Node.js | 22.19 or newer | 22.19 or newer |
QEMU (incl. qemu-img) |
brew install qemu |
qemu-system-arm (arm64) or qemu-system-x86 (x86_64), plus qemu-utils |
| krun (optional) | Apple silicon; runner ships with the npm package | read/write access to /dev/kvm; runner ships with the npm package on x86_64, build it on arm64 |
| Gondolin daemon | npm platform package (arm64, x64) | npm platform package (arm64, x64; static binaries) |
QEMU is needed on both backends: it is the VM backend wherever krun is not
available, and its qemu-img creates the root disk overlays for krun VMs as
well. Node.js is not needed for the static CLI.
Install the Package
Run the CLI without installing anything globally:
npx @earendil-works/gondolin bashInstall the CLI globally:
npm install -g @earendil-works/gondolin
gondolin bashAdd the SDK to a project:
npm install @earendil-works/gondolinThe package declares platform packages as optional dependencies; the package manager installs only the ones matching your machine:
| Package | Contents | Platforms |
|---|---|---|
@earendil-works/gondolin-native-<platform> |
bin/gondolin: the Gondolin daemon and the static CLI |
darwin-arm64, darwin-x64, linux-arm64, linux-x64 |
@earendil-works/gondolin-krun-runner-<platform> |
gondolin-krun-runner with a bundled libkrun |
darwin-arm64, linux-x64 |
The Linux daemon builds are static (musl) binaries. Keep the platform packages
at the same version as @earendil-works/gondolin.
Install QEMU
macOS (Homebrew):
brew install qemu nodeDebian/Ubuntu, arm64:
sudo apt install qemu-system-arm qemu-utils nodejs npmDebian/Ubuntu, x86_64:
sudo apt install qemu-system-x86 qemu-utils nodejs npmDistribution Node.js packages are often older than 22.19; check
node --version and install a newer Node.js if needed.
qemu-img is looked up next to the QEMU binary, then on PATH. On Linux,
QEMU uses KVM when /dev/kvm is accessible and falls back to software
emulation (TCG, much slower) otherwise.
How fast guest file access is depends on the QEMU build: with vhost-user
support (qemu-system-aarch64 -device help | grep vhost-user-fs lists
vhost-user-fs-pci), the guest mounts its files over virtio-fs. Linux
distribution builds have it; Homebrew's QEMU does not, so QEMU on macOS uses
the slower sandboxfs transport (see
QEMU Backend). krun VMs use virtio-fs on
both systems.
krun
The krun backend (libkrun through the gondolin-krun-runner helper) is used
by default when all of these hold, and QEMU otherwise:
- the host supports libkrun: macOS on Apple silicon, or Linux on arm64/x86_64
with read/write access to
/dev/kvm(libkrun has no software emulation) - a krun runner is installed
- the image has krun boot assets for the host architecture (published images
and
gondolin buildoutput have them) - no QEMU-only option is set
The full selection rules, how to pin a backend and the feature differences are in VM Backends.
The runner is found, in this order: sandbox.krunRunnerPath (SDK),
GONDOLIN_KRUN_RUNNER, a local build at
host/krun-runner/zig-out/bin/gondolin-krun-runner (searched from the current
directory and the package location), the installed
@earendil-works/gondolin-krun-runner-* package, then gondolin-krun-runner
on PATH. The static CLI skips the npm package (see
Static CLI).
krun on Linux
Give your user read/write access to /dev/kvm. On most distributions the
device belongs to the kvm group:
ls -l /dev/kvm
sudo usermod -aG kvm "$USER" # then log out and back inOn Linux x86_64 the runner comes with the npm package. On Linux arm64 build it from a checkout (see Building the krun Runner) and point Gondolin at it:
export GONDOLIN_KRUN_RUNNER=/path/to/gondolin/host/krun-runner/zig-out/bin/gondolin-krun-runnerUse the runner in place: it loads its bundled libkrun from zig-out/lib/.
krun on macOS
krun needs Apple silicon; Intel Macs use QEMU. The runner must be signed with
the com.apple.security.hypervisor entitlement to use Hypervisor.framework.
The npm runner package and make krun-runner builds are; Gondolin checks the
signature before booting.
The Gondolin Daemon Binary
Every VM is run by the Gondolin daemon, gondolind. The platform package ships
it as one multi-call binary, bin/gondolin: started as gondolin daemon (or
under the name gondolind, for example through a symlink or the package's npm
bin link) it is the daemon, otherwise it is the static CLI.
The SDK and the Node CLI use the first binary they find:
GONDOLIN_NATIVE_BIN: path to agondolinorgondolindbinary- a source checkout containing the package: the newest of
native/target/{release,debug}/gondolinandnative/target/{release,debug}/gondolind - the installed platform package
@earendil-works/gondolin-native-<platform>-<arch>
When none is found, starting a VM fails; there is no fallback.
Installs that skip optional dependencies (npm install --omit=optional,
--no-optional) do not get the platform package. Install it explicitly, at the same version:
npm install @earendil-works/gondolin-native-linux-x64@<version>or point GONDOLIN_NATIVE_BIN at a binary:
export GONDOLIN_NATIVE_BIN=/opt/gondolin/bin/gondolinThe daemon can also run on another machine than your Node.js process; that machine needs only the binary and QEMU (or the krun runner). See Remote Machines.
Static CLI
The gondolin binary of the platform package is also a self-contained CLI that
needs no Node.js: bash, exec, list, attach, snapshot and image, with
the same options as the Node CLI. It runs the VM in-process, so QEMU (or the
krun runner) is its only runtime dependency. build and tools need the Node
CLI. Usage and differences: CLI Reference.
To get the binary without installing the npm package, download the platform
package and extract package/bin/gondolin:
npm pack @earendil-works/gondolin-native-linux-x64
tar -xzf earendil-works-gondolin-native-linux-x64-*.tgz package/bin/gondolin
install -m 0755 package/bin/gondolin ~/.local/bin/gondolinFrom a checkout, make native-release builds it for all four platforms into
native/target/dist/<target>/gondolin.
The static CLI finds the krun runner through GONDOLIN_KRUN_RUNNER,
host/krun-runner/zig-out/bin and krun-runner/zig-out/bin in the current
directory, the directory of the gondolin binary, then PATH. Without one of
these it uses QEMU.
Verify the Installation
gondolin --versionThe Node CLI prints its package version and the daemon command it would use:
gondolin X.Y.Z
gondolind: /path/to/node_modules/@earendil-works/gondolin-native-darwin-arm64/bin/gondolin daemongondolind: not available (...) means no daemon binary was found (see
Troubleshooting). The static CLI prints
gondolin X.Y.Z (static); gondolin daemon --version prints the daemon's
version.
Then boot a VM:
gondolin exec -- uname -aThe first run downloads the default image (about 200 MB). Continue with the Quick Start.
Building from a Checkout
From a clone of the repository (Rust, Zig 0.17.0, pnpm and Node.js):
make native # daemon + static CLI: native/target/debug/gondolin
make krun-runner # libkrun + runner: host/krun-runner/zig-out/bin/gondolin-krun-runnerThe SDK and the Node CLI pick up both builds automatically when they run from
the checkout (node host/bin/gondolin.ts bash). For an optimized daemon, run
make -C native release (native/target/release/gondolin); the newest build
wins.
Guest images are built with make -C guest build (output in
guest/image/out, which a checkout uses as its default image) or with
gondolin build; see Building Custom Images. To include
locally modified guest helpers, set GONDOLIN_BUILD_SANDBOX_HELPERS_FROM_SOURCE=1.
Building the krun Runner
make krun-runner clones libkrun, applies Gondolin's patches, stages it under
.cache/ (no global install), builds the runner and, on macOS, ad-hoc signs it
with the hypervisor entitlement. Linux prerequisites (Debian/Ubuntu):
sudo apt install \
build-essential curl git make pkg-config clang lld xz-utils \
libclang-dev llvm-dev libcap-ng-dev
# libkrun needs current stable Rust (edition2024 crates)
curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal
. "$HOME/.cargo/env"
# install Zig 0.17.0 for your Linux architectureTroubleshooting
gondolind binary not found
No daemon binary was found. Install the platform package for your machine
(the error names it), build one in a checkout (make native), or set
GONDOLIN_NATIVE_BIN. On other platforms the error is
gondolind is not available for <platform>-<arch>: only macOS and Linux on
arm64 and x64 are supported.
QEMU Binary Not Found
Error: QEMU binary 'qemu-system-aarch64' not found. QEMU is not installed or
not on PATH. Install it (see Install QEMU); the binary must
match the guest architecture (qemu-system-aarch64 or qemu-system-x86_64).
Errors about qemu-img mean the QEMU utilities are missing
(qemu-utils on Debian/Ubuntu).
krun Runner Not Signed (macOS)
krun runner is not signed with the macOS Hypervisor entitlement means the
runner cannot use Hypervisor.framework, for example after copying or
re-linking it. Rebuild it with make krun-runner, or set
GONDOLIN_KRUN_RUNNER to a signed runner. Check a binary with:
codesign -d --entitlements :- /path/to/gondolin-krun-runnerkrun Runner Binary Not Found
--vmm krun, sandbox.vmm: "krun" or GONDOLIN_VMM=krun was set but no
runner was found. Install the runner package (macOS arm64, Linux x86_64),
build it with make krun-runner, or set GONDOLIN_KRUN_RUNNER /
sandbox.krunRunnerPath. Without an explicit backend Gondolin uses QEMU
instead.
VMs Use QEMU Instead of krun on Linux
Gondolin only selects krun when your user can read and write /dev/kvm. Check
with test -r /dev/kvm -a -w /dev/kvm && echo ok, add yourself to the device's
group (usually kvm) and log in again. Inside VMs and containers, KVM also
needs nested virtualization or the device passed through. Without /dev/kvm,
QEMU runs guests with software emulation, which works but is slow.