Skip to content
FreeBSDHow-To Published Updated 6 min readViews unavailable

How to Set Up a FreeBSD Poudriere Build Server for Custom Packages

A reproducible Poudriere workflow for supported FreeBSD build jails, pinned Ports trees, reviewed options, signed repositories, client tests, and upkeep.

Poudriere builds Ports packages in clean FreeBSD jails and produces a ready-to-export pkg repository. Isolation prevents dependencies installed on the build host from accidentally satisfying a port build, but it does not guarantee reproducibility by itself. The target release, architecture, Ports commit, options, make configuration, distfiles, Poudriere version, signing policy, and package list must also be controlled.

Build servers execute large, changing source trees and should not double as general-purpose application hosts. Give the service dedicated storage, restricted administration, monitoring, and a tested recovery plan.

Size and secure the build host

Record the host and storage baseline:

freebsd-version -kru
uname -m
zpool status -v
zfs list
pkg audit -F

CPU, RAM, swap, and storage needs depend heavily on the package set. Browsers, compilers, and language runtimes can consume many gigabytes and run for hours. Monitor pool capacity and memory during a representative build before choosing concurrency or tmpfs settings.

Install the maintained package and preserve its version in build records:

pkg install poudriere
poudriere version
pkg info poudriere

Review /usr/local/etc/poudriere.conf against the installed sample. Typical choices include a dedicated ZPOOL, BASEFS, POUDRIERE_DATA, and DISTFILES_CACHE. USE_TMPFS can accelerate builds but can exhaust memory or swap; do not enable it merely by copying a sample. Restrict the configuration and signing-key files to administrators.

Create a named Ports tree and record its commit

Create an explicit tree instead of relying on an unnamed mutable checkout:

poudriere ports -c -p default -m git+https
poudriere ports -l

The command checks out the current Ports tree. Record the Git commit from the managed tree and use it as part of the repository release manifest. poudriere ports -u -p default advances the tree; it does not preserve the old dependency graph forever. Update deliberately, review /usr/ports/UPDATING in the managed tree, and keep enough information to reconstruct or retain the previous working package set.

For strict change control, test a new Ports snapshot under another tree name before promoting it. A scheduled update followed immediately by publication gives upstream changes production authority without review.

Create a jail for a supported target release

The original 14.1-RELEASE target is end-of-life. Choose a release currently supported by the FreeBSD Security Team. For an amd64 host building for the current 15.1 release:

poudriere jail -c -j 151amd64 -v 15.1-RELEASE -a amd64
poudriere jail -l

The jail name is an operator label; include enough target information to prevent confusion. A build jail is not an iocage application jail and should be managed only through Poudriere.

Do not assume one host can build every architecture merely because -a accepts a value. Native compatibility, host kernel capabilities, and QEMU user-mode emulation determine which targets work. Cross-architecture builds require the documented emulation setup and separate validation on real target hardware or a representative system.

Patch-level jail updates are explicit:

poudriere jail -u -j 151amd64
poudriere jail -l

Record the resulting patch level. Never delete or upgrade the only known-good jail while clients still depend on its repository.

Define package origins and build options as source

Create a version-controlled package list containing one valid origin per line:

www/nginx
security/sudo
shells/bash

Store it as /usr/local/etc/poudriere.d/pkglist. Verify every origin exists in the selected tree and avoid obsolete versioned origins copied from old instructions.

Configure nondefault Ports options through Poudriere, not interactively during a bulk run:

poudriere options -j 151amd64 -p default \
  -f /usr/local/etc/poudriere.d/pkglist

Poudriere stores option files below /usr/local/etc/poudriere.d. Version those files along with any make.conf, blacklist, overlays, or local patches. Build options affect ABI, dependencies, attack surface, and compatibility; they are part of the package specification, not a workstation preference.

Preview and run the bulk build

Use the dry-run option to see what Poudriere plans to build or delete:

poudriere bulk -n -j 151amd64 -p default \
  -f /usr/local/etc/poudriere.d/pkglist

Then start the reviewed build:

poudriere bulk -j 151amd64 -p default \
  -f /usr/local/etc/poudriere.d/pkglist

Do not use -c reflexively; it cleans prior packages and logs and can remove the rollback set needed after a failed build. Control parallelism only after measuring resource use. A host swapping heavily or filling its pool can make builds slower and less reliable than a conservative job count.

Poudriere writes per-port logs and an HTML/JSON build view under its data directory. Review the final summary, skipped and failed ports, dependency failures, and package counts. A bulk command completing does not mean every requested origin was built successfully.

Sign and publish the repository

Poudriere can sign generated pkg metadata with the setting documented in its sample configuration:

PKG_REPO_SIGNING_KEY=/root/pkg-signing/repo.key

Keep the key mode restrictive and outside all web roots. For stronger separation, use Poudriere’s signing-command support with a dedicated signing service. Distribute only the public key to clients through a trusted channel.

The repository name is derived from jail, Ports tree, and optional set—not from the filename passed with -f. With this example, inspect:

ls -la /usr/local/poudriere/data/packages/151amd64-default
pkg repo --help

Poudriere produces ready-to-export metadata and manages publication. Do not rerun pkg repo over its live output while clients fetch it. Serve the completed package directory read-only over HTTPS. Keep build logs on a separate, authenticated location if they contain internal paths or diagnostics; nginx autoindex on is not required for pkg clients.

Prove the repository from a disposable client

Configure the client with signature_type: "pubkey", the trusted public-key path, and the HTTPS repository URL. Keep the official repository enabled only if mixing has been designed intentionally. Then run:

pkg -vv
pkg update -f
pkg rquery -r local '%n-%v'
pkg upgrade -n

Install and exercise representative packages on a system matching the target ABI. Test a package upgrade, service restart, configuration preservation, and rollback to the previous repository. Confirm the client rejects altered metadata and that the repository contains every dependency when the official source is disabled.

Operate the build pipeline, not just the command

Automate notifications and evidence before automating promotion. A healthy pipeline checks supported-release status, updates or pins the Ports tree, updates the build jail, runs the bulk build, audits resulting packages, signs, tests from a client, publishes atomically, and retains the prior repository. Failed or incomplete builds must never advance the production pointer.

Alert on build failure, repository age, jail release approaching end-of-life, stale Ports commit, signing failure, low storage, vulnerable packages, and clients still using an obsolete release. Periodically restore the Poudriere configuration, package list, option files, keys, and published repository onto a clean host.

The goal is not merely “packages compiled.” It is an attributable, signed package set that can be rebuilt, tested, promoted, monitored, and rolled back without improvisation.

Related:

Sources:

Comments