Blackship: A FreeBSD Jail Orchestrator That Understands State

I’ve been running FreeBSD as my daily workstation for 2+ years, with jails handling everything from development environments to services. The tooling is fine. But “fine” doesn’t cut it when you have 15 jails with dependencies, need health checks, and want to know exactly what state each jail is in.
Blackship is a FreeBSD jail orchestrator with:
- TOML configuration
- Dependency management (topological ordering via petgraph)
- State machine lifecycle control
- ZFS integration (snapshots, clones, send/receive)
- Health checks with circuit breaker protection
- Docker-like Jailfile templates
Quick Start¶
# Install
cargo install blackship
# Create config
cat > blackship.toml << 'EOF'
[config]
data_dir = "/var/blackship"
zfs_enabled = true
zpool = "zroot"
dataset = "blackship"
[[jails]]
name = "web"
release = "15.0-RELEASE"
hostname = "web.local"
[jails.network]
vnet = true
bridge = "blackship0"
ip = "10.0.1.10"
gateway = "10.0.1.1"
EOF
# Bootstrap FreeBSD release
blackship bootstrap 15.0-RELEASE
# Create network
blackship network create default --subnet 10.0.1.0/24 --gateway 10.0.1.1 --bridge blackship0
# Launch
blackship up web
That’s a running jail with VNET networking.
What Makes It Different¶
State Machines, Not Flags¶
Every jail has explicit states:
stateDiagram-v2
[*] --> Stopped
Stopped --> Starting: start()
Starting --> Running: started()
Running --> Stopping: stop()
Stopping --> Stopped: stopped()
Starting --> Failed: fail()
Running --> Failed: fail()
Stopping --> Failed: fail()
Failed --> Stopped: recover()
Invalid transitions are rejected. You can’t stop a jail that’s already stopped. You can’t start a jail that’s starting. The system knows what’s happening.
Dependency Ordering¶
[[jails]]
name = "app"
depends_on = ["cache", "database"]
[[jails]]
name = "cache"
[[jails]]
name = "database"
Run blackship up app:
- Start
database - Start
cache - Start
app
Run blackship down app:
- Stop
app - Stop
cache - Stop
database
Dry run first: blackship up app --dry-run
The Warden (Supervisor)¶
Auto-restart with exponential backoff and circuit breaker:
blackship supervise
When a jail crashes:
- Wait 1 second, restart
- If it crashes again, wait 2 seconds
- Then 4s, 8s, 16s… up to 60s max
- After 5 consecutive failures, circuit opens (no restarts for 5 minutes)
- After 5 minutes, try again in half-open state
No restart loops. No hammering the system.
ZFS Everything¶
# Snapshots
blackship snapshot create web pre-upgrade
blackship snapshot list web
blackship snapshot rollback web pre-upgrade --force
blackship snapshot delete web old-snap
# Clones
blackship clone web@pre-upgrade web-test
# Export/Import
blackship export web -o backup.tar.zst
blackship export web -o backup.zfs --zfs-send # Fast
blackship import backup.tar.zst --name web-restored
Jailfile Templates¶
Build reproducible jails:
FROM 15.0-RELEASE
METADATA name=nginx version=1.0
ARG NGINX_VERSION=1.26
ENV NGINX_VERSION=${NGINX_VERSION}
RUN pkg install -y nginx-${NGINX_VERSION}
RUN sysrc nginx_enable=YES
COPY nginx.conf /usr/local/etc/nginx/nginx.conf
COPY html/ /usr/local/www/html/
WORKDIR /usr/local/www
EXPOSE 80/tcp
CMD /usr/local/sbin/nginx -g 'daemon off;'
Build it:
blackship build -f Jailfile -n nginx-jail
blackship build -f Jailfile -n nginx-jail --build-arg NGINX_VERSION=1.26
Health Checks¶
Command-based. Exit 0 = healthy.
[jails.healthcheck]
enabled = true
[[jails.healthcheck.checks]]
name = "http"
command = "curl -sf http://localhost:80/health"
target = "jail"
interval = 30
timeout = 10
retries = 3
[[jails.healthcheck.checks]]
name = "process"
command = "pgrep nginx"
target = "jail"
Monitor:
blackship health web
blackship health --watch --interval 5
blackship health --json # For scripting
Lifecycle Hooks¶
Run scripts at specific phases:
[[jails.hooks]]
phase = "post_start"
target = "jail"
command = "/etc/rc.d/nginx start"
on_failure = "abort"
[[jails.hooks]]
phase = "pre_stop"
target = "jail"
command = "/etc/rc.d/nginx stop"
on_failure = "continue"
Phases: pre_create, post_create, pre_start, post_start, pre_stop, post_stop
Port Forwarding¶
Uses PF anchors (no manual /etc/pf.conf editing):
blackship expose web -p 80
blackship expose web -p 443 -I 192.168.1.100
blackship expose web -p 8080 --internal 80 --proto tcp
blackship ports
Add to /etc/pf.conf:
rdr-anchor "blackship"
anchor "blackship"
Full Example: Web Stack¶
[config]
data_dir = "/var/blackship"
zfs_enabled = true
zpool = "zroot"
dataset = "blackship"
[[jails]]
name = "postgres"
release = "15.0-RELEASE"
hostname = "db.local"
[jails.network]
vnet = true
bridge = "blackship0"
ip = "10.0.1.10"
gateway = "10.0.1.1"
[[jails]]
name = "redis"
release = "15.0-RELEASE"
hostname = "cache.local"
[jails.network]
vnet = true
bridge = "blackship0"
ip = "10.0.1.11"
gateway = "10.0.1.1"
[[jails]]
name = "webapp"
release = "15.0-RELEASE"
hostname = "app.local"
depends_on = ["postgres", "redis"]
[jails.network]
vnet = true
bridge = "blackship0"
ip = "10.0.1.20"
gateway = "10.0.1.1"
[jails.network.dns]
nameservers = ["8.8.8.8"]
mode = "custom"
[jails.healthcheck]
enabled = true
[[jails.healthcheck.checks]]
name = "http"
command = "curl -sf http://localhost:3000/health"
target = "jail"
interval = 30
blackship up --all
# Starts: postgres → redis → webapp
blackship down --all
# Stops: webapp → redis → postgres
Commands Reference¶
Lifecycle: up, down, restart, ps, check, init, cleanup
Console: console <jail>, exec <jail> -- <cmd>
Bootstrap: bootstrap <release>, releases list|delete|verify
Networking: network create|destroy|list, expose, ports
Snapshots: snapshot create|list|rollback|delete, clone
Export/Import: export, import
Build: build, template list|inspect|validate
Monitoring: health, supervise, logs
Shell Completion: completion bash|zsh|fish
Requirements¶
- FreeBSD 14.0+
- ZFS (optional, for snapshots/clones)
- PF (optional, for port forwarding)
Install¶
# From crates.io
cargo install blackship
# Or download binary
fetch https://github.com/seuros/blackship/releases/latest/download/blackship-freebsd-amd64.tar.gz
tar xzf blackship-freebsd-amd64.tar.gz
mv blackship /usr/local/bin/
# Shell completion
blackship completion zsh > /usr/local/share/zsh/site-functions/_blackship
Why I Built This¶
Existing jail managers are either too simple (no dependencies, no state tracking) or too complex (full VM management when I just need jails).
I wanted:
- Clean state machine lifecycle with explicit transitions
- Automatic dependency ordering
- Circuit breaker protection against restart loops
- ZFS-first design without bolted-on abstractions
- Docker-like build templates without the Docker overhead
Blackship is that.
Why “Blackship”?¶
The name comes from Warhammer 40K. In the Imperium, Black Ships are massive vessels operated by the Adeptus Astra Telepathica. Their purpose: transport dangerous psykers across the galaxy, containing them in specialized cells until they reach Terra.
The parallel to FreeBSD jails was too good to ignore:
- Containment: Jails isolate processes like Black Ships isolate psykers
- Transport: Export/import moves jails between systems like Black Ships move cargo between worlds
- Control: State machines ensure nothing escapes containment unexpectedly
The internal naming follows the naval theme:
- Warden: The supervisor daemon (guards the cargo)
- Bridge: Central orchestration component (the ship’s command center)
- Bulkhead: Isolation boundaries between jails
- Sickbay: Health check system
No, it has nothing to do with race. It’s a 40K reference. The Emperor protects.
GitHub | MIT License
The bridge is yours. Launch your fleet.
🔗 Interstellar Communications
No transmissions detected yet. Be the first to establish contact!
Related Posts
Blackship vs The Galaxy: FreeBSD Jail Managers Compared
Docker, Podman, Bastille, CBSD, iocage, pot. Which jail manager actually fits your workflow? A brutally honest comparison.
Helmsman: Stop Writing AGENTS.md That Lies to Half Your Models
Your static instruction file works for Opus and breaks for Haiku. Helmsman serves model-aware instructions that adapt to capability tiers, environment, and project context.
Blackship Architecture: State Machines, Dependency Graphs, and Resilience Patterns
How Blackship uses explicit state machines, topological ordering, circuit breakers, and lifecycle hooks to manage FreeBSD jails reliably.