Quickstart: deploy a small static website
Build Keel from source, prepare a base image with a tiny web server and some HTML, and get a self-healing jail serving real traffic on a real FreeBSD host.
Prerequisites
- A FreeBSD 15.1 host or VM with jails, ZFS, and VNET already usable (a ZFS pool such as
zroot, andvnet/if_bridge/if_epairkernel support). - Root access on that host (jail/ZFS/
rctl(8)management requires it). - The Rust toolchain (rustup) installed on the host, or cross-compiled binaries copied over.
- Outbound network access from the host, to fetch a FreeBSD base distribution and a package for the base image below.
1. Clone and build
git clone https://github.com/lebarondecharlus/keel.git cd keel cargo build --release --workspace
This produces target/release/keel-agentd and target/release/keelctl.
2. Prepare a base image
Keel clones every jail's root filesystem from a ZFS dataset you populate yourself; provisioning that dataset is out of scope for Keel itself (see Architecture). For a static site, that dataset just needs a FreeBSD userland plus a tiny web server and your HTML:
# as root fetch https://download.freebsd.org/ftp/releases/amd64/amd64/15.1-RELEASE/base.txz zfs create -p zroot/keel/base/14.2-web tar -xf base.txz -C /zroot/keel/base/14.2-web cp /etc/resolv.conf /zroot/keel/base/14.2-web/etc/resolv.conf pkg -c /zroot/keel/base/14.2-web install -y python3 mkdir -p /zroot/keel/base/14.2-web/srv/www cat <<'HTML' > /zroot/keel/base/14.2-web/srv/www/index.html <!doctype html> <html><body><h1>Hello from Keel</h1></body></html> HTML
Python's standard library ships a static file server, which is all a small static site needs; swap in nginx or any other web server the same way if you'd rather. The resolv.conf copy is only there so pkg can resolve its repository inside the chroot.
3. Write a jail spec
Save the following as jail.yaml:
apiVersion: keel/v1
kind: Jail
metadata:
name: web-1
spec:
image: base/14.2-web
command: ["/usr/local/bin/python3", "-m", "http.server", "8080", "--directory", "/srv/www", "--bind", "10.0.0.5"]
network:
vnet: true
bridge: keel0
address: 10.0.0.5/24
resources:
cpu: "1"
memory: "256M"
restartPolicy: Always
image must match an existing base dataset: with the default --pool zroot, image: base/14.2-web resolves to zroot/keel/base/14.2-web, the dataset prepared in step 2. The server binds to the jail's own VNET address, not 0.0.0.0, since that's the address anything else on the network will actually reach it on.
4. Run keel-agentd
# as root ./target/release/keel-agentd
By default this reconciles against pool zroot, keeps its state under /var/db/keel, and serves its API on the Unix socket /var/run/keel-agentd.sock. Override any of these with --pool, --state-dir, or --socket.
5. Apply and visit the site
# from another shell, as root ./target/release/keelctl apply -f jail.yaml ./target/release/keelctl get web-1
Confirm it's really running, then fetch the page:
jls -j keel-web-1 curl http://10.0.0.5:8080/
The curl should print back the index.html from step 2. Keel prefixes every jail it manages with keel-, so the spec named web-1 becomes the jail keel-web-1.
6. Clean up
./target/release/keelctl delete web-1
Next steps
This walkthrough deploys one jail on one node. A static site is also a natural fit for kind: Service: N replicas of the same base image, spread across a cluster, reachable through one load-balanced virtual IP, self-healing if a node goes down. See the JailSpec reference and HTTP API reference for how to describe and reach one.
Once your site is running as a Service, the automatic HTTPS quickstart continues from exactly here: put a real domain in front of it and get a browser-trusted certificate that issues and renews itself with no manual intervention.
scripts/smoke-test.sh in the repository runs a jail's full lifecycle end-to-end against a real rc.d-managed keel-agentd service, including killing the daemon mid-flight to prove the automatic restart and state recovery both work, then a clean teardown. See that script and Architecture for how the pieces fit together.
Keel