Skip to content

Container images

Every vX.Y.Z tag publishes three multi-arch (amd64 + arm64) images to GHCR, so you don’t have to rebuild orlop from source — in particular the mount client, which otherwise needs the Rust toolchain plus libfuse3-dev. The images job in .github/workflows/release.yml cuts them on each tag from the same per-arch binaries that ship in the release tarballs — no in-Docker rebuild.

Image Component Role Base
ghcr.io/liu1700/orlop-control control plane HTTP API and operator CLI — migrate is a subcommand of this one binary, not a separate image distroless static, nonroot
ghcr.io/liu1700/orlop-server data plane the data-plane server (ops + data listeners, mTLS) distroless static, nonroot
ghcr.io/liu1700/orlop-mount mount client static orlop disk-mount binary with fusermount3 available at runtime debian trixie-slim

Each release publishes :vX.Y.Z and moves :latest. Both are multi-arch manifests — Docker/containerd selects linux/amd64 or linux/arm64 automatically. Each push also prints an immutable digest; pin it for reproducible deploys:

Terminal window
docker pull ghcr.io/liu1700/orlop-control:v0.6.1
# or, pinned by digest (printed by the images job):
docker pull ghcr.io/liu1700/orlop-control@sha256:<digest>

The control-plane HTTP API and the operator CLI are the same binary, so migrate is a subcommand (orlop-control migrate up).

Port 8080 (HTTP API; override with PORT)
Entrypoint orlop-control — no args starts the server; migrate, server, token, user, ca are subcommands
Default CMD none (no args → serve)
Required env DATABASE_URL (postgres://… or sqlite:/data/orlop.db); ORLOP_CONTROL_PLANE_TOKEN (shared service token the data plane presents back); ORLOP_SERVER_FQDN; and either ORLOP_SECRETS_BACKEND=postgres or ORLOP_SECRETS_DIR (filesystem CA backend)
Optional env ORLOP_MOUNT_PREFIX controls the agent-visible virtual_path returned by entity APIs; default /mnt/orlop
Volumes none declared. Stateless with Postgres and ORLOP_SECRETS_BACKEND=postgres; with SQLite or the filesystem CA backend, give it a writable volume (owned by uid 65532) for the DB file and ORLOP_SECRETS_DIR

Run orlop-control migrate up before serving (e.g. a Kubernetes initContainer); it is idempotent and self-checks the schema — see upgrade-safety.md.

Terminal window
docker run --rm \
-e DATABASE_URL="postgres://user:pw@db:5432/orlop?sslmode=disable" \
-e ORLOP_SECRETS_BACKEND=postgres \
ghcr.io/liu1700/orlop-control:v0.6.1 migrate up
Ports 7878 (ops/HTTPS, server.ops_bind) and 8443 (data/mTLS, server.data_bind)
Entrypoint orlop-server
Default CMD -config /etc/orlop/server.yaml — mount your YAML config there (e.g. a ConfigMap)
Required env ORLOP_SERVICE_TOKEN (must equal the control plane’s ORLOP_CONTROL_PLANE_TOKEN); ORLOP_JFS_META_URL (only for the JuiceFS quota backend)
TLS with tls.self_provision, the server fetches its leaf cert + client CA from the control plane; tls.fqdn must match the cert SAN / Service name or the control plane returns fqdn_not_allowed and the server exits
Volumes none declared. The object store and routes DB live at the config’s store.root / routes.path / tenants_root — back those paths with a volume

The mount client with FUSE available at runtime — the image that saves you the Rust build. Linux releases use fuser’s pure backend and a static musl binary: there is no libfuse3.so dependency. fuse3 in the image supplies the fusermount3 helper used when the process cannot mount /dev/fuse directly. The binary is therefore safe to copy into bookworm, trixie, or another Linux base with a compatible kernel:

COPY --from=ghcr.io/liu1700/orlop-mount:vX.Y.Z \
/usr/local/bin/orlop /usr/local/bin/orlop
Ports none (outbound only)
Entrypoint orlop — pass a subcommand, e.g. orlop mount …
Devices needs /dev/fuse and CAP_SYS_ADMIN for the FUSE mount
Env-driven mount orlop mount --from-env (designed for pods) requires ORLOP_AGENT_ID, ORLOP_CONTROL_PLANE, ORLOP_MOUNT_POINT, plus either ORLOP_ENROLL_TOKEN or both ORLOP_SA_TOKEN_PATH and ORLOP_REFRESH_URL; workload identity is preferred because each process retry mints a fresh one-shot token. ORLOP_CERT_DIR, ORLOP_ON_EVICTION, and ORLOP_MOUNT_TAKEOVER are optional
Lease takeover acquiring the mount lease while another mount’s lease is still live fails with 409 lease_live. A spawner that knows the previous pod is gone (crash recovery) sets ORLOP_MOUNT_TAKEOVER=1 (or passes --takeover) to displace it without waiting out the lease TTL
Eviction behavior when the mount lease is lost involuntarily (revoked, expired, taken over), --from-env mounts default to --on-eviction=abort: the FUSE connection is aborted so workload I/O fails with ENOTCONN instead of silently writing into the directory the unmount would expose. Set ORLOP_ON_EVICTION=unmount (or --on-eviction=unmount) for the old clean-unmount behavior
Config-driven mount orlop mount --config <file> [--credentials <file>] reads no env; defaults to ~/.config/orlop/config.yaml and ~/.config/orlop/credentials.json

On Kubernetes, grant FUSE access:

securityContext:
capabilities:
add: ["SYS_ADMIN"]
# plus a /dev/fuse device (a device plugin, or a privileged-ish pod policy)