Table of Contents

Create a FreeBSD Jail

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.


Building a FreeBSD release image

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.

1. Create the dataset

Make a dataset for the release version under zbane/jails/image/:

zfs create zbane/jails/image/15.1-RELEASE

2. Install the base system

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).

3. Snapshot the image

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.


Creating a jail from the image

1. Generate a UUID

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.

2. Clone the image into the new jail

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.

3. Write the jail config

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:

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.

4. Start the jail

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

See also

If you created a new jail, add an entry to changelog.