LinkedIn Sourceforge

Vincent's Blog

Pleasure in the job puts perfection in the work (Aristote)

Converting a FreeBSD Jail with pkgbasify

Posted on 2026-10-10 09:40:00 from Vincent in FreeBSD VM

Give a shoutout to  Claude.ai For years, keeping a FreeBSD system up to date meant running freebsd-update(8), which patches the base system as a single, opaque block. pkgbase changes that model: the base system is split into ordinary packages that you can install, query and upgrade with pkg(8), just like any other software. The pkgbasify script can convert an existing installation, but it rewrites the whole base system, which makes it a poor first experiment on a machine you care about. A jail is a much better laboratory. It is isolated from the host, cheap to create and, with ZFS, trivial to roll back. In this article I walk through converting a classic jail to pkgbase from start to finish, including the one jail parameter that makes the conversion fail if you forget it. I then show how to upgrade the result to a newer minor release, all without touching the host.


Introduction

If you want to transfer a Jail from the old method with Sets to the new method of packages, the pkgbasify is the tool that will help you for such conversion.

If you want to create a fresh new Jail, then use directly the new method

pkgbasify rewrites the base system of whatever machine it runs on. Running it on the host by mistake would be a bad day, so the whole procedure here is carried out inside the jail, through jexec(8). The host's pkg configuration, repositories and base system stay untouched. If anything goes wrong, a single zfs rollback brings the jail back to its starting point.

One rule applies to everything that follows: a jail shares the kernel of its host, so the jail's userland must never be newer than the host's kernel. Check the host first with freebsd-version(1) using freebsd-version -k.

Step 1: Create a traditional jail

Start with a jail installed the classic way, from the base.txz distribution set. Create a dataset with zfs-create(8) (or a plain directory if you do not use ZFS), download the tarball matching your host release with fetch(1), and extract it with tar(1):

zfs create -o mountpoint=/vm/jls/test zroot/vm/jls/test
fetch -o /tmp/base.txz https://download.freebsd.org/releases/amd64/15.0-RELEASE/base.txz
tar -xf /tmp/base.txz -C /vm/jls/test
cp /etc/resolv.conf /vm/jls/test/etc/

Adjust the release in the URL to the one you want. Copying resolv.conf(5) gives the jail working DNS, which it needs to reach the package servers later.

Step 2: Take a snapshot

Before changing anything, snapshot the pristine jail with zfs-snapshot(8):

zfs snapshot zroot/vm/jls/test@fresh

The conversion replaces the entire base system, and a partially converted jail is awkward to repair. With the snapshot you can retry as many times as needed.

Step 3: Configure the jail

Create /etc/jail.conf.d/test.conf. The file format is described in jail.conf(5):

test {
  path = "/vm/jls/test";
  host.hostname = "test";
  ip4 = inherit;
  allow.chflags;
  exec.start = "/bin/sh /etc/rc";
  exec.stop  = "/bin/sh /etc/rc.shutdown";
  exec.clean;
  mount.devfs;
}

The important line is allow.chflags, one of the parameters documented in jail(8). Some files in the base system carry file flags such as schg (see chflags(1)), and by default a jail is not allowed to change them. Without this parameter the conversion aborts with the following message, leaving the jail half converted:

pkg: Package FreeBSD-runtime has files with flags that cannot be managed in this jail.
Set allow.chflags in the jail configuration.

The parameter only affects files inside the jail. Start the jail with service(8) using service jail onestart test.

Step 4: Run pkgbasify inside the jail

pkgbasify is not available as a package. It is a single Lua script that runs on the flua interpreter included in the base system. Download it from the project's GitHub repository (check the current location before fetching) and put it inside the jail's filesystem:

fetch -o /vm/jls/test/root/pkgbasify.lua \
  https://raw.githubusercontent.com/FreeBSD-Foundation/pkgbasify/main/pkgbasify.lua

Then execute it through jexec, never directly on the host:

jexec test /usr/libexec/flua /root/pkgbasify.lua

