FreeBSD.software
Home/Guides/Poudriere on FreeBSD: Custom Package Builder Review
review·2026-04-09·9 min read

Poudriere on FreeBSD: Custom Package Builder Review

Review of Poudriere on FreeBSD: building custom packages from ports, jail-based builds, repository management, CI integration, and when to use it vs pkg.

Poudriere on FreeBSD: Custom Package Builder Review

Poudriere is FreeBSD's official tool for building packages from the ports tree in isolated jail environments. If you have ever needed a package compiled with non-default options, a port that does not have an official binary package, or a private package repository for your infrastructure, Poudriere is how you do it. This review covers installation, configuration, building packages, repository management, CI integration, and when Poudriere is worth the overhead compared to using pkg alone.

For background on the difference between binary packages and ports, see our FreeBSD pkg vs ports guide.

Why Poudriere Exists

The FreeBSD project builds official binary packages from the ports tree using Poudriere. The same tool is available to you. Here is why you would use it:

  • Custom compile options: The official packages use default options. If you need PostgreSQL with ICU support, or OpenSSL built with a specific cipher configuration, you build your own.
  • Ports not in the official repository: Some ports exist in the tree but do not have official packages.
  • Consistent builds: Poudriere builds in a clean jail every time, eliminating "works on my machine" issues.
  • Private repositories: Distribute custom packages across your infrastructure via a standard pkg repository.
  • Security: Build from audited source code with your own compiler flags and patches.

Installation

Package Install

sh
pkg install poudriere

Ports Install

sh
cd /usr/ports/ports-mgmt/poudriere make install clean

Dependencies

Poudriere needs ZFS (strongly recommended but not strictly required) and a ports tree. It also benefits from:

sh
pkg install ccache # Optional: speeds up repeated builds

Configuration

Poudriere's configuration lives in /usr/local/etc/poudriere.conf. Copy the sample and edit:

sh
cp /usr/local/etc/poudriere.conf.sample /usr/local/etc/poudriere.conf

Essential Settings

sh
# /usr/local/etc/poudriere.conf # ZFS pool for Poudriere data ZPOOL=zroot # Base directory BASEFS=/usr/local/poudriere # FreeBSD package mirror FREEBSD_HOST=https://download.FreeBSD.org # Build parallelism PARALLEL_JOBS=8 # Tmpfs for workdirs (speeds up builds, requires RAM) USE_TMPFS="all" # ccache support CCACHE_DIR=/var/cache/ccache # Package signing # PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/poudriere.key # Build log directory POUDRIERE_DATA=${BASEFS}/data

ZFS Configuration

Poudriere works best with ZFS. It uses clones for build jails, making creation and teardown nearly instant:

sh
# Verify ZFS is available zpool list

If you do not have ZFS, Poudriere falls back to directory-based operation, which is slower and uses more disk space.

Setting Up Build Jails

A build jail provides the clean FreeBSD environment where ports are compiled.

Create a Jail

sh
poudriere jail -c -j 14amd64 -v 14.2-RELEASE -a amd64

This downloads and extracts a FreeBSD 14.2-RELEASE base system. The -j flag names the jail for reference.

Multiple Jails for Multiple Versions

sh
poudriere jail -c -j 13amd64 -v 13.4-RELEASE -a amd64 poudriere jail -c -j 14amd64 -v 14.2-RELEASE -a amd64

This lets you build packages for different FreeBSD versions from the same host. Cross-architecture builds (e.g., building ARM packages on AMD64) are also supported.

Updating Jails

Keep build jails current with security patches:

sh
poudriere jail -u -j 14amd64

Listing Jails

sh
poudriere jail -l

Managing Ports Trees

Poudriere needs a ports tree to build from.

Create a Ports Tree

sh
# Default: fetch from Git (FreeBSD's official repository) poudriere ports -c -p default

Update the Ports Tree

sh
poudriere ports -u -p default

Multiple Trees

You can maintain multiple ports trees for testing:

sh
poudriere ports -c -p quarterly -B 2026Q1 poudriere ports -c -p latest -m git+https

Listing Trees

sh
poudriere ports -l

Building Packages

Define a Package List

Create a file listing the ports you want to build:

