The FreeBSD Ports Collection vs. pkg: Choosing (and Combining) the Right Tool
A practical comparison of the FreeBSD Ports Collection and the pkg binary package manager, and how to use both together without breaking your system.
FreeBSD ships with two complementary ways to install third-party software: the Ports Collection, a tree of build recipes for roughly thirty thousand applications, and pkg, a binary package manager that consumes pre-built packages produced from that same tree. New users often treat this as a choice between “the slow way” and “the fast way,” but the two systems share a common pkgng database format and are designed to interoperate. Understanding how they fit together — and where mixing them can go wrong — is essential to running a maintainable FreeBSD system.
What the Ports Collection actually is
The Ports Collection is a directory tree, conventionally mounted at /usr/ports, containing a Makefile and supporting metadata for every supported piece of software. Each port’s Makefile doesn’t contain the software itself; it describes how to fetch the upstream source distribution, which patches to apply, which dependencies to resolve first, and how to configure, build, and package the result.
# Fetch the ports tree for the first time
portsnap fetch extract
# Build and install a port, resolving dependencies automatically
cd /usr/ports/www/nginx
make install clean
Because a port compiles from source on your machine, you can enable or disable optional features at build time through make config, which presents a dialog of compile-time OPTIONS — TLS backends, scripting language bindings, optional codecs, and so on. This is the primary reason to reach for ports instead of packages: fine-grained control over exactly what gets compiled in.
What pkg actually is
pkg is a binary package manager, conceptually closer to apt or dnf. Rather than compiling anything locally, it downloads pre-built packages from a remote repository — by default, pkg.freebsd.org — along with a signed repository catalog that lists every available package, its dependencies, and checksums.
# Bootstrap pkg on a fresh install
pkg bootstrap
# Install a binary package and its dependencies
pkg install nginx
# Search the repository catalog
pkg search nginx
Official FreeBSD package sets are built with a fixed, conservative set of OPTIONS for each port, chosen to work for the widest range of users. That’s the trade-off: pkg is fast and requires no local compiler toolchain, but you get whatever default configuration the FreeBSD package build cluster chose.
The shared foundation: pkgng
Since FreeBSD 10, both tools have used the same underlying package database and manifest format, internally referred to as pkgng. When a port is installed via make install, it registers itself in the exact same SQLite-backed database (/var/db/pkg/local.sqlite) that pkg install writes to. This means:
pkg infolists packages regardless of whether they came from ports or the binary repository.pkg delete,pkg upgrade, andpkg auditoperate uniformly across both.- Dependency resolution logic is shared, so a port can depend on a package installed via
pkgand vice versa.
This shared foundation is what makes mixing the two systems possible — but not always safe.
Where mixing gets dangerous
The most common failure mode is an ABI or OPTIONS mismatch: you install nginx from the binary repository (built with the default HTTP/2 and no --with-mail), then later build a port that depends on nginx with a specific option enabled. pkg doesn’t recompile existing packages to satisfy a new option requirement — it will either silently keep the mismatched build or complain about an unmet dependency, depending on the case.
The safer pattern is to pick one philosophy per machine, or at least per dependency graph:
# Tell the ports build system to prefer already-installed packages
# for dependencies, only building from source when you explicitly ask.
echo 'WITH_PKGNG=yes' >> /etc/make.conf
If you do need one specific port built from source with custom options while keeping everything else on binary packages, isolate it and its option-sensitive dependents, and rebuild that dependency chain deliberately with make config-recursive before installing.
Keeping the two in sync
pkg upgrade compares your installed package versions against the remote catalog and updates accordingly; it knows nothing about your local /usr/ports checkout. Conversely, portsnap or git (the ports tree has migrated to a Git repository) only updates the recipes, not what’s installed. Treat them as separate subscriptions:
# Refresh the recipe tree (source of truth for building)
portsnap fetch update
# Refresh the binary catalog and upgrade installed packages
pkg update && pkg upgrade
A useful sanity check after any bulk change is pkg check -d, which verifies that every installed package’s declared dependencies are actually satisfied on disk — catching the half-upgraded states that mixing build methods can produce.
A practical default
For most FreeBSD deployments, the pragmatic approach is: use pkg for everything by default, and drop down to a port only when you need a specific compile-time option the official package doesn’t offer, or you’re patching something locally. Keep a short, explicit list of which packages are “port-managed” so future-you (or a colleague) doesn’t accidentally pkg upgrade over a carefully hand-built dependency. FreeBSD’s tooling won’t stop you from mixing freely — the discipline has to come from you.