Deploying the orchestrator
Running the control plane as a long-lived service
For anything past local dev, run the orchestrator as a service rather than a foreground process.
Tokens and ports
Two things cause most bring-up confusion: which token goes where, and which port is which service. State both up front.
There are four distinct tokens, travelling in different directions:
| Token | Set on | Used by | Flag / env |
|---|---|---|---|
| Orchestrator bearer | Orchestrator | you → orchestrator | ORCH_AUTH_TOKEN / fuse connect --token |
| Host agent token | Host agent (fc-agent) |
orchestrator → agent | fuse host register --token (the agent’s FC_AGENT_TOKEN) |
| Host agent token | Host agent (qemu-agent) |
orchestrator → agent | fuse host register --token (the agent’s QEMU_AGENT_TOKEN) |
| API key | Orchestrator | apps → orchestrator | fuse apikeys create |
Both host agents take a token the same way, only the env var naming differs per
backend, so a GPU host registers with $QEMU_AGENT_TOKEN where a Firecracker
host registers with $FC_AGENT_TOKEN.
The single most common setup mistake is passing the host agent token to
fuse connect, or the orchestrator token to fuse host register. They are not
interchangeable. fuse connect and fuse host register both probe with the
token you give them and fail early with a message naming the layer that
rejected it, rather than surfacing an opaque 401 one command later.
Default ports:
| Port | Service |
|---|---|
8080 |
Orchestrator (control plane API) |
8090 |
Firecracker host agent (fc-agent) |
8091 |
GPU/QEMU host agent (qemu-agent) |
19551-19799 |
Per-VM guest-agent DNAT range, Firecracker hosts (fc-agent) |
19651-19899 |
Per-VM guest-agent DNAT range, GPU/QEMU hosts (qemu-agent) |
The two DNAT ranges differ because each agent has its own
FUSE_HOST_PORT_BASE default, 19550 for fc-agent and 19650 for
qemu-agent, and each allocates one port per VM index from 1 to 249. Open the
range that matches the backend the host actually runs, rather than assuming one
range covers the fleet.
Configuration
| Env var | Flag | Default | Purpose |
|---|---|---|---|
ORCH_LISTEN |
--listen |
:8080 |
API listen address. |
FIRECRACKER_BASE_URL |
--firecracker-url |
empty → stub | Firecracker host agent URL. |
FIRECRACKER_TOKEN |
--firecracker-token |
Bearer token for the host agent. | |
DATABASE_URL |
--database-url |
empty → in-memory | Postgres state store connection string. |
TOKEN_ENCRYPTION_KEY |
Hex-encoded 32-byte AES key for per-VM token encryption at rest. | ||
AGENT_DOWNLOAD_URL |
URL to fetch the guest agent binary at boot (alternative to baking it in). | ||
ORCH_TLS_CERT / ORCH_TLS_KEY |
--tls-cert / --tls-key |
Serve the API over TLS. Setting only one fails at startup. | |
ORCH_SECURE_COOKIES |
--secure-cookies |
empty → derived from TLS | Force the Secure flag on session cookies. Set to true when a proxy terminates TLS. See Security hardening. |
ORCH_REQUIRE_AUTH |
false |
Fail-closed strict mode: refuses to boot unless ORCH_AUTH_TOKEN, TOKEN_ENCRYPTION_KEY, and DATABASE_URL are all set. |
|
ORCH_AUTH_TOKEN |
--auth-token |
empty → no auth | Static bearer/master token. See Authentication. |
ORCH_ALLOWED_CIDRS |
--allowed-cidrs |
empty → open | Comma-separated CIDR allowlist. |
ORCH_SHUTDOWN_TIMEOUT_SECONDS |
--shutdown-timeout |
30 |
Graceful shutdown ceiling. |
ORCH_VM_PREFIX |
--vm-prefix |
fuse- |
Prefix applied to generated VM names. |
With FIRECRACKER_BASE_URL unset, the orchestrator runs the in-memory stub provider
(fine for local dev, not for production). With DATABASE_URL unset, state is
in-memory and does not survive a restart.
Postgres is the source of truth
Fuse doesn’t ship any backup or disaster-recovery tooling of its own, no
pg_dump wrapper, no export/import command, no point-in-time-recovery
integration. Everything durable (environment records, snapshot metadata, API
keys, host registrations) lives in the migrations under
internal/orchestrator/migrations/, applied automatically at boot. Treat this
Postgres database the same way you’d treat any other production database: standard
backup practices apply (pg_dump/pg_restore, WAL archiving, a managed
Postgres provider’s automated snapshots, whatever your organization already
uses elsewhere). This is general Postgres operational guidance, not something
specific to Fuse, there’s no Fuse-side hook or lifecycle event tied to it.
Snapshot restore (fuse snapshot restore, see
Snapshots) is an entirely different, VM-level
concept, restoring a Firecracker microVM’s disk from a checkpoint. It has no
relationship to Postgres backup and doesn’t substitute for one.
Quickest path: co-locate with a Firecracker host
The simplest topology co-locates the orchestrator on a Firecracker host and talks to
fc-agent over loopback. The host toolchain installs both as systemd services:
cd host-agent/firecracker
./fc-install.sh # firecracker + kernel + rootfs
./fc-agent.sh install-service # host agent (systemd, :8090)
./fc-agent.sh install-orchestrator # orchestrator (systemd, :8080), co-located
sudoedit /etc/default/orchestrator # set DATABASE_URL (postgres)
sudo systemctl start orchestrator
install-orchestrator resolves the orchestrator binary (from ORCH_BIN_SRC=/path,
a local ./orchestrator, an existing /usr/local/bin/orchestrator, or the latest
GitHub release), writes /etc/default/orchestrator prefilled with
FIRECRACKER_BASE_URL=http://127.0.0.1:8090, this host’s FIRECRACKER_TOKEN, and
freshly generated ORCH_AUTH_TOKEN + TOKEN_ENCRYPTION_KEY (never overwriting an
existing file), then installs orchestrator.service ordered after
fc-agent.service.
Auth is on by default: the orchestrator refuses to boot until you supply a Postgres
DATABASE_URL. The installer leaves that as a placeholder and does not start the
service until you fill it in; the schema is created automatically on first boot.
It then prints the FUSE_BASE_URL and FUSE_TOKEN values for connecting a
dashboard, if you’re running one. FUSE_TOKEN must match the orchestrator’s
ORCH_AUTH_TOKEN. Open 8080/tcp (or terminate TLS at a reverse proxy and point
clients there). Remove the service with ./fc-agent.sh uninstall-orchestrator.
One script: dedicated control-plane host
For a host that runs only the control plane (no co-located agent), use the standalone installer instead:
sudo ./ops/install-orchestrator.sh
It installs the orchestrator binary (from ORCH_BIN_SRC=/path, a local
./orchestrator, or the latest GitHub release), writes /etc/fuse/orchestrator.env
with a freshly generated ORCH_AUTH_TOKEN and TOKEN_ENCRYPTION_KEY (never
overwriting an existing file), installs and starts fuse-orchestrator.service,
and prints the exact fuse connect line to run from your laptop:
orchestrator installed and listening on :8080
fuse connect http://<this-host>:8080 --token <generated> --master
When the binary comes from a GitHub release, the installer downloads
checksums.txt for that exact tag and verifies the archive’s SHA-256 before
extracting or installing anything. host-agent/firecracker/fc-update.sh and
fc-agent.sh install-orchestrator do the same for every asset they fetch. A
missing checksums.txt, a missing entry for the asset, or a mismatch aborts
the install, leaving the existing binary in place and the service unrestarted.
The default posture is a single-node, in-memory store with auth on, so it starts
immediately. For a durable deploy, set DATABASE_URL in
/etc/fuse/orchestrator.env, uncomment ORCH_REQUIRE_AUTH=true, and restart. This
is a different unit (fuse-orchestrator.service, config in /etc/fuse/) from the
co-located install-orchestrator path above (orchestrator.service, config in
/etc/default/orchestrator); use one or the other per host, not both.
Running the orchestrator standalone
To run one control plane scheduling across many hosts instead of co-locating, skip
install-orchestrator and point FIRECRACKER_BASE_URL at each host’s agent URL,
then register the rest via fuse host register (see
Hosts). Topology is just one env var: loopback
co-locates, a remote URL doesn’t.
Build and run directly
go build -o bin/orchestrator ./orchestrator
export FIRECRACKER_BASE_URL=http://<host>:<port>
export FIRECRACKER_TOKEN=<token>
export TOKEN_ENCRYPTION_KEY=<hex-encoded 32-byte AES key>
export ORCH_AUTH_TOKEN=<token>
export DATABASE_URL=postgres://...
./bin/orchestrator