My blog posts about FreeBSD’s fantastic jail container system have sparked interest, so I thought I’d touch on another aspect of their operation: what does the update process look like? Many of the guides online assume you’re using an orchestration tool like ezjail or iocage, but what if you’re like me and are just using FreeBSD’s internal tooling and ZFS snapshots to manage them?

Before we get started, let’s review the cardinal rules for upgrading jails on a FreeBSD system. Memorise them, they’ll be on the test:

  • Read the handbook. Bloggers like me write about what they’ve learned in the hopes it’s helpful, and interests people to give FreeBSD a try. The handbook is the canonical source of truth. No, not that Canoncial.

  • Don’t use freebsd-update(8) within jails. Run it from the host, as I’ll demonstrate shortly.

  • Don’t upgrade a jail to a newer system release than the host. If you do, Colin Percival will materialise in front of you and start reading off all your incompatible library files.

  • Name your jails after Star Trek ships, or anime characters. Okay it’s probably more useful to give them FQDNs, or at least name them after their function.

Walking through a binary upgrade

Say you’re like me and needed to update your prod home box with jails that run Plex, Samba, Netatalk, and Minecraft. It was running 12.2-RELEASE, and you used freebsd-update(8) to upgrade the host to 13.0-RELEASE.

First, you backed up your jails right? A ZFS snapshot, or at least made a tar archive of your jail folder? That’s a relief.

If you have your jails set to autostart on boot in rc.conf, all might appear well when you access them via SSH or jexec(8). But hold on, we have a problem:

holohost:~ $ uname -rs   
==> FreeBSD 13.0-RELEASE-p3
holohost:~ $ jexec ina /bin/sh   
==> "Welcome to the Takodachi, friend! Wah!"
ina:~ $ uname -UK   
==> 1202000 1300139

Our user environment version (-U) differs from the kernel (-K), which is inherited from the host. Remember that jails aren’t virtual machines; at least, not in the strict sense. I live in Xen land where PV exists, so those in glass houses…

We can use the same freebsd-update tool to upgrade our jails. Say I wanted to upgrade ina from 12.2-RELEASE to 13.0-RELEASE:

holohost:~ # freebsd-update -b /jail/ina \
	--currently-running 12.2-RELEASE -r 13.0-RELEASE \

Note the -b for the base directory of our jail, and that we’ve called out the version the jail currently runs. You may not have had to use these options before on a host, though the former might be familiar if you used bsdinstall(8) to install your jail, as I wrote about.

The process now is similar to what you’d run against a standard FreeBSD machine. You’ll be asked if the new packages look reasonable, and to confirm config files prior to merging. Now you can install the update:

holohost:~ # freebsd-update -b /jail/ina install   
==> Kernel updates have been installed.  Please reboot 
==> and run "/usr/sbin/freebsd-update install" again to 
==> finish installing updates.

Reboot your jail if it was running, then run install again:

holohost:~ # service jail restart ina
holohost:~ # freebsd-update -b /jail/ina install   
==> Completing this upgrade requires removing old shared
==> object files.  Please rebuild all installed 3rd party
==> software (e.g., programs installed from the ports 
==> tree) and then run "/usr/sbin/freebsd-update install" 
==> again to finish installing updates.

Log into your jail, and confirm the upgrade worked:

ina:~ $ uname -UK
==> 1300139 1300139

Nice! Now we can update our packages. Force pkg(8) to bootstrap again again so we pull the packages for the newer release, then do the upgrade:

ina:~ # pkg bootstrap -f
ina:~ # pkg update
ina:~ # pkg upgrade

You should see the newer build target shown for each package. Here’s Perl, and a package I just made up:

perl5-5.32.1_1 (ABI changed: 'freebsd:12:x86:64' -> 
thank_you_poul_henning_kamp-1.0 (ABI changed: 'freebsd:12:x86:64' -> 

Now back on your host, you can run install against the jail one more time:

holohost:~ # freebsd-update -b /jail/ina install
==> Installing updates... done.



I won’t lie (as opposed to all those other times?), I largely treat jails as compostable, a phrase I started using that sounds like composable, but with the implication that I’m recycling them. All my configuration, databases, and files are backed up and version controlled elsewhere, so I can stand up and install a new jail from Ansible or scripts. I tend to rebuild jails with these when performing large system upgrades as an opportunity to clean house, though generally on a separate ZFS dataset in case things go pear-shaped and I need to refer to the old ones.

Sometimes though, it’s just easier to upgrade. Isn’t that true of life?