Install
crucible ships as a single binary that is both the daemon and the CLI. One
installer, install.sh, handles both roles and picks the right one for your OS:
- Linux → installs the daemon as a systemd service (plus the CLI). The daemon needs KVM + Firecracker, so it only runs on Linux.
- macOS / Windows → installs the client only (the CLI +
crucible mcp serve). Everything but the daemon is a thin HTTP client, so it drives a remote Linux daemon over the network.
# Linux daemon (needs root + systemd) — install, fetch deps, start:
curl -fsSL https://raw.githubusercontent.com/gnana997/crucible/main/install.sh | sudo sh -s -- --enable --with-deps
# macOS / Windows / Linux client — point it at a Linux daemon:
curl -fsSL https://raw.githubusercontent.com/gnana997/crucible/main/install.sh \
| sh -s -- --client --addr https://my-linux-host:7878 --token <key>A default install mints no key and leaves auth off — the local CLI on the daemon host just works. You only turn on auth when you open the daemon to other machines (see Remote access).
Modes at a glance
| Your OS | Installed by default | --client forces |
Needs root | Needs systemd |
|---|---|---|---|---|
| Linux (x86_64) | Daemon + CLI | client only | yes (daemon) | yes (daemon) |
| macOS (arm64/amd64) | Client | — (already client) | no | no |
| Windows (amd64) | Client | — (already client) | no | no |
--client forces client-only mode anywhere — e.g. a Linux box that should
only drive a remote daemon, not run one.
What the client install gives you (macOS / Windows / Linux)
Run with --client (automatic on macOS/Windows). No root, no systemd, no daemon
— just the tools that talk to one:
-
The
cruciblebinary — the full CLI andcrucible mcp serve(the MCP server for Claude Code / Cursor / any MCP client). On Windows it'scrucible.exe. -
Downloaded + checksum-verified from the matching release asset:
Platform Release asset macOS arm64 crucible_<tag>_darwin_arm64.tar.gzmacOS amd64 crucible_<tag>_darwin_amd64.tar.gzWindows amd64 crucible_<tag>_windows_amd64.zip(needsunzip)Linux amd64 crucible_<tag>_linux_amd64.tar.gz -
Installed without root. Into
CLIENT_BINDIRif set, else the systembindir when it's writable (or you're root), else~/.local/bin. The installer tells you if that dir isn't on yourPATHand how to add it. -
A client config file at
~/.config/crucible/env(mode600) exportingCRUCIBLE_ADDR/CRUCIBLE_TOKENwhen you pass--addr/--token. Source it from your shell rc, orsource ~/.config/crucible/envon demand. The installer never edits your shell rc files for you. -
Interactive prompts for the daemon URL and key only when run from a real terminal without
--addr/--token. A piped install (curl | sh) stays non-interactive and just prints guidance.
What it does not do: install Firecracker, a kernel, a rootfs, a systemd unit, or any daemon. The client is useless on its own — it needs a Linux daemon to point at. After installing, wire it into an agent:
command: crucible
args: ["mcp", "serve"]
env: CRUCIBLE_ADDR, CRUCIBLE_TOKENWhat the daemon install does (Linux)
The Linux path (default, or forced off with --client) requires root +
systemd. It:
- Installs the binary to
$PREFIX/bin/crucible(default/usr/local/bin). - Creates the state tree under
$STATEDIR(default/var/lib/crucible):profiles/(pre-baked<profile>.ext4),images/(converted OCI image cache),logs/(durable per-sandbox logs). - Installs the systemd unit at
$UNITDIR/crucible.service(default/etc/systemd/system) and runssystemctl daemon-reload. - Enables lazy fork — writes
vm.unprivileged_userfaultfd=1to/etc/sysctl.d/99-crucible.confso the jailed, uid-dropped Firecracker can useuserfaultfdfor on-demand snapshot memory. - Writes the config
$CONFDIR/crucible.env(default/etc/crucible) from the template — never clobbering an existing one — and folds in sensible defaults: the host's default-route NIC for--network-egress-iface, and the ingress proxy on:7879. - Drops an example scoped policy at
$CONFDIR/policies/example.json(inert — grants nothing until you mint a key against it). - Optionally fetches dependencies with
--with-deps(below) and optionally starts the service with--enable.
A daemon install mints no key and leaves the daemon on loopback
(127.0.0.1:7878). It's zero-surprise: nothing is exposed until you ask.
--with-deps: fetched dependencies
Opt-in and checksum-verified; each piece is skipped if already present. x86_64 only (that's where the prebuilt rootfs + kernel exist):
| Piece | Source | Destination |
|---|---|---|
| firecracker + jailer | firecracker-microvm releases (FC_VERSION) |
$PREFIX/bin |
| rootfs profile | crucible release <ROOTFS_PROFILE>.ext4 |
$STATEDIR/rootfs-<tag>.ext4 |
| guest kernel | crucible release vmlinux-x86_64 (firecracker-CI fallback) |
$STATEDIR/vmlinux |
The rootfs is tag-stamped (rootfs-<tag>.ext4) and the installer re-points
--rootfs at it — so a version upgrade actually picks up the new rootfs (with
its current baked guest agent) instead of silently keeping a stale one. A rootfs
you supplied yourself (a --rootfs path the installer doesn't own) is left
untouched. See Upgrading.
Without --with-deps you provide these yourself at the paths the config expects:
firecracker : /usr/local/bin/firecracker (and /usr/local/bin/jailer)
kernel : /var/lib/crucible/vmlinux
rootfs : /var/lib/crucible/rootfs.ext4
profiles : /var/lib/crucible/profiles/<name>.ext4Host prerequisites
Booting OCI images (crucible run nginx:alpine, crucible build) and --disk
sizing shell out to e2fsprogs — mkfs.ext4, fsck.ext4, debugfs,
resize2fs. The installer warns (doesn't fail) if they're missing:
apt-get install -y e2fsprogs # or: dnf install e2fsprogsCLI flags
| Flag | Role | Description |
|---|---|---|
--client |
any | Install just the CLI + mcp serve; no daemon/systemd/root. Automatic on non-Linux. |
--addr URL |
client | Default daemon address → CRUCIBLE_ADDR (e.g. https://host:7878). |
--token TOK |
client | API key → CRUCIBLE_TOKEN. |
--enable |
daemon | systemctl enable --now the service after install. |
--with-deps |
daemon | Also fetch firecracker + jailer, a rootfs, and a guest kernel (opt-in, checksum-verified). |
--no-egress-auto |
daemon | Don't auto-wire the host's egress NIC into a fresh config. |
--no-proxy |
daemon | Don't enable the ingress proxy (reach apps by name) by default. |
--upgrade-config |
daemon | Apply missing flags (--image-dir, --log-dir, --app-db, --registry-store, --network-egress-iface, proxy, app→app) to an existing config. |
--connect-token |
daemon | Mint a scoped token and print a ready-to-paste client one-liner + MCP config. |
--token-name NAME |
daemon | Name for --connect-token's key (default remote-client). |
--version TAG |
any | Release tag to install (default: latest published release). |
--binary PATH |
any | Install this local binary instead of downloading a release. |
-h, --help |
any | Print the header help and exit. |
Environment variables
Prefix them before the command (use sudo -E so root inherits them):
| Variable | Default | Applies to | Description |
|---|---|---|---|
PREFIX |
/usr/local |
both | Install prefix; the binary lands in $PREFIX/bin. |
CLIENT_BINDIR |
(auto) | client | Where the client binary is installed (else system bin, else ~/.local/bin). |
UNITDIR |
/etc/systemd/system |
daemon | systemd unit directory. |
CONFDIR |
/etc/crucible |
daemon | Config + policies directory. |
STATEDIR |
/var/lib/crucible |
daemon | State: rootfs, kernel, profiles, images, logs, tokens. |
FC_VERSION |
v1.16.1 |
daemon | Firecracker version fetched by --with-deps. |
ROOTFS_PROFILE |
base |
daemon | Which profile rootfs --with-deps fetches. |
KERNEL_URL |
(release asset) | daemon | Override the guest-kernel URL (uncompressed vmlinux). |
KERNEL_SHA256 |
(pinned) | daemon | Literal digest or .sha256 URL to verify a custom kernel. |
PROXY_LISTEN |
:7879 |
daemon | Ingress-proxy HTTP listen. :80 for a production ingress; host:port pins an interface. |
PROXY_TLS_LISTEN |
(off) | daemon | TLS SNI-passthrough listen (needs a TLS-serving guest), e.g. :7880 or :443. |
PROXY_DOMAIN |
apps.local |
daemon | Base domain for name routing (<app>.<domain>). |
INTERNAL_NET |
0 (off) |
daemon | Set 1 to opt into app→app networking (experimental). |
INTERNAL_PORT |
80 |
daemon | Override the app→app VIP port when INTERNAL_NET=1. |
Common recipes
# Production ingress on standard ports (root binds :80/:443 with no extra caps):
PROXY_LISTEN=:80 PROXY_TLS_LISTEN=:443 sudo -E bash install.sh --enable --with-deps
# Turn the ingress proxy off entirely:
sudo bash install.sh --enable --with-deps --no-proxy
# Enable app→app networking in one line (no manual config edit):
INTERNAL_NET=1 sudo -E bash install.sh --enable --with-deps
# Pin a specific release, install to a custom prefix:
PREFIX=/opt/crucible sudo -E bash install.sh --enable --with-deps --version v0.5.2
# Install from a local build (repo checkout) without downloading:
make build && sudo ./install.sh --enable --binary ./crucibleThe config file
Daemon flags live in $CONFDIR/crucible.env as a single CRUCIBLE_FLAGS="…"
line, passed verbatim to crucible daemon (systemd splits it on whitespace).
Edit it and restart to apply:
sudo systemctl restart crucibleRun crucible daemon --help for the full flag list. The installer seeds the
paths and feature flags for you; hand-editing is only needed for advanced tuning
(TLS, custom listen address, resource defaults).
Upgrading
Re-run the installer with a newer --version (or latest). Two things make an
upgrade safe:
- Existing config is never clobbered. Add
--upgrade-configto fold in any flags a newer release introduced (it only adds missing flags, never rewrites your values), then restart. - The rootfs is tag-stamped. With
--with-deps, an upgrade fetchesrootfs-<newtag>.ext4and re-points--rootfsat it, so the daemon boots the new rootfs (and its current guest agent) rather than a stale one. The previous rootfs is kept on disk for rollback.
Remote access
To drive the daemon from another machine, mint a scoped key — this turns on auth for every client, including local ones. Let the installer do it and print the exact client command + MCP block:
sudo ./install.sh --connect-token --token-name my-laptopThen make the daemon reachable: set --listen to a routable address and
--tls-cert / --tls-key in $CONFDIR/crucible.env, and restart. Non-loopback
listeners require both a key and TLS. See policy.md for scoped
tokens and cli/daemon.md for crucible daemon token.
Uninstall
sudo systemctl disable --now crucible
sudo rm /etc/systemd/system/crucible.service /etc/sysctl.d/99-crucible.conf
sudo rm /usr/local/bin/crucible # + firecracker/jailer if --with-deps installed them
sudo rm -rf /etc/crucible /var/lib/crucible # config + all state (rootfs, images, logs, tokens)
sudo systemctl daemon-reloadThe client: delete the binary from its install dir and ~/.config/crucible/.