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
shpkg install poudriere
Ports Install
shcd /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:
shpkg install ccache # Optional: speeds up repeated builds
Configuration
Poudriere's configuration lives in /usr/local/etc/poudriere.conf. Copy the sample and edit:
shcp /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
shpoudriere 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
shpoudriere 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:
shpoudriere jail -u -j 14amd64
Listing Jails
shpoudriere 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
shpoudriere ports -u -p default
Multiple Trees
You can maintain multiple ports trees for testing:
shpoudriere ports -c -p quarterly -B 2026Q1 poudriere ports -c -p latest -m git+https
Listing Trees
shpoudriere 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:
shpoudriere 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:
shpoudriere options -j 14amd64 -p default security/openssl
Build
shpoudriere bulk -j 14amd64 -p default -f /usr/local/etc/poudriere.d/pkglist.txt
This:
- Creates a clean jail clone
- Installs build dependencies
- Builds each port and its dependencies in dependency order
- Packages the results
- Destroys the jail clone
Build a Single Port
shpoudriere 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:
shpoudriere 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:
shPKG_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:
shpoudriere 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
shPARALLEL_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
shpoudriere 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:
shpoudriere 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:
shpoudriere 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.