The script bootstraps pkg inside the jail, creates the base repository configuration, and replaces the installed base system with packages. You may see warnings about boot environments (see bectl(8)), which do not exist in a jail, and your snapshot is the real safety net. If an error interrupts the run, fix the cause, roll back to @fresh and start again. The script's own advice, to rerun with --force once the problem is resolved, also works, but a clean rollback is the more predictable path.

Step 5: Point the repository at the release you want

After the conversion, the jail's base repository is defined in /usr/local/etc/pkg/repos/FreeBSD.conf. Repository files are documented in pkg.conf(5). Depending on what the script wrote, this file may contain very little, so a sed(1) one-liner is not reliable. As explained by Vermaden in his blog, replace the whole file with an explicit definition:

FreeBSD-base: {
  url: "pkg+https://pkg.FreeBSD.org/${ABI}/base_release_1",
  mirror_type: "srv",
  signature_type: "fingerprints",
  fingerprints: "/usr/share/keys/pkgbase-${VERSION_MAJOR}",
  enabled: yes
}

Leave ${ABI} and ${VERSION_MAJOR} exactly as written, because pkg expands them itself. The number at the end of base_release_1 is the minor release: _0 for 15.0, _1 for 15.1, and so on. Tracking the development branches uses base_latest instead.

Step 6: Update and upgrade

With the repository defined, bring the jail up to date from inside it with pkg-update(8) and pkg-upgrade(8):

jexec test pkg update
jexec test pkg upgrade

Take a second snapshot before the upgrade (zfs snapshot zroot/vm/jls/test@pre-upgrade) if you want a restore point between the conversion and the upgrade. When it finishes, restart the jail so every service runs the new userland:

service jail onerestart test

Step 7: Verify the result

Confirm that the jail now reports the expected version and that the base system really comes from packages, using freebsd-version(1), pkg-info(8) and pkg-which(8):

jexec test freebsd-version -ru
jexec test pkg info | grep FreeBSD-
jexec test pkg which /bin/sh

15.1-RELEASE-p4
15.1-RELEASE-p4
FreeBSD-acct-15.1p4            System resource accounting
FreeBSD-acpi-15.1              Advanced Configuration and Power Interface (ACPI) utilities
FreeBSD-apm-15.1               Intel / Microsoft APM BIOS utility
FreeBSD-at-15.1                Scheduled and batch command utilities
FreeBSD-atf-15.1               Automated Testing Framework
FreeBSD-atf-dev-15.1           Automated Testing Framework (development files)
FreeBSD-atf-lib-15.1           Automated Testing Framework (libraries)
... (211 lines)
FreeBSD-zstd-15.1              Fast, lossless compression algorithm
FreeBSD-zstd-dev-15.1          Fast, lossless compression algorithm (development files)
FreeBSD-zstd-lib-15.1          Fast, lossless compression algorithm (libraries)
/bin/sh was installed by package FreeBSD-runtime-15.1p4

On a host running 15.1 the version output should look like 15.1-RELEASE-p4 (the patch level will differ over time). The kernel value reported inside a jail comes from the host, so seeing the right release there also confirms that host and jail are in step.

Finally, look for configuration files that pkg could not merge cleanly with find(1):

jexec test find / -name '*.pkgsave' -xdev

Each .pkgsave file is your previous version of a config file that was replaced by the packaged default. Compare them with the new files and carry over any local changes.

Keeping the jail up to date

From now on, freebsd-update is no longer used for this jail. Patch levels arrive through the normal package workflow:

jexec test pkg update
jexec test pkg upgrade

The repository URL only needs to change when you move to the next minor release, for example from base_release_1 to base_release_2. As always, upgrade the host kernel first, then the jails.

Conclusion

Converting a jail to pkgbase takes only a handful of steps: build a traditional jail, snapshot it, allow file flag changes, run pkgbasify inside it, fix the repository definition, and upgrade with pkg. Doing the experiment in a throwaway jail keeps the host safe and gives you a cheap way to learn how pkgbase behaves before deciding whether to use it on systems that matter.



👍 1, 👎 0
displayed: 48



What is the first vowel of the word Python?