This page documents the standard way of creating a new FreeBSD jail on bane: build (or reuse) a FreeBSD release image, clone it into a new dataset with ZFS, write the jail config, and start the jail with jailer.
All commands are run on bane as root – SSH to bane and become root first.
Jails are cloned from a clean FreeBSD release image – a minimal base install kept in its own ZFS dataset under zbane/jails/image/. The image is built once per release (e.g. 15.1-RELEASE) and then reused for every new jail.
Make a dataset for the release version under zbane/jails/image/:
zfs create zbane/jails/image/15.1-RELEASE
Go to the dataset's mountpoint:
cd /usr/local/jails/image/15.1-RELEASE
Then install the base system – the same release that is on your host – into that folder:
bsdinstall jail .
bsdinstall will ask which distribution sets to install (base, doc, lib32, src, …). You only need the base set – uncheck everything else. The result is a minimal FreeBSD userland (no lib32, doc, src or ports).
Take a snapshot of the finished image. By convention the snapshot is named after the patch level the base install reports (bsdinstall installs the base already patched to the current errata level):
chroot /usr/local/jails/image/15.1-RELEASE /bin/freebsd-version -u # -> 15.1-RELEASE-p6 zfs snapshot zbane/jails/image/15.1-RELEASE@p6
New jails are then cloned from this snapshot.
uuidgen -r
Use the first segment of the generated UUID (the first field): for f9ba13cb-e512-454e-be10-a7092ed0e3e9 use f9ba13cb. It ends up in the jail's VNET interface name (j${uuid}ng0), so every jail gets a unique interface.
Clone the release image into the new jail's dataset with zfs send / recv:
zfs send zbane/jails/image/15.1-RELEASE@p6 | zfs recv -v zbane/jails/passwd-01
The destination dataset must not exist yet – zfs recv creates it. It inherits its mountpoint from zbane/jails and appears under /usr/local/jails/passwd-01, exactly the path used in the jail config below.
Jail config files live in /etc/jail.conf.d (the host's /etc/jail.conf includes every *.conf in that directory). Create /etc/jail.conf.d/name-01.conf using this template:
name-01 {
$uuid = "<uuid>";
devfs_ruleset = 10;
$bridge = "prod";
$domain = "abi.am";
$if_name_0 = "j${uuid}ng0";
vnet;
vnet.interface = "${if_name_0}";
exec.prestart = "service ngbuddy jail ${if_name_0} $bridge";
exec.start += "/sbin/ifconfig lo0 127.0.0.1 up";
exec.start += "/sbin/ifconfig ${if_name_0} ether 58:9c:fc:ab:10:12";
exec.start += "/sbin/ifconfig ${if_name_0} inet 172.20.200.102/24";
exec.start += "/sbin/route add default 172.20.200.1";
exec.start += "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown jail";
exec.poststop += "service ngbuddy unjail ${if_name_0} ${name}";
host.hostname = "${name}.${domain}";
path = "/usr/local/jails/${name}";
exec.consolelog = "/var/log/jail/${name}.log";
persist;
depend = pr-01, log-01, dns-01;
}
Values to fill in:
name-01) and the config file name must match the jail's name (${name} expands to it at runtime).$uuid – the first segment of your generated UUID (e.g. f9ba13cb → interface jf9ba13cbng0).ether – a free MAC address in the 58:9c:fc:ab:xx:xx range; must be unique per jail.inet – a free IPv4 address in 172.20.200.0/24 (the jail network; gateway 172.20.200.1).$domain – abi.am for externally visible jails, local.abi.am for internal ones.depend – the jails that must be running before this one starts.
Everything else stays as-is: the exec.prestart/exec.poststop hooks call ngbuddy to create and destroy the jail's netgraph interface on the prod bridge automatically, and exec.consolelog sends the jail's console output to /var/log/jail/name-01.log.
jailer start name-01
That's it – the jail is up. Verify with:
jls jailer list
and get a shell inside the jail with:
jailer console name-01
If you created a new jail, add an entry to changelog.