Why I stopped fearing freebsd-update

For years my upgrade routine was the same: back up what I could remember to back up, run freebsd-update, hold my breath, and hope. Sometimes it worked fine. Sometimes a kernel module didn’t rebuild right, or a port that depended on some ABI quirk fell over, and I’d spend the evening trying to remember what state things were in before I touched anything. That’s not an upgrade workflow, that’s gambling with extra steps.

Boot environments fixed this for me. If you’re running ZFS as your root filesystem on FreeBSD, which you really should be at this point, you already have the machinery to make every upgrade trivially reversible. You just need to actually use it instead of treating ZFS like “the thing that makes snapshots sound fancy.”

What a boot environment actually is

A boot environment is just a clone of your root dataset that the bootloader knows how to boot into. Instead of upgrading your one and only root filesystem in place, you clone it, upgrade the clone, and if the clone turns out to be garbage you reboot into the old one like nothing happened. No restore process, no re-imaging, no digging through a tarball at 2am. You just pick the old entry at the boot menu.

The tool that makes this painless is bectl, which ships in base on current FreeBSD releases. If you’re still on something old enough that it’s not there, go grab it from ports or just upgrade your system properly first, which, yes, is a bit of a chicken-and-egg problem the first time.

Setting it up

Assuming you installed FreeBSD with ZFS on root (the installer defaults to this now, and if you didn’t pick it, that’s a different and more painful conversation), you likely already have boot environments working whether you knew it or not. Check with:

  • bectl list — shows your existing boot environments, which dataset is active, and which one is set to boot next.
  • bectl create name — makes a new boot environment as a clone of the currently active one.
  • bectl activate name — marks a boot environment as the one to boot into on next restart.
  • bectl destroy name — removes one you no longer need, once you’re confident you won’t want to roll back.

That’s genuinely most of the command surface you need for day-to-day use. It’s refreshingly small.

Boot Environments Saved My Upgrade Day (And They Can Save Yours)

My actual upgrade routine now

Here’s what I do before any major version jump or even a risky patch update:

  • Create a fresh boot environment and give it a name that means something to future-me, not “be1” — I use something like pre-14.1-upgrade.
  • Activate it, then reboot into it, just to confirm the mechanism works before I start messing with the system. Trust but verify.
  • Run the actual upgrade — freebsd-update, a full source build, whatever the situation calls for — inside that boot environment.
  • Reboot into the new environment and poke at it. Check services, check dmesg for anything ugly, check that the network actually comes up the way it’s supposed to.
  • If it’s broken, reboot and pick the old boot environment from the loader menu. If it’s fine, I leave both around for a week or two before cleaning up, because some problems don’t show up for a few days.

This is slower than just running the upgrade blind, by maybe five minutes of extra typing. Those five minutes have saved me entire evenings more than once.

The afternoon it actually mattered

I’ll be specific about the one time this really paid off, without turning it into a war story longer than the how-to itself. I was upgrading a box that also ran a jail hosting some internal tooling I actually used daily. The upgrade went through clean, no errors, nothing in the logs that looked alarming. Rebooted, everything came up.

Except the jail’s network stack was behaving strangely — intermittent packet loss that only showed up under load, which of course meant it didn’t show up during my five-minute post-upgrade poke-around. It only became obvious a few hours later when things I actually depended on started timing out. Normally this is the point where you start down a debugging rabbit hole with no clear end, probably involving pcap captures and increasingly specific Google searches.

Instead I just activated the previous boot environment, rebooted, and was back to a known-good state in under two minutes. Then I debugged the problem at my leisure, in a non-production boot environment, with zero time pressure. Turned out to be an interaction with a sysctl tuning value I’d forgotten I’d set, unrelated to the upgrade itself, but that’s beside the point — the point is I had the luxury of figuring that out calmly instead of panicking at 11pm.

Boot Environments Saved My Upgrade Day (And They Can Save Yours)

A few things worth knowing

  • Boot environments aren’t a substitute for real backups. They protect you against bad upgrades and config mistakes on the same disk, not against disk failure, which will absolutely still ruin your week.
  • Don’t let boot environments pile up forever. Each one holds onto the blocks it diverged from, so old ones you’ll never roll back to are just eating disk space for no benefit.
  • If you dual-boot or have a complicated loader setup, boot environments play nicely with the default FreeBSD boot menu, but double-check your loader config if you’ve customized it heavily.

Just use them

If you’re running FreeBSD with ZFS root and you’re not using boot environments for upgrades, you’re doing the dangerous version of something that has a safe version built right into the base system for free. There’s no real downside here beyond a few extra commands and a little bit of disk space. Start using bectl before your next upgrade, not after the one that goes badly.