An agentless control plane for VM fleets, shipped as a single static binary.
Fleetplane creates, reuses, and reclaims cloud resources — virtual machines, volumes, and other provider-backed kinds — on demand. Run it on a very small always-on VM and let it create larger pre-imaged machines when work arrives (for example Hetzner Cloud CI runners), reuse existing capacity before creating more, and delete idle capacity by policy. No agent runs on the managed machines: Fleetplane talks only to provider APIs.
Everything mutating flows through a crash-safe operation journal: kill the process mid-create, restart it, and recovery converges to exactly the fleet you asked for — no duplicates, no leaks. examples/demo.sh proves this live with a kill -9.
- Pools — declare "keep N machines of this class"; the reconciler holds the fleet at that size with a bounded mutation budget per cycle.
- Acquisitions and leases —
fleetplane acquirebinds you to an existing machine when capacity is free, or creates one when it is not; leases carry TTLs and idle machines are reclaimed by policy. - Cost-aware leasing — providers declare billing windows (e.g. per-started-hour); Fleetplane reuses already-paid capacity, queues acquisitions to pack work into paid windows, and times deletions to land before billing boundaries (design doc 11).
- Parked machines (the warm tier) — where stopped machines bill at storage price (GCP, AWS), idle machines are parked instead of deleted and restarted in seconds instead of re-provisioned in minutes; the scheduler starts a parked machine before creating a new one (design doc 12).
- Crash-safe operations — every provider call is journaled first; the operation engine is the only retry authority, and ambiguous outcomes are verified rather than guessed (ADR-014, ADR-017).
- Multi-provider — the same kernel drives Hetzner Cloud, DigitalOcean, AWS, and GCP (plus a deterministic fake provider); orchestration code never imports a cloud SDK (enforced mechanically).
- Dynamic classes — machine templates managed via API/CLI/dashboard or config, validated against the kind registry at definition time.
- Single static Go binary: server, CLI, and web dashboard in one
fleetplaneexecutable - Providers: Hetzner Cloud, DigitalOcean, AWS, GCP, Docker (containers as machines — real end-to-end tests with no cloud account), and a fake provider for local development and tests
- Generic resource kinds:
compute.machineandstorage.volume(the kind registry is open to more) - Declarative apply:
fleetplane apply -fwith multi-doc YAMLPoolandResourcemanifests, compiled onto the same imperative API - Web dashboard embedded in the binary, served at
/ui/— no extra deployment, works offline - Prometheus metrics on a separate loopback ops listener (
/metrics), plus pprof and hot backup - Static token auth with a closed permission set; secrets only ever referenced as
secret://— never stored in config - SQLite storage (WAL) with online backup via
VACUUM INTO - Discovery sweep: detects orphans and ghosts in the provider account and converges them safely
No cloud account needed — the built-in fake provider behaves like a real one, including asynchronous creates.
Install a prebuilt binary (Linux/macOS; the script is install.sh in this repo):
curl -fsSL https://raw.githubusercontent.com/samishal1998/fleetplane/main/install.sh | shOr build from source (requires Go 1.26+):
git clone https://github.com/samishal1998/fleetplane
cd fleetplane
go build -o fleetplane ./cmd/fleetplaneWrite a minimal config.yaml:
server:
addr: ":8080"
storage:
path: ./fleetplane.db
providers:
demo:
driver: fake
settings:
createSteps: 2
classes:
demo-small:
kind: compute.machine
provider: demo
spec:
serverType: cpx31
image: "snapshot:demo=v1"Start the control plane:
./fleetplane serve --config config.yamlIn another shell, acquire capacity — a machine is created and bound:
./fleetplane acquire --class demo-small --cpu 1 --ttl 30m
# acq_01J... -> res_01J...
# state: provisioning
./fleetplane watch acq_01J... # polls until bound
./fleetplane resources # ID KIND PROVIDER PHASE EXTERNAL NAMEAcquire again and the same machine is reused; release when done:
./fleetplane release acq_01J...Or declare a pool and let the reconciler hold it at size:
./fleetplane apply -f - <<'EOF'
apiVersion: fleetplane.io/v1alpha1
kind: Pool
metadata:
name: demo-pool
spec:
class: demo-small
replicas: 2
EOF
./fleetplane poolsOpen the dashboard at http://127.0.0.1:8080/ui/ to see the fleet live.
For the full self-verifying tour (reuse, idle reclaim, and crash recovery under kill -9), run:
./examples/demo.shTo go from here to a real cloud, follow the setup guide.
fleetplane CLI ──HTTP──▶ ┌──────────────────────────────┐
web dashboard ──/ui/──▶ │ API (auth, idempotency) │
├──────────────────────────────┤
│ scheduler pool reconciler │
│ operation engine │◀── crash-safe journal
├──────────────────────────────┤
│ SQLite (WAL) │
└──────┬───────────┬───────────┘
provider SDK provider SDK
│ │
Hetzner / DO AWS / GCP APIs
The kernel (API, scheduler, reconciler, operation engine, storage) is provider-agnostic: it journals intent, dispatches through a narrow provider SDK, and verifies outcomes. Providers are thin adapters; the boundary is enforced by scripts/check-boundaries.sh and depguard, so orchestration code can never import a cloud SDK. Depth lives in the design docs: architecture, provider SDK, API and resource model, reconciliation and scheduling.
| Document | What it covers |
|---|---|
| Setup guide | Zero to production: install, config, tokens, systemd, provider walkthroughs |
| Concepts | Resources, classes, pools, acquisitions, operations, discovery |
| Configuration | Every config key, defaults, and validation rules |
| CLI | The full fleetplane command tree and exit codes |
| API | HTTP routes, auth, idempotency, error shape |
| Providers | Hetzner, DigitalOcean, AWS, GCP, and fake driver settings |
| Operations | Running in production: metrics, backup, uncertain operations |
| Dashboard | The embedded web UI |
Deeper background:
- Design docs:
docs/(00_README.md…12_PARKED_MACHINES.md) - Architecture decision records:
docs/adr/ - Runbooks: backup and restore
- OpenAPI contract:
api/openapi.yaml
Current release: v0.7.0. All phases of the implementation plan are complete — generic resource kernel, provider SDK, six drivers, pools and reconciliation, acquisition scheduling, cost-aware leasing, parked machines, CLI/API hardening, production hardening (metrics, backup, chaos tests), and the storage.volume kind as the genericity proof. The API version is fleetplane.io/v1alpha1; expect additive evolution.
make gate # build + vet + fmt-check + tidy-check + test(-race) + boundaries + lintEvery increment must end with make gate green (ADR-009). Individual targets: make build, make vet, make fmt-check, make tidy-check, make test (runs go test -race -shuffle=on -count=1 ./...), make boundaries, make lint (and make lint-install for golangci-lint).
Test layout:
- Unit tests live beside their packages (
internal/...,pkg/...,providers/...). - Cross-cutting integration tests live in
tests/(API, pools, acquisition, ownership, readiness, chaos, fault injection). - End-to-end tests against a real Hetzner project are opt-in: set
FLEETPLANE_E2E=1andHETZNER_TOKEN(dedicated throwaway project only — see ADR-015; all test resources are labeled and swept). - Chaos tests are gated behind
FLEETPLANE_CHAOS=1and print their seed for reproduction.
Kernel purity (orchestration code never imports provider SDKs) is enforced mechanically — see .golangci.yml (depguard) and scripts/check-boundaries.sh.
MIT.