Standalone quickstart (single node)
Run the whole stack on one machine and give an agent a durable disk, with no external control plane: Postgres, the control plane (CA + allocation), the data-plane server, and a mount client. You will write a file, unmount, remount, and watch the file survive because it lives in the server, not on the mount.
Every command below was run end to end on a single host. The flow is:
postgres → orlop-control (auto CA) → server register → orlop-server → token issue → orlop mount --from-env → write → unmount → remount → data persistsPrerequisites
Section titled “Prerequisites”- Go and Rust (
cargo) toolchains. - A Postgres instance (the snippet uses Docker).
- Local mount support: Linux uses FUSE (
/dev/fuse+fuse3); macOS uses the built-in NFSv3 client (no macFUSE needed). Check withorlop doctorafter the build step.
0. Build the three binaries
Section titled “0. Build the three binaries”From the repo root:
GOWORK=off go build -o ./bin/orlop-control ./cmd/orlop-controlGOWORK=off go build -o ./bin/orlop-server ./cmd/orlop-servercargo build --release --bin orlop # → target/release/orlopexport PATH="$PWD/bin:$PWD/target/release:$PATH"
orlop doctor # confirms this host can mount1. Postgres + schema
Section titled “1. Postgres + schema”docker run -d --name dg-pg -e POSTGRES_PASSWORD=pw -e POSTGRES_DB=dg -p 5432:5432 postgres:16-alpineexport DATABASE_URL="postgres://postgres:pw@localhost:5432/dg?sslmode=disable"
orlop-control migrate up2. Start the control plane
Section titled “2. Start the control plane”The CA is created automatically on first boot (ORLOP_SECRETS_BACKEND=postgres
stores it in the DB), so there is no separate ca init step for a dev node.
Three values are the operator’s to choose, and two of them must match the data-plane server’s config later (called out in step 4):
export ORLOP_CONTROL_PLANE_TOKEN=$(openssl rand -hex 16) # shared service tokenexport ORLOP_TRUST_DOMAIN=demo.example # must match server tls.trust_domainexport ORLOP_DATAGW_SERVER_FQDN=localhost # must match server tls.fqdn (cert SAN)
ORLOP_SECRETS_BACKEND=postgres PORT=8080 orlop-control &# wait for: GET /healthz → 200Why
ORLOP_DATAGW_SERVER_FQDN=localhost: the control plane only signs a server certificate for an allow-listed name. The server’s cert SAN must equal the host agents dial. Usinglocalhosteverywhere keeps one cert valid for both the control→server and agent→server connections on a single box.
3. Register the data-plane server in the placement pool
Section titled “3. Register the data-plane server in the placement pool”/agent/enroll places each agent on a server from the pool. With an empty pool
it has nowhere to put a disk and returns 503, so register the one local server:
orlop-control server register \ --data-addr localhost:8443 \ --ops-addr localhost:7878 \ --total-bytes $((10 * 1024 * 1024 * 1024))--data-addr is where agents connect; --ops-addr is where the control
plane connects. Both use localhost so the one localhost cert covers both.
4. Start the data-plane server
Section titled “4. Start the data-plane server”tenant: id: a_demo # bootstrap tenant; more register dynamically at enroll name: demo agent diskstore: { type: local, root: ./dg-data/objects }routes: { type: sqlite, path: ./dg-data/routes.db }server: ops_bind: ":7878" # dual-stack ":port", NOT 127.0.0.1 — see note data_bind: ":8443" # must be set; the data plane is off by defaulttls: self_provision: true # fetches its cert + the client CA from the control plane control_url: http://localhost:8080 fqdn: localhost # must equal ORLOP_DATAGW_SERVER_FQDN trust_domain: demo.example # must equal ORLOP_TRUST_DOMAINtenants_root: ./dg-data/tenantsquota: { enforce: false }mkdir -p dg-data/objects dg-data/tenants# the service token authenticates the cert self-provisioning requestORLOP_DATAGW_SERVICE_TOKEN="$ORLOP_CONTROL_PLANE_TOKEN" \ orlop-server -config server.yaml &# wait for: "data-plane TCP listening with mTLS" bind=":8443"Why
:8443and not127.0.0.1:8443: the mount client resolveslocalhostto IPv6::1first. A127.0.0.1-only listener refuses that connection. The bare:portform is dual-stack, so both::1and127.0.0.1reach it while the cert SAN stayslocalhost.
5. Mint an enroll token and mount
Section titled “5. Mint an enroll token and mount”orlop-control token issue --agent demo --control-plane http://localhost:8080It prints a ready-to-paste block (the token is short-lived, ~10m, so mount promptly):
export ORLOP_AGENT_ID=demoexport ORLOP_MOUNT_POINT=./agent-diskexport ORLOP_CONTROL_PLANE=http://localhost:8080export ORLOP_ENROLL_TOKEN=<token from above>orlop mount --from-env &# wait for: "mount verified at ./agent-disk" (the post-mount health probe)6. Use it, then prove durability
Section titled “6. Use it, then prove durability”echo "hello from a durable agent disk" > ./agent-disk/hello.txtmkdir -p ./agent-disk/sub && echo "nested" > ./agent-disk/sub/note.mdcat ./agent-disk/hello.txt
# unmount: the bytes are NOT local — the mount point goes emptykill -TERM %3 # the `orlop mount` job; its Drop unmounts cleanlyls ./agent-disk # empty
# remount with a fresh token: the files are still thereorlop-control token issue --agent demo --json # grab a new tokenexport ORLOP_ENROLL_TOKEN=<new token>orlop mount --from-env &cat ./agent-disk/hello.txt # → hello from a durable agent diskcat ./agent-disk/sub/note.md # → nestedThe file survived the unmount/remount because it lives in the data-plane server (here on local disk; in production behind JuiceFS-on-S3), never on the mount.
The values that must match
Section titled “The values that must match”A single-node bring-up only breaks where the two halves disagree. Keep these in sync:
| control plane | data-plane server | why |
|---|---|---|
ORLOP_CONTROL_PLANE_TOKEN |
ORLOP_DATAGW_SERVICE_TOKEN |
authenticates cert self-provisioning |
ORLOP_DATAGW_SERVER_FQDN |
tls.fqdn |
the server cert SAN agents validate |
ORLOP_TRUST_DOMAIN |
tls.trust_domain |
SPIFFE trust domain on every cert |
server register --data-addr host |
tls.fqdn |
agents dial the name in the cert |
Cleanup
Section titled “Cleanup”kill %1 %2 %3 2>/dev/null # orlop mount, orlop-server, orlop-controldocker rm -f dg-pgWhat this is not
Section titled “What this is not”This is a single-node developer bring-up. It is not the multi-server placement, quota enforcement, JuiceFS-backed storage, or autoscaling path. It exists to let you run the whole system end to end on one machine and see the durability guarantee with your own eyes.