The upgrade that almost wasn’t

I used to treat major FreeBSD upgrades the way some people treat dentist appointments: necessary, dreaded, and postponed as long as humanly possible. Then I got burned once by a kernel/userland mismatch that left a box unbootable at 1am, and I decided to stop being an idiot about it. The fix wasn’t more caution. It was boot environments.

If you’re running FreeBSD on ZFS and you’re not using boot environments, you’re doing upgrades with no seatbelt. This post is the explainer I wish someone had handed me years ago, minus the hand-wringing.

What a boot environment actually is

A boot environment is just a bootable clone of your root dataset. ZFS snapshots are cheap and instantaneous because of copy-on-write, so cloning your entire root filesystem costs you almost nothing in disk space until things start diverging. The loader menu lets you pick which clone to boot into. That’s the whole trick.

In practice this means: you can snapshot your current, working system, do something reckless to a clone of it — upgrade the OS, mess with config files, try a kernel patch — and if it goes sideways, you reboot into the old environment like nothing happened. No reinstall, no restoring from backup, no apologizing to anyone.

Boot Environments Saved My Weekend, and They Can Save Yours Too

Setting it up

Most FreeBSD installers set up ZFS-on-root with a layout that already supports boot environments, so there’s a decent chance you’re ready to go without doing anything. The tool you want is bectl, which ships in base. Check what you’ve got:

  • bectl list — shows existing boot environments, which one is active, and which one is set to boot next.
  • bectl create — makes a new boot environment from the current one. This is your snapshot-before-you-break-it step.
  • bectl activate — sets which environment boots by default next time.
  • bectl mount — mounts a boot environment somewhere so you can poke around in it without booting into it.

If bectl list comes back empty or complains, your root pool probably wasn’t set up with the dataset layout boot environments expect — usually a zroot/ROOT/default style hierarchy. Retrofitting this onto an existing install is possible but annoying, and honestly, for a server that matters, it’s often faster to reinstall cleanly with the right layout than to fight your way into it after the fact. I’m not proud of how many hours I’ve spent trying to avoid that conclusion.

The actual workflow I use

Before any upgrade, OS version bump, or config change I’m not 100% sure about:

  • Name the new boot environment something that tells me what it’s for, not just a date. Something like pre-14.1-upgrade beats be-2 when you’re staring at the list six months later wondering what half of these are.
  • Create it, then activate it so it’s the one that boots next, and reboot into it before touching anything. This confirms the clone actually boots before you’ve changed a single file.
  • Do the upgrade or the risky change inside that environment.
  • If it works, keep going with your life. If it doesn’t, reboot, pick the old environment from the loader menu — or use bectl activate on the old one and reboot — and you’re back exactly where you started.

This is the part people skip because it feels like overkill for a “quick config change.” It is not overkill. The config change that breaks sshd at 2am is never the one you saw coming.

Cleaning up after yourself

Boot environments aren’t free forever — they diverge from the parent over time as packages update and files change, so old ones slowly eat disk space. I keep maybe three or four around: the current one, the previous stable one, and whatever I’m actively testing. Destroy the rest with bectl destroy once you’re confident you won’t need to roll back.

One gotcha that got me early on: you can’t destroy the currently booted environment, which seems obvious in hindsight but somehow wasn’t to past-me at 11pm trying to clean house before bed.

Boot Environments Saved My Weekend, and They Can Save Yours Too

Where this doesn’t save you

Boot environments protect the operating system and anything living in the root dataset. They don’t protect data on separate datasets that aren’t part of the boot environment hierarchy, and they’re not a substitute for real backups if a drive physically dies. Think of them as a very good undo button for “I upgraded and now things are weird,” not as disaster recovery.

They also won’t save you from your own bad decisions made inside the new environment that get synced to something external before you notice the problem — a database migration that already wrote to a remote system, for instance. Scope matters. Know what’s inside the boundary and what isn’t.

Why I bother writing this down

None of this is exotic. It’s been in base for years and plenty of people already use it religiously. But I still run into admins treating every upgrade like a one-way door, backing up configs by hand, crossing fingers, and hoping for the best. If you’re on ZFS root and you’re not using bectl, you’re carrying risk you don’t need to carry. It costs you five minutes before an upgrade and saves you the entire rest of your evening when something goes wrong, which it eventually will.