sh
# /usr/local/etc/poudriere.d/pkglist.txt www/nginx databases/postgresql16-server security/openssh-portable lang/python311 sysutils/tmux editors/vim net/rsync security/sudo

Configure Port Options

Set custom build options before building:

sh
poudriere options -j 14amd64 -p default -f /usr/local/etc/poudriere.d/pkglist.txt

This presents the options dialog for each port, similar to make config in the ports tree. Options are saved to /usr/local/etc/poudriere.d/options/.

To set options for a single port:

sh
poudriere options -j 14amd64 -p default security/openssl

Build

sh
poudriere bulk -j 14amd64 -p default -f /usr/local/etc/poudriere.d/pkglist.txt

This:

  1. Creates a clean jail clone
  2. Installs build dependencies
  3. Builds each port and its dependencies in dependency order
  4. Packages the results
  5. Destroys the jail clone

Build a Single Port

sh
poudriere bulk -j 14amd64 -p default www/nginx

Dependencies are built automatically.

Monitoring Builds

Poudriere includes a web interface for monitoring builds. Enable it:

sh
# Install a lightweight web server pkg install nginx # Point it at Poudriere's HTML output # In nginx.conf: # server { # listen 8080; # root /usr/local/poudriere/data/logs/bulk; # autoindex on; # }

Or watch from the terminal:

sh
poudriere status -j 14amd64

Build logs are stored in /usr/local/poudriere/data/logs/bulk/.

Repository Management

After a successful build, Poudriere creates a package repository under:

shell
/usr/local/poudriere/data/packages/14amd64-default/

Serving the Repository

Set up a web server to serve the packages:

sh
# Nginx configuration server { listen 80; server_name pkg.internal.example.com; root /usr/local/poudriere/data/packages; autoindex on; }

Configuring Clients

On client machines, create a repository configuration:

sh
# /usr/local/etc/pkg/repos/custom.conf custom: { url: "http://pkg.internal.example.com/14amd64-default", enabled: yes, priority: 10 }

Optionally disable the official repository if you want clients to only use your custom packages:

sh
# /usr/local/etc/pkg/repos/FreeBSD.conf FreeBSD: { enabled: no }

Package Signing

Sign packages to ensure integrity:

sh
# Generate a signing key openssl genrsa -out /usr/local/etc/ssl/keys/poudriere.key 4096 openssl rsa -in /usr/local/etc/ssl/keys/poudriere.key -pubout -out /usr/local/etc/ssl/certs/poudriere.cert

Enable signing in /usr/local/etc/poudriere.conf:

sh
PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/poudriere.key

Distribute the public certificate to clients:

sh
# In /usr/local/etc/pkg/repos/custom.conf custom: { url: "http://pkg.internal.example.com/14amd64-default", signature_type: "pubkey", pubkey: "/usr/local/etc/ssl/certs/poudriere.cert", enabled: yes }

Make Configuration

Apply global make options via /usr/local/etc/poudriere.d/make.conf:

sh
# /usr/local/etc/poudriere.d/make.conf OPTIONS_UNSET+=DOCS EXAMPLES X11 DEFAULT_VERSIONS+=python=3.11 DEFAULT_VERSIONS+=pgsql=16 DEFAULT_VERSIONS+=ssl=openssl CFLAGS+=-O2 -pipe

For jail-specific make.conf:

sh
# /usr/local/etc/poudriere.d/14amd64-make.conf # Settings specific to the 14amd64 jail

CI/CD Integration

Poudriere fits naturally into a CI/CD pipeline for infrastructure.

Automated Builds with Cron

sh
# /etc/cron.d/poudriere 0 2 * * 0 root /usr/local/bin/poudriere ports -u -p default && /usr/local/bin/poudriere bulk -j 14amd64 -p default -f /usr/local/etc/poudriere.d/pkglist.txt

Git Hook Integration

Trigger builds when the package list or options change:

sh
#!/bin/sh # post-receive hook poudriere bulk -j 14amd64 -p default -f /usr/local/etc/poudriere.d/pkglist.txt

Build Verification

After a build, check for failures:

sh
poudriere status -j 14amd64 -l

Parse the log output for failures in automated scripts. Non-zero exit codes from poudriere bulk indicate build failures.

Performance Tuning

tmpfs

The USE_TMPFS setting dramatically speeds up builds by using RAM for work directories:

