FreeBSD Ports After Portsnap: Git Howto & ZFS Tips

 

If you’ve read my earlier article on managing FreeBSD ports, you may have noticed that portsnap no longer works. Don’t panic — your system isn’t broken, it’s just evolving. From FreeBSD 15 onward, portsnap has been retired. The ports tree is now managed with Git (see official release notes). Think of it as moving from cassette tapes to streaming: same music, easier updates. This new workflow replaces both portsnap and Subversion.  With Git, updates are faster, branching is simpler, and history is clear enough that even your future self will thank you.

FreeBSD Daemon reading documentation
BSD Daemon reading documentation

Background:

Portsnap is deprecated: FreeBSD 15 no longer supports portsnap. The ports tree is now distributed exclusively via Git.
SVN retired: Since 2021, the FreeBSD project has migrated all repositories (src, ports, docs) from Subversion to Git.
Unified workflow: Git provides a single, modern version control system for ports, source, and documentation.

Setting up ports:

Before we start, let’s make sure our ZFS is set up properly:

Let’s find out the dataset name:

root@f5:~ # zfs list /usr/ports
NAME              USED  AVAIL  REFER  MOUNTPOINT
zroot/usr/ports    96K   891G    96K  /usr/ports

Now that we know the Dataset name, in our case, it is the default zroot. Let’s check the attributes:

root@f5:~ # zfs get all zroot/usr/ports
NAME             PROPERTY                VALUE                   SOURCE
zroot/usr/ports  type                    filesystem              -
zroot/usr/ports  compressratio           1.00x                   -
zroot/usr/ports  mounted                 yes                     -
zroot/usr/ports  recordsize              128K                    default
zroot/usr/ports  mountpoint              /usr/ports              inherited from zroot/usr
zroot/usr/ports  compression             on                      inherited from zroot
zroot/usr/ports  atime                   off                     inherited from zroot
... (many defaults omitted)

The important bits here are compression, recordsize, and atime. The rest are defaults.

Before cloning, let’s tune ZFS for better performance /usr/ports:

  • atime=off: stops extra metadata writes when files are read.
  • recordsize=16K: ideal for many small files, reduces wasted space.
  • compression=zstd-19: strong balance of speed and savings. Use zstd-9 if CPU is limited.

These settings will keep your ports tree lean and efficient.

root@f5:~ # zfs set atime=off zroot/usr/ports
root@f5:~ # zfs set compression=zstd-19 zroot/usr/ports
root@f5:~ # zfs set recordsize=16K zroot/usr/ports

Now we verify:

root@f5:~ # zfs get compression,recordsize,atime zroot/usr/ports
NAME             PROPERTY     VALUE           SOURCE
zroot/usr/ports  compression  zstd-19         local
zroot/usr/ports  recordsize   16K             local
zroot/usr/ports  atime        off             local
Workflow Impact
  • Git pulls will be faster with compression and a small record size.
  • Disk usage will shrink significantly (ports tree ~900 MB raw → ~300 MB compressed).
  • Turning off atime avoids extra I/O during builds.

Back to ports…

Install Git

root@f5:~ # pkg install git

Ensure /usr/ports is empty before cloning.

root@f5:~ # rm -r /usr/ports/*
rm: /usr/ports/*: No such file or directory
root@f5:~ # rm -r /usr/ports/.git*
rm: /usr/ports/.git*: No such file or directory
Clone the ports tree

This will take some time…

root@f5:~ # git clone https://git.freebsd.org/ports.git /usr/ports
Cloning into '/usr/ports'...
remote: Enumerating objects: 6745346, done.
remote: Counting objects: 100% (936/936), done.
remote: Compressing objects: 100% (120/120), done.
remote: Total 6745346 (delta 923), reused 816 (delta 816), pack-reused 6744410 (from 1)
Receiving objects: 100% (6745346/6745346), 1.50 GiB | 6.21 MiB/s, done.
Resolving deltas: 100% (4084572/4084572), done.
Updating files: 100% (168524/168524), done.

The full clone weighs in at 6,745,346 bytes — not much by modern standards, but enough to hold the entire ports tree. Imagine it as a library card: you’ve checked out the whole collection, and now you need to learn the new system for keeping it updated.

Updating and Managing Ports

To update the ports tree (like the old portsnap fetch update):

cd /usr/ports
git pull --rebase

For convenience, create an alias or script so you only need one command. (~/.cshrc for tcsh, ~/.bashrc for bash, etc.):

alias portsupdate='cd /usr/ports && git pull --rebase'

Usage:

portsupdate
Option 2: Shell Function (more flexible)

For bash/zsh:

portsupdate() {
  cd /usr/ports || return
  git pull --rebase
}

This avoids leaving you stuck in /usr/ports after the update

Option 3: Script in /usr/local/bin

Create /usr/local/bin/portsupdate

#!/bin/sh
cd /usr/ports || exit 1
exec git pull --rebase

Make it executable:

chmod +x /usr/local/bin/portsupdate

Now it behaves like a system command:

portsupdate

You can still use cdport from my old post, Managing FreeBSD Ports Before 15.x.

This is your Git‑era portsnap: a single command that updates the tree without clutter. Alias it as portsupdate or even portsnap if you want to preserve muscle memory continuity.

Benefits of Git-based Ports

  • Speed: Updates are faster than portsnap snapshots.
  • Transparency: Every commit is visible with diffs and history.
  • Flexibility: ranching allows quarterly snapshots (like 2025Q4) alongside main.
  • Collaboration: Easier to fork, patch, and merge.
  • Compression options: ZFS compression (zstd-19) saves space with little cost.
Deduplication: The Tempting Mirage

Deduplication sounds cool — like a magic trick that saves space. But when you’re compiling ports, it’s more like asking your CPU to juggle while it’s already running a marathon.
Ports are packed with tiny, unique files, so dedup has almost nothing to “deduplicate.” Instead, it burns cycles comparing blocks while your compiler taps its foot impatiently.
Compression and recordsize tuning are the real heroes here. Dedup? It’s the party guest who eats all the snacks but doesn’t help clean up. And remember: after compiling, you’ll wipe the source tree anyway, so any “savings” vanish.
Let your CPU build ports, not audition for a hashing contest.