Status and roadmap
What works, and what does not yet
JailOps is under active development. It has not reached a stable release and has not been security audited. Treat it as something to try on a scratch pool, not something to put in front of production workloads.
How to read this page. Most of the code is exercised on Linux against fakes of zfs, jail and pfctl. A set of mechanisms has been run on a real FreeBSD 15.1 host (an arm64 test host; amd64 has not been tested), and only that set is marked verified, with the limits stated in each row. Verified means a prototype ran, not that the finished product is proven or audited. Anything not listed as verified has not been proven on FreeBSD.
- Implemented, tested with fakes code and tests exist, run on Linux against fakes
- Verified on FreeBSD 15.1 (arm64) run on a real FreeBSD 15.1 host
- Experimental unproven or flagged unverified
- Planned not implemented
| Capability | Status | Notes |
|---|---|---|
| Jail lifecycle | Create, start, stop, restart, delete, snapshots, copies, exec and shell (with a terminal), for thin and thick jails. Tested on Linux against fakes of zfs, jail and pfctl, and run on FreeBSD 15.1 arm64 with the daemon as an unprivileged user and the root helper on a socket: create from an image generation, start, stop, exec in FreeBSD and Alpine jails. Freezing a jail is not implemented and answers unsupported. | |
| Jail system calls from pure Go | jail_set, jail_get and jail_remove work from pure Go through x/sys, and results and enumeration equal jls. LOCAL_PEERCRED peer checks work. Lowering a jail's securelevel from the host is not refused by the kernel, so nothing relies on that. | |
| Image generations and thin jails | Immutable pkgbase generations, thin jails as ZFS clones, persistent datasets. A prototype built a userland with pkg -r, started a thin jail and ran pkg install in it on FreeBSD 15.1. The persistent path list is not final: how /var/db/pkg behaves after a root swap is open. | |
| Jail upgrade and rollback scopes | Upgrade with an /etc three-way merge, root swap and health check; rollback scopes root, data and all with data-loss confirmation. The mechanism (etcupdate on pkgbase generations, zfs rename root swap, root rollback keeping data) ran on FreeBSD 15.1. The account databases are merged by JailOps itself. | |
| Managed bridges, IPAM, address ranges | VNET bridges, deterministic allocation from explicit ranges, routed IPv6, no DHCP, RA or SLAAC. Rendering is pure and golden-tested. On FreeBSD 15.1 a jail on a managed bridge got its address, reached its gateway and, through the NAT anchor, the internet. A dual-stack bridge (a /24 with NAT and a /80 carved from a routed /64) also worked: jails got an IPv4 and an IPv6 address automatically, reached the internet over both, and the host reached them. Not yet verified: traffic into a jail from the internet, several networks per jail, amd64. | |
| PF anchors per jail | Anchors load, replace and flush without touching the administrator's ruleset; replacement is atomic (200 reloads under sampling, never empty or mixed); syntax errors are rejected and the old ruleset stays. Tables are anchor-local, and pfctl -n accepts an undefined table, so the renderer proves every table is defined. Nested anchors are evaluated only when every parent anchor contains anchor "*", and the renderer emits it. | |
| Isolation of projects on a shared bridge | On the arm64 test host, PF filtered traffic between projects on a shared bridge, and a per-jail source-address table at the member interface dropped a jail sourcing another jail's address. The private bridge-member flag blocks all member-to-member traffic, including within a project, so it is an option for routed setups, off by default. Only ordinary tools were used: active attacks (forged ARP, NDP, router advertisements, DHCP) were not automated, and Ethernet-level protection (ARP, NDP, MAC spoofing) is not covered and is still open. A shared bridge is not a full security boundary. | |
| IPv6 NDP proxy mode | Built-in proxy for upstreams that treat addresses as on-link (a switched /64). Off by default. Unproven on FreeBSD; routed mode does not depend on it. | |
| Linux jails (Alpine, Debian) | An Alpine 3.22 minirootfs ran as a jail on FreeBSD 15.1 arm64 under the Linuxulator: apk update and apk add openssh nginx worked, nginx served a page, sshd answered, and a ZFS rollback repaired a broken root. It needs kern.elf64.fallback_brand=3, which JailOps manages. Debian 13 (trixie, glibc 2.41) built with debootstrap (the release signature is always checked) and ran the same way: apt-get update and install worked, and both jails had IPv4 and IPv6 connectivity. Not yet verified: amd64, other glibc distributions, behaviour under load. Managed updates and package provisioning support Alpine only. Linux jails stay experimental. | |
| Chunk repository and backup node | Client-side encryption, zstd, content-defined chunking, keyless jailops-backupd on a directory or S3 (tested against an in-process S3 fake), retention, garbage collection, verification, quarantine, PostgreSQL control plane. Tested on Linux; on FreeBSD the node ran against PostgreSQL and a directory repository and served a host over mutual TLS. Not yet run on FreeBSD: S3, retention and garbage collection against real data. | |
| Backup and restore from a host | A running jail was backed up from its ZFS snapshot to a backup node (the second backup uploaded 1.3 KiB), verified at three depths (deep, extract and full, which boots the backup as a jail of its own and removes it), and restored as a new jail that kept its owners, modes and files and started. The daemon is not root: file agents of the privileged helper, confined by chroot to one checked directory, read the snapshot and write the restored jail. Capture assumptions were checked earlier: snapshot metadata (including extended attributes, NFSv4 ACLs and file flags) matched a live stat, and inode and ctime were stable across snapshots, rollback and send. zfs diff does not report added or removed hard-link names, so the accelerator rescans modified directories and multi-link files. A snapshot that is destroyed during a read fails with ENXIO, so capture holds it. | |
| ZFS replication between trusted hosts | The ZFS behaviours it relies on ran on the arm64 test host: clone sends, send -R, bookmarks as incremental bases, holds, resume tokens, raw sends of encrypted datasets. recv accepts an incremental on top of a newer receiver snapshot without -F, so guid continuity is enforced by JailOps. JailOps' replication code is a library and is not yet exposed through the API or CLI. | |
| REST API, operations, events, authentication | Routes, permission table, tokens, mTLS pinning, sessions, audit log. Contract and authorization-matrix tests run against fakes. | |
CLI jailops | Command tree covered by golden tests, and used against a real daemon on FreeBSD for the flows above (images, networks, projects, jails, exec and shell, labels, backups). | |
| Go SDK and in-memory fake server | Separate module, standard library only. | |
| Web consoles (host and backup node) | Vue 3 apps with unit tests and browser tests in demo mode, with light and dark themes and phone layouts. They are embedded in the daemons. The jail terminal of the host console was driven by a browser against a daemon on FreeBSD; the other pages have only run in demo mode. The consoles are under active change. | |
| Hub mode | Registry of hosts, forwarding with --host over pinned mTLS. Tested against fakes. | |
jailops-sync | validate, render, plan, drift, sync --apply, adopt, prune-check, rollouts, age secrets, apply log. | |
| Managed updates | Update policy per jail (manual, notify, managed), checks, explicit apply with a snapshot, history, and the same through jailops-sync rollouts. Tested with fakes; the package and generation updates have not been run on a real jail yet. | |
| Cluster mode with Raft | The Raft library is chosen (hashicorp/raft, after running both candidates on FreeBSD), the cluster mode is not built. Replicated fleet metadata. The Raft library has not been chosen yet. | |
| Self-healing fleets | A direction under discussion, not a capability, and not scheduled. Automatic promotion of stateful workloads will not happen without fencing, and asynchronous replication does not mean zero data loss. | |
| CARP | May be offered later as an optional networking feature. JailOps does not build failover on it. | |
| WebAuthn and OIDC sign-in | Planned after the first stable release. | |
| FreeBSD pkg packaging | Three packages (jailops-client, jailops, jailops-backupd), rc.d scripts, jailops-priv init and a signed pkg repository are built by scripts and a release workflow. On a clean FreeBSD 15.1 machine the whole flow ran from the packages: install, setup, services, image, jail, backup, restore. The public repository at pkg.jailops.net is not live yet, so there is no published package. |
Roadmap
The design plans work in milestones, with no dates. Each milestone is meant to be released as a 0.x version, and 1.0 is planned only when all of them pass their tests.
- M1, core. Generations, thin and thick jails, lifecycle, snapshots, rollback, daemon and privileged helper, API, SDK, CLI, packaging.
- M2, network and firewall. Bridges, VNET, IPAM, routed and proxy IPv6, PF anchors, doctor, web console.
- M3, Linux jails. Alpine and Debian (both proven on arm64 only so far).
- M4, backup. Chunk repository, backup node, retention, restore and restore tests, S3, and replication between trusted hosts.
- M5, hub. Mutual TLS, remotes, cold moves between hosts.
- M6, fleet.
jailops-sync, rollouts, age secrets, adopting live objects into the repository. - M7, cluster mode. Raft-replicated fleet metadata.
Not on the roadmap: virtual machines, OCI containers, live migration, non-ZFS storage.