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.

  1. 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
  2. 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)
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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)
  12. 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
  13. 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)
  14. 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)
  15. 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
  16. 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
  17. 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
  18. 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
  19. 28 Sep

    An installer that needs no checkout

    apiaryctl install takes 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.

    ADR-0147, Part 1
  20. 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
  21. 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
  22. 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 service(8) claims about it, and there is a test for exactly the case where service(8) lies.

    ADR-0146
  23. 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
  24. 26 Sep

    Seven states, none of which is "probably fine"

    Every status badge in the UI now comes from one documented vocabulary. unknown means the check did not complete, stale means the observation is aging out of its freshness window, and contradictory means two evidence sources disagree with each other. Silence is a verdict rather than an all-clear, so it never renders green.

    ADR-0144

Screenshots

Current views from a live Apiary Colony: its overview, virtual machines, HAST replication, image inventory, Colony updates, and machine configuration.

Overview
Virtual Machines
Replication
ISO and Disk Images
Colony Updates
Machine Configuration