Running Zyvor Fabric in Docker or Podman
Two containers: zyvor-fabricd (the daemon + web console) and fluxvm (the VM engine Fabric
drives over REST at 127.0.0.1:7788). Fabric alone can serve the API and console, but can't
create/start/stop a real VM without FluxVM reachable.
Host prerequisites
These are genuine host-level requirements, not something either container's tooling papers over:
nbdkernel module, loaded before you start the stack:sudo modprobe nbd max_part=16. FluxVM's NBD-backed storage and GuestKit's image-customization step both need this, and it cannot be reliably loaded from inside a container (FluxVM's own Kubernetes docs already state this as a host prerequisite there too)./dev/kvmpresent and accessible (ls -l /dev/kvm) -- FluxVM's QEMU backend needs it directly.- A rootful container engine. Both containers run with
network_mode: host,/dev/kvmaccess, and capabilities likeCAP_SYS_CHROOT/CAP_SETUID/CAP_SETGIDthat fight rootless Podman's user namespace model. Usesudo podman ...(or a rootful Podman machine), not rootless. Docker's default install is already rootful. - cgroup v2 -- FluxVM's cgroup crate writes directly to
/sys/fs/cgroup; thefluxvmservice'scgroup: hostsetting depends on this.
Network Fabric (optional eBPF edge)
Compose mounts configs/fluxvm-dataplane.toml as
/etc/fluxvm.toml, mounts host /sys/fs/bpf, and raises memlock so
sandbox.dataplane.mode = "ebpf" can attach. After first green attach
(schema_version=4), set required = true for fail-closed. Operator guide:
guides/vm-drivers/fluxvm-dataplane.md.
Build
Fabric's own image is a plain single-context build; FluxVM's needs the sibling guestkit repo
supplied as a second BuildKit build context, which a compose build: block can't express portably
across Docker Compose and Podman Compose versions -- so it's built with a small script instead:
# FluxVM and guestkit checked out as siblings of this repo (../FluxVM, ../guestkit)
./scripts/build-container-images.sh # defaults to podman
BUILDER=docker ./scripts/build-container-images.sh # or explicitly docker
Run (eval profile)
docker compose up -d # or: podman compose up -d
curl -sf http://localhost:9095/health
curl -sf http://localhost:9095/readyz | jq .
Auth stays on even in this profile -- unauthenticated requests are read-only by design, so
disabling auth wouldn't let you create/start/stop a VM anyway, just browse. Log in at
http://localhost:9095/app (or POST /api/auth/login) with username admin, password
eval-admin-only (fixed and documented, overridable via ZYVOR_FABRICD_ADMIN_PASSWORD -- this is
not a real secret, don't reuse it anywhere else). TLS is off, so it's plain HTTP.
Port
Both compose files listen on 9095 by default, overridable with ZYVOR_FABRICD_PORT -- useful for
running alongside a bare-metal zyvor-fabricd on the same host, or just to avoid a clash:
ZYVOR_FABRICD_PORT=19095 docker compose up -d
curl -sf http://localhost:19095/health
curl -sf http://localhost:19095/readyz | jq .
This is backed by a real env var the daemon itself reads, ZYVOR_FABRICD_LISTEN
(backend/zyvor-fabricd/src/config.rs), which overrides whatever daemon.listen the loaded TOML
config sets. The same variable works for a bare-metal install too -- set it in
/etc/zyvor-fabricd/zyvor-fabricd.env, which systemd/zyvor-fabricd.service's EnvironmentFile=
already loads, no unit file change needed.
If you change the published port (or sit behind a hostname/ingress), also set
ZYVOR_FABRICD_PUBLIC_URL (or daemon.public_url) so the OpenStack service
catalog advertises the URL clients actually call — for example
http://localhost:19095 when using ZYVOR_FABRICD_PORT=19095. See
openstack-compat.md.
Auth and TLS are both off in this profile (configs/zyvor-fabricd-docker.toml) -- it's for local
dev only, don't expose it beyond localhost. Open http://localhost:9095/app for the console.
Run (production)
cp docker-compose.prod.example.yml docker-compose.prod.yml
cp configs/zyvor-fabricd-prod.toml.example configs/zyvor-fabricd-prod.toml
# edit secrets, then:
REGISTRY=... TAG=v1.0.0 docker compose -f docker-compose.prod.yml up -d
This profile requires ZYVOR_FABRICD_ADMIN_PASSWORD and ZYVOR_FABRICD_JWT_SECRET, enables auth and
TLS (self-signed by default -- replace /etc/zyvor-fabricd/tls/{server.crt,server.key} with a real
cert for anything beyond an internal network), and expects pre-built, pinned images rather than
building locally.
Why network_mode: host
zyvor-fabricd's nftables/rtnetlink calls need to act on the host's real network namespace to
manage actual VM traffic -- CAP_NET_ADMIN etc. only grant control over whatever netns the process
is actually in. Running both containers on the host network is also what lets Fabric reach FluxVM
at its default 127.0.0.1:7788 with zero config changes, the same reason FluxVM's own Kubernetes
DaemonSet sets hostNetwork: true. This is intentional, not a hardening gap to close later -- it
mirrors the capability grants the bare-metal systemd deployment already has.
Known gaps in this container profile
- Guest-agent injection into VM images doesn't work yet.
/vendor/zyvor-guest-agent(served by the daemon for a VM's cloud-init to fetch) is a vendor binary this repo's own build doesn't produce -- runscripts/build-vendor-binaries.shseparately and mount the result into/var/lib/zyvor-fabricd/vendor/if you need this.
Kubernetes
For the same privilege model as DaemonSets on k3s (NodePort 30095, Helm chart, remote deploy),
see KUBERNETES.md and ./scripts/deploy k8s USER@HOST.