Motivation

FreeBSD has mature, battle-tested primitives for isolation and resource control: jails, ZFS snapshots/clones, rctl(8) resource limits, VNET per-jail networking. What it lacks is something that ties them together the way Kubernetes ties together Linux namespaces, cgroups, and overlay networking.

Existing FreeBSD jail tools (bastille, ezjail, cbsd, plain jail.conf) are good at creating jails, but none of them are reconciliation-based: none continuously watch a declarative spec and drive the live system to match it, restart what crashed, clean up what was removed, or survive their own restarts without losing track of what they manage. That gap, declarative, self-healing orchestration for FreeBSD, is what Keel is for.

Why FreeBSD

Jails, ZFS, and VNET are not bolted-on features; they're core, long-lived parts of the base system. That means Keel can be a comparatively thin layer: most of what a container orchestrator normally has to build (copy-on-write filesystem layers, resource accounting, network namespace isolation) is already correct and well-tested at the OS level.

How this differs from Kubernetes

Keel borrows Kubernetes' shape (declarative specs, a reconciliation loop, a CLI that will mirror kubectl) but it is not trying to be a drop-in replacement, and today it is far smaller in scope:

KubernetesKeel (current)
Workload unitPod (Linux containers)Jail
Isolationnamespaces + cgroupsjails + rctl(8)
Filesystemoverlay/container imagesZFS clones of a base dataset
StoragePersistentVolume/PVC, follows a rescheduled pod across any nodenamed ZFS volumes (spec.volumes), decoupled from a jail's own lifecycle but pinned to the single node they were created on; no cross-node movement yet
NetworkingCNI, overlay networks, ServicesVNET + epair(4) + bridge(4) per host, plain kernel routing between nodes' per-node subnets, no tunnel/overlay protocol
Scopemulti-node cluster, resource-aware schedulermulti-node, resource-aware scheduler (heuristic, no admission guarantee)
Control planeAPI server + etcd + schedulernode registry + resource-aware scheduler + spec routing, mutual TLS with revocation/rotation
Service / load balancingService + kube-proxy, ClusterIP/NodePort/LoadBalancerkind: Service replica sets + a per-node virtual-IP proxy, round-robin, cluster-internal only (no NodePort/LoadBalancer equivalent yet)

In other words: what exists today spans a kubelet plus a multi-node control plane with a resource-aware scheduler, mutual-TLS-secured cluster membership, cross-node kernel routing between per-node subnets, a Service abstraction with its own per-node load-balancing proxy, and named persistent volumes scoped to a single node, not a full cluster. There is still no external/outside-cluster reachability (a NodePort/LoadBalancer equivalent) and no cross-node volume movement for a rescheduled stateful replica; see the journey for how the project got here and what's next.

Why you'd use it

  • Declarative jails. Describe a jail (image, command, resources, network, restart policy) as a spec; apply it; the daemon makes reality match it, continuously, not a one-shot script.
  • Self-healing. Crashed jails restart automatically per policy, with crash-loop backoff so a persistently broken jail doesn't spin forever.
  • Safe by construction. The daemon only ever touches jails it created (name-prefixed and tracked in its own state), so it can share a host with other jails or tooling without stepping on them. State is crash-safe: killing the daemon or the whole VM never leaves it confused about what it manages.
  • Fast provisioning. Jail root filesystems are ZFS clones of a base image, so creating a new jail is close to instant and cheap on disk.