Skip to content

Bastille on FreeBSD 15

Clawdie uses Bastille as the host-side jail manager for its Warden runtime on FreeBSD.

  • FreeBSD 15 host
  • ZFS root pool
  • Bastille installed from packages
  • warden0 bridge on 10.0.1.1/24 in the repo registry example
  • host-side orchestration, not an operator jail

Keep Bastille boring and explicit:

  • bootstrap 15.0-RELEASE
  • keep the stock Bastille layout
  • use warden0 as the canonical bridge name
  • use 10.0.1.0/24 as the default internal jail subnet example
  • keep jails thin by default
  • keep only the optional db jail thick
Terminal window
pkg install -y bastille
bastille bootstrap -p 15.0-RELEASE

Default fixed service slots:

  • git on <subnet>.2
  • cms on <subnet>.3
  • llama-cpp on <subnet>.4 (llama-server, embeddings)
  • db on <subnet>.5

The operator controlplane is not a jail in the current model. It runs on the FreeBSD host and is published at ai.<internal_base>.

Example bring-up for the default install:

Terminal window
bastille create -B -g <subnet>.1 git 15.0-RELEASE <subnet>.2/24 warden0
bastille create -B -g <subnet>.1 cms 15.0-RELEASE <subnet>.3/24 warden0
bastille create -T -B -g <subnet>.1 db 15.0-RELEASE <subnet>.5/24 warden0

Apply internal hostnames after creation:

Terminal window
bastille config cms set host.hostname cms.home.arpa
bastille config git set host.hostname git.home.arpa

Workers are service-owned execution environments and start in the high range:

  • default worker: 10.0.1.101
  • future networked workers continue upward from there

Use the setup path rather than hand-writing worker create commands:

Terminal window
just setup -- --step jails --create

The intended host-side network is:

  • bridge: warden0
  • gateway: 10.0.1.1
  • jailed subnet: 10.0.1.0/24

If a VNET jail comes up without a default route, treat that as a provisioning defect and fix the create command rather than applying ad hoc routes later.

Clawdie keeps jails thin by default.

  • thin jails share the Bastille release tree and save disk space
  • the optional db jail stays thick because the database is long-lived state and we want its base lifecycle to stay explicit

Thin jails do not automatically follow host patchlevels. They follow the Bastille release tree they are mounted from. Updating the FreeBSD host alone does not refresh that release tree.

In practice, coordinated updates need two steps:

  1. update the host
  2. update the Bastille release tree and then refresh or rebuild the affected jails

Repo helper:

Terminal window
sudo just system-update

This runs the current-release patch path for the host, refreshes the Bastille release tree, updates thin jails, and updates the optional db jail when DB_RUNTIME=jail.

Current setup steps own the jail bootstrap contract:

  • db installs PostgreSQL + pgvector
  • git installs plain git storage
  • cms installs nginx and the Astro/Starlight web baseline; optional Strapi content/bootstrap remains internal and deployment-specific

Do not bootstrap a separate operator jail. The FreeBSD host is the operator surface.

With the default Bastille + Clawdie settings, datasets should live under a project prefix such as:

zroot/clawdie-runtime/jails
zroot/clawdie-runtime/releases
zroot/clawdie-runtime/templates

Snapshot persistent service jails before risky changes, for example:

Terminal window
zfs snapshot zroot/clawdie-runtime/jails/clawdie-db@pre-schema-14.mar.2026-1200
zfs snapshot zroot/clawdie-runtime/jails/clawdie-cms@pre-strapi-14.mar.2026-1230

Use user-facing snapshot names in DD.mmm.YYYY-HHMM format.

  • host orchestrator on FreeBSD
  • Bastille-managed service and worker jails
  • no dedicated operator jail in the active model
  • shared internal surfaces named by role: ai, cms, git
  • public web serving delegated to the cms jail instead of host nginx ownership