The bhyve yard
A FreeBSD control plane
you can actually explain.
Apiary runs a small Colony of FreeBSD hosts as one thing: bhyve VMs and jails, placed and reconciled over Raft consensus, with ZFS storage, PF networking, and a web control plane that shows its work - not a black box wrapped around libvirt.
Colony → Comb → Cell
Apiary's own vocabulary for the physical containment hierarchy it manages.
- Colony
- The whole cluster: every Comb, and everything running on them, held together by a single Raft consensus group.
- Comb
- One physical FreeBSD host. A Colony can be a single standalone Comb or several Combs joined together - both are first-class, not a degraded state.
- Cell
- One VM or jail. Placed on a Comb, reconciled continuously against its desired state, and visible cluster-wide regardless of which Comb owns it.
Built to show its work
A running list of what makes Apiary a control plane instead of a wrapper.
- Raft, not a leader-election script
- Cluster membership, placement, and configuration are Raft-committed state (hashicorp/raft), with guarded join/approve flows instead of hand-edited peer lists.
- Evidence, not vibes
- Automated assumption checks, a Why-Not engine, and a resilience coverage map explain what's actually true about the Colony right now, and what would break it.
- FreeBSD-native
- bhyve, jails, ZFS, PF, and rc.d, used the way FreeBSD already does this well - a bolt-on control plane, not a reimplementation of the OS underneath it.
Recent changes
Selected changes merged to main. Each item links to its implementation or design record.
-
28 Oct
Apiary 1.0.0-alpha, the first tagged release
The first alpha release is tagged, signed, and published on GitHub, with a FreeBSD port submitted for review. This is an early alpha: expect rough edges, and treat it as a preview rather than a production-ready build.
Release notes FreeBSD ports PR 300206 -
26 Oct
A new web UI that opens on Colony health
The web control plane has a new layout: an icon rail, a top bar with tabs, and an overview that opens on the Colony's health. The overview is one status line, a list of what needs attention, and every Comb as a table, as cards, or as a health matrix, whichever the operator picks; the choice is remembered. A command palette, opened with /, finds cells, Combs, and pages, and lists only the actions the signed-in role can take.
Blend UI (#196) Top bar and refresh fixes (#197) -
26 Oct
Certificates prepared for a Comb before it joins
A Comb in the Colony can prepare a working certificate for a host that has not joined yet. The joining Comb receives it over the join channel, sealed to its own certificate, installs it atomically, and then obtains its own replacement through the Cloudflare integration, which is provisioned only to a Comb that was prepared for. Replacing a Comb's pinned certificate is a staged transition with a bounded overlap, shown on a new peer trust page. A guided apiaryctl walkthrough sets up the Cloudflare and Let's Encrypt integration on the first Comb.
Reservations (#187) Trust transition (#188) Join delivery (#189) Setup walkthrough (#190) Peer trust page (#195) ADR-0169 -
25 Oct
HAST replication is encrypted
hastd has no encryption of its own, and it accepts a connection only from the peer address it was configured with, so a TLS proxy in front of it would be refused. HAST replication now runs over a WireGuard link between each pair of Combs that share a resource, which keeps the source address hastd checks. This closes one of the gaps noted on 21 Oct.
HAST transport encryption (#176) ADR-0158 -
25 Oct
Monitoring reports, and a Zabbix template
Each Comb's current observations and the Colony's verdict can be polled over RPC and REST by an external monitoring system. The reports are read-only and pull-only, and Apiary keeps no history of them. A Zabbix 7.0 template reads the whole Colony in one request per interval, discovers every Comb from that one report, and is checked against its design record by the test suite.
Monitoring reports (#184) Zabbix template (#185) ADR-0160 ADR-0161 -
25 Oct
A read-only MCP server for AI clients
Apiary ships an MCP server that lets AI clients read the Colony's state through three read-only tools. Its tools make no assumption about the model behind the client. It installs on chosen Combs as an rc.d service, refuses unauthenticated requests, reads its secrets from a file, and is tested against orcli's MCP client and against a real deployment.
Model-agnostic tools (#179) Deployable on a Comb (#180) ADR-0162 ADR-0166 -
25 Oct
One page for both sides of a Colony join
A read-only ledger shows the joining Comb's steps beside the target's, with the Comb each action happens on and the value it produces or consumes. It never submits anything on the operator's behalf. Creating the first API key also no longer locks a Comb out of its own web UI.
Join ledger (#178) First API key fix (#183) ADR-0163 -
24 Oct
The Colony has an identity
Two Combs bootstrapped separately used to each form their own single-node cluster, and nothing could tell. The Colony now carries an identity in the raft log, set once and refused a second time on every replica.
Colony identity (#164) ADR-0156 -
24 Oct
VMs can be renamed without touching their host resources
A VM now has a logical ID, which a rename changes, and a physical resource ID, which its bhyve VM, ZFS dataset, HAST resource, and PF anchor keep. A rename is one atomic raft command, so nothing on the host is re-provisioned or destroyed.
Atomic rename (#173) ADR-0152 -
24 Oct
The join allowlist follows Colony membership
The allowlist in front of the join RPCs is now derived from raft's committed membership, so it stays current as Combs join. Before, an operator had to hand-edit it on every Comb after every join.
Derived allowlist (#174) ADR-0164 -
24 Oct
Image comments, and a kind for prepared VM disks
Uploaded ISOs and VM images can carry a comment, shown on the images page and in the VM creation pickers. Prepared FreeBSD VM disk images are listed as their own kind.
Image comments (#166) Prepared image kind (#155) -
21 Oct
Raft member traffic is now mutually authenticated TLS
Every Comb in the Colony now runs raft over mutual TLS, with a Colony certificate authority created on the first Comb. Manager-to-manager peer calls are on TLS by default, and an explicit opt-out is required to turn either one off. HAST replication and installer-driven certificate provisioning are not covered yet.
Encrypt raft and peer transports (#149) Colony CA commands (#152) ADR-0155 -
20 Oct
VM configuration can be edited, with restart-required changes
vCPU count and memory can be changed on an existing VM. Changes apply on the next start. A memory or CPU increase is checked against the node's capacity before it is accepted.
VM config edit (#145) -
20 Oct
Jail IPv4 addresses can be requested at creation
A jail can request an IPv4 address when it is created, validated against its network. Updating a VM or a jail no longer clears its MAC or IP address.
Jail IPv4 (#147) UpdateVM keeps addresses (#148) -
14 Oct
Colony joins now enforce the full authorization flow
Approval requires a live join window, a matching unused entry in the root-owned authorization store, and two approvals from distinct API keys presented to distinct Combs - even when the Colony currently has one member. The join path also records which Comb handled each approval, checks the joining Comb's reported certificate fingerprint, and refuses repeated first authorizations.
ADR-0147 Join-flow fixes -
14 Oct
Join and authorization secrets are provisioned during setup
Installation and join setup now create the required secrets automatically, so operators do not need to prepare them by hand before using the guarded Colony join flow.
Implementation -
14 Oct
ISO upload forms reset after a successful upload
The upload form clears its file and fields after the server accepts an ISO, confirming completion and leaving the form ready for another image.
Implementation -
13 Oct
Join approvals explain the required operator actions
The approval screen now labels the two authorization steps clearly, and shows the information operators need to complete each step on the appropriate Comb.
Implementation -
28 Sep
An installer that needs no checkout
ADR-0147, Part 1apiaryctl installtakes a bare FreeBSD host to an installed Comb - configuration, certificates, and identity - from a root shell and nothing else. It changes nothing without--apply, and the part of its report worth reading is the list of what it decided not to overwrite, and why. -
28 Sep
A join window, closed by default
A new Comb can only join while the Colony's join window is open, and closed is the normal state - a request that arrives at a closed Colony is refused by name. The window is replicated Raft state rather than a flag in one Comb's config file, and it carries an epoch, so a request approved against the old window does not survive its reopening. Alongside it, every Comb now derives its peer trust anchor from a replicated trust store instead of naming its own path. TLS by default is deliberately still not done: encryption with an unverified certificate is a worse failure than no encryption, so the trust anchor has to land first.
ADR-0147, Part 4 -
28 Sep
25 mutations, 25 killed, 0 survived
The installer's test suite is now run against deliberately broken code. The one mutant that survived the first pass turned out to have found a real gap rather than a weak test - nothing asserted what the summary reports after it refuses something - so it became two new tests instead of a suppressed failure.
Commit 534512e -
27 Sep
Updating the Colony without punching a hole in quorum
A colony-wide update runs under a single-flight replicated through Raft, and managerd hands the update over cooperatively - which is what lets a controlled update restart the very daemon driving it. A restart is confirmed by its listener port, not by what
ADR-0146service(8)claims about it, and there is a test for exactly the case whereservice(8)lies. -
26 Sep
A code audit, and what it cost
15 findings across auth, join, Raft, login, and bhyve, from static analysis plus manual triage of every hit that looked real. Each one is either fixed with tests alongside it or explicitly accepted with the reason written down - including the two where the honest answer was that the exposure stays until an operator sets an allowlist.
docs/audits/2026-09-26-code-audit.md -
26 Sep
Seven states, none of which is "probably fine"
Every status badge in the UI now comes from one documented vocabulary.
ADR-0144unknownmeans the check did not complete,stalemeans the observation is aging out of its freshness window, andcontradictorymeans two evidence sources disagree with each other. Silence is a verdict rather than an all-clear, so it never renders green.
Screenshots
Current views from a live Apiary Colony: its overview, virtual machines, HAST replication, image inventory, Colony updates, and machine configuration.