Skip to content
FreeBSDDeep Dive Published Updated 7 min readViews unavailable

The FreeBSD Ports Collection vs. pkg: Choosing (and Combining) the Right Tool

How FreeBSD Ports recipes, official pkg repositories, custom options, branches, locks, audits, and poudriere fit into one package workflow.

FreeBSD offers two related delivery paths for third-party software. The Ports Collection contains build recipes, patches, metadata, and option definitions for more than 36,000 applications. pkg installs binary packages produced from ports into a local package database. The important choice is not merely source versus binary speed; it is who controls options, which ports branch supplies dependencies, how builds are tested, and what repository is authoritative for upgrades.

Both paths register package manifests, files, dependencies, and versions with pkg. That common database makes a mixed installation technically possible. It does not make arbitrary combinations of a fresh main ports tree, quarterly official packages, and locally selected options dependency-compatible.

What the Ports Collection actually is

The Ports Collection normally lives at /usr/ports. A port skeleton includes a Makefile, distinfo checksums, a description, a package list or generated plist, optional patches, and framework metadata. Fetching a ports tree does not install applications or update already installed packages.

FreeBSD has used Git as the only version-control system for the Ports Collection since April 2021. portsnap examples are obsolete on current systems. Install Git and clone the branch that matches the intended package repository policy:

pkg install git
git clone --branch 2026Q3 https://git.FreeBSD.org/ports.git /usr/ports
git -C /usr/ports status --short --branch

2026Q3 is an example quarterly branch for July 2026, not a timeless command. Check the branch that exists and corresponds to the repository being used. To track the rapidly changing development tree, clone main deliberately and build a coherent package repository from it rather than mixing it with quarterly binaries.

A direct local build is useful for exploration or one exceptional port:

cd /usr/ports/www/nginx
make showconfig
make config
make install clean

The build may fetch source distfiles, apply FreeBSD patches, build dependencies, stage files, create package metadata, and register the result. make config-recursive exposes options for dependencies, but recursively accepting dialogs is not a reproducible policy. Saved options below /var/db/ports and /etc/make.conf must be backed up and reviewed.

Direct host builds use the host’s installed dependencies and can leave work directories or expose conflicts late. For a repeatable fleet, build packages in clean jails with poudriere and install those packages with pkg.

What pkg actually is

pkg(8) downloads repository catalogs and signed package artifacts, solves declared dependencies, runs package scripts, and records installed files. Invoking pkg on a system that has only the bootstrap stub offers to install the package manager; pkg bootstrap can perform that step explicitly.

# 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
pkg rquery '%n-%v %R' nginx

Official repositories build each port with their repository’s selected options. Inspect the installed package and its recorded options instead of assuming a feature:

pkg info nginx
pkg info -o nginx
pkg query '%Ok=%Ov' nginx
pkg -vv | less

pkg -vv shows repository configuration and ABI context. Package availability and repository strategy can differ by FreeBSD release and architecture. Repository files under /etc/pkg are system defaults; local overrides belong under /usr/local/etc/pkg/repos.

The shared foundation: pkgng

When a port is installed with make install, it registers a package manifest in the same local database managed by pkg. Consequently:

  • pkg info lists packages regardless of whether they came from ports or the binary repository.
  • pkg delete, pkg upgrade, and pkg audit operate uniformly across both.
  • Dependencies are expressed as package origins and versions regardless of where the installed package was built.

The database records the result, not the entire build environment. It cannot prove that a locally enabled option preserves ABI for every reverse dependency, that a local patch is compatible with the next official upgrade, or that two packages came from the same ports commit.

Where mixing gets dangerous

The Handbook explicitly warns that official pkg repositories normally track a quarterly ports branch while a checked-out ports tree may track main. Dependencies, flavors, defaults, and library versions can differ. Building one port from main against quarterly packages can force upgrades or replacements that the quarterly repository cannot later satisfy.

Options add another risk. Package dependencies usually express versions and origins, not every behavior implied by an option. A locally built library can retain the expected package name while omitting a feature or changing ABI expected by official dependents. A later pkg upgrade may replace the custom build with the repository build.

WITH_PKGNG=yes does not solve this. It was associated with migration from FreeBSD’s legacy package tools and is obsolete in a modern pkg environment. Adding it to /etc/make.conf neither aligns branches nor protects custom options.

For one temporary exception, pkg lock can prevent replacement:

pkg lock nginx
pkg lock -l
pkg unlock nginx

A lock also blocks security and compatibility upgrades, so it is a short-term operational marker, not a maintenance strategy. Document why it exists, monitor pkg audit, and set an expiry condition.

Poudriere: make custom builds behave like packages

The durable solution for custom options is to build an internally coherent repository with ports-mgmt/poudriere. Poudriere creates clean FreeBSD jails, builds selected ports and their dependency graphs, preserves per-port logs, and publishes a repository that pkg clients consume.

pkg install poudriere
poudriere jail -c -j 151Ramd64 -v 15.1-RELEASE -a amd64
poudriere ports -c -p quarterly -m git+https -B 2026Q3
poudriere options -j 151Ramd64 -p quarterly -f /usr/local/etc/poudriere.d/pkglist
poudriere bulk -j 151Ramd64 -p quarterly -f /usr/local/etc/poudriere.d/pkglist

Names and branches are examples. The host must be able to run the selected build jail, package lists must include the fleet’s required software, and signing and repository transport must be designed before clients trust it. Poudriere’s clean environment catches undeclared dependencies that a host build can accidentally satisfy.

Clients can disable the official repository and enable the internal one under /usr/local/etc/pkg/repos. Running two repositories at once requires priority and provenance controls; otherwise the solver may mix artifacts. Verify the selected source with pkg rquery and pkg -vv before rollout.

Keeping the two in sync

git pull updates recipes only. pkg update refreshes repository catalogs only. pkg upgrade changes installed packages. Keep those events separate in change records:

git -C /usr/ports pull --ff-only

pkg update -f
pkg upgrade -n
pkg upgrade

Read /usr/ports/UPDATING before major ports changes and use pkg updating for entries relevant to installed packages. The -n dry run reveals proposed installations, removals, and repository switches; review it instead of treating every solver plan as harmless.

Post-change checks should cover dependencies, files, shared libraries, provenance, and vulnerabilities:

pkg check -d -n -a
pkg check -s -a
pkg check -m -a
pkg audit -F
pkg info -ao

The dependency check uses -n so it reports missing dependencies without installing them; checksum and metadata checks cover every installed package with -a. Consult pkg-check(8) before adding repair flags that modify the database. pkg audit -F fetches the current vulnerability database and compares installed versions; a clean audit does not validate local patches or configuration.

Use pkg autoremove -n before removing automatic dependencies and pkg delete -n before removing a package with reverse dependencies. Back up /usr/local/etc, application state, repository configuration, poudriere option sets, and package manifests—not the package database alone.

A practical default

For most hosts, use one coherent pkg repository and avoid compiling on production. If official options are sufficient, the official repository minimizes local work. If custom options or patches are required, build the complete affected graph with poudriere and make that repository authoritative for the clients.

One hand-built port can be acceptable as a controlled exception, but it needs a matching ports branch, documented options and patches, reverse-dependency testing, a lock or repository policy, security monitoring, and a planned replacement path. The real boundary is not Ports versus pkg; it is reproducible packages from one dependency universe versus artifacts assembled from incompatible universes.

Related:

Sources:

Comments