sh
# In poudriere.conf USE_TMPFS="all"

This requires sufficient RAM. For building large ports (Chromium, LibreOffice), expect peak tmpfs usage of 4-8 GB per parallel job.

ccache

Enable ccache to speed up rebuilds:

sh
# In poudriere.conf CCACHE_DIR=/var/cache/ccache # Create the cache directory mkdir -p /var/cache/ccache

ccache is most effective when you rebuild frequently (e.g., after ports tree updates). First builds see no benefit.

Parallel Jobs

sh
PARALLEL_JOBS=8

Set this to the number of CPU cores available for building. More parallelism requires more RAM, especially with tmpfs enabled.

Disk Space

Poudriere uses significant disk space:

  • Ports tree: ~1 GB
  • Each build jail: ~1 GB (ZFS clones minimize this)
  • Build work directories: 2-10 GB per parallel job (tmpfs reduces disk I/O)
  • Package repository: varies, typically 5-20 GB depending on the package list
  • Build logs: can grow large over time, prune regularly

Common Workflows

Updating Packages After a Security Advisory

sh
# Update the ports tree poudriere ports -u -p default # Update the build jail poudriere jail -u -j 14amd64 # Rebuild affected ports poudriere bulk -j 14amd64 -p default security/openssl # Clients update via pkg # pkg update && pkg upgrade

Testing a Port Update

sh
# Create a test ports tree poudriere ports -c -p test -m git+https -B main # Build the updated port poudriere testport -j 14amd64 -p test www/nginx # testport builds the port and runs its test suite

Building for Multiple Architectures

sh
poudriere jail -c -j 14arm64 -v 14.2-RELEASE -a aarch64 poudriere bulk -j 14arm64 -p default -f /usr/local/etc/poudriere.d/pkglist.txt

When to Use Poudriere vs pkg

Use pkg (binary packages) when:

  • Default compile options are acceptable
  • You want minimal maintenance overhead
  • You are running a small number of servers
  • Build time is not available

Use Poudriere when:

  • You need custom compile options
  • You run 10+ servers and want consistency
  • Security policy requires building from source
  • You need ports that lack official packages
  • You want to pin specific package versions

For most environments, the answer is both: use pkg as the default and Poudriere for the handful of ports that need custom options.

Verdict

Poudriere is essential infrastructure for any FreeBSD shop that goes beyond default packages. It is well-engineered, ZFS integration is excellent, and it produces standard pkg repositories that work with the existing package management toolchain.

The learning curve is moderate. Initial setup takes an hour. Understanding the full options (multiple jails, trees, make.conf hierarchy) takes longer. But once configured, it runs unattended and produces consistent results.

Rating: 9/10 -- A mature, well-designed tool that does exactly what it promises. The only deduction is the steep initial resource requirement (disk, RAM, build time) that makes it impractical for single-server deployments.

Frequently Asked Questions

How much disk space does Poudriere need?

Minimum 50 GB for a basic setup (one jail, one ports tree, small package list). For building a full desktop stack, plan for 100+ GB. ZFS compression (LZ4) helps significantly.

Can I run Poudriere in a jail?

Yes, but the build jail needs privileges. Set children.max=10 (or higher) in the parent jail configuration to allow nested jails. Performance may be slightly reduced compared to running on bare metal.

How long does a full build take?

Depends on the package list and hardware. Building 50 common server packages on an 8-core Xeon takes 30-60 minutes. Building a full desktop environment (Xorg, KDE/GNOME) takes 4-8 hours.

How do I handle build failures?

Check the build log in /usr/local/poudriere/data/logs/bulk/. Common causes: missing dependencies, options conflicts, and ports that do not build on the target FreeBSD version. Fix the issue, then rebuild with the same poudriere bulk command -- it skips already-built packages.

Can I build packages for an older FreeBSD version?

Yes. Create a jail with the older version:

sh
poudriere jail -c -j 13amd64 -v 13.4-RELEASE -a amd64

Build against it. This is a common pattern for organizations running mixed FreeBSD versions.

How do I remove old packages from the repository?

Poudriere does not automatically clean obsolete packages. Use:

sh
poudriere pkgclean -j 14amd64 -p default -f /usr/local/etc/poudriere.d/pkglist.txt

This removes packages not in the current list or not required as dependencies.

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.