Setting up Poudriere on FreeBSD

2024-12-23

Table of Contents:

FreeBSD has a very sophisticated infrastructure built around compiling ports from source, which inspired Gentoo' portage[1]. But unlike Gentoo, it's configuration management is mostly still manual (you are recommended to use the manual TUI config). In this article I will record how I set up and manage my poudriere build system, and use it as a backend(?) for my package manager.

Options for FreeBSD Ports

As a long time Gentoo user, I found the FreeBSD way of configuring packages kind of overly complex, and there's no userland software that let you quickly query packages and options, like Gentoo's euse. So I have developed a compromise solution, that is configure selected packages manually, and use global options to set options for all dependencies.

The following snippet is my current (2024-12-23) configuration for optimizing speed and minimizing unused dependencies for a headless server.

# /etc/make.conf
#
# Global options
#
# Headless server settings
OPTIONS_UNSET+=X11 WAYLAND DBUS XCB LIBWACOM
# Performance
OPTIONS_SET+=OPTIMIZED_CFLAGS AVX AVX2 AVX512 LTO
OPTIONS_SET+=XXHASH
GOAMD64=v4

The variables are documented in /usr/ports/Mk/bsd.options.mk

Using it alongside with Poudriere

Install and setup poudriere according to the official website and the manual page (It's really helpful), then we can configure poudriere

make.conf in jails

Since poudriere use jails, it makes more sense to use the poudriere wrapper of make config, namely poudriere-options to manage them.

To use /etc/make.conf as the single source of truth, link it to /usr/local/etc/poudriere.d/make.conf. Poudriere will then use that file for jails.

After adding options in make.conf, if you have configured your options by hand , make sure to run poudriere options -r to clean up the existing config. Since if you don't use force, it will only be applied to new packages.

Setting up Poudriere

Jail

This one is simple, follow the official documentation:

poudriere jail -c -j default -v 14.2-RELEASE -a amd64

This sets up a jail with version 14.2-RELEASE, if switching version, I prefer yanking it and making a new one, which can be optimized with ccache

Port

FreeBSD Port have mainly two branches: quarterly and latest (git main). Latest is much easier to maintain and upgrade (just git pull), and quarterly is easier to build (less updates) but harder to maintain (you need to manually switch branches, but there's a bug report to fix that [2]). Currently I have chosen latest, not sure if it'll backfire though.

Currently I only need one port collection for my headless server, so to keep on single source of truth, I use local clone of /usr/ports, and configure poudriere to create a local port tree with it:

# man poudriere-ports
poudriere ports -c -p local -m null -M /usr/ports

poudriere ports -l yields:

PORTSTREE METHOD TIMESTAMP           PATH
local     null   2024-11-17 13:22:24 /usr/ports

I update it with a script:

#!/bin/sh
# To update the ports repo and update the jail with binary pkg
JAIL=default

git -C /usr/ports pull --rebase
git -C /usr/ports gc --auto
git -C /usr/ports pull --depth=3 # Trim the log

poudriere jail -u -j $JAIL

Optimization

The resulting binary performance and building time for poudriere can be optimized in several ways:

ccache

Poudriere is conservative about dependency updates, which means sometimes a dependency update will trigger update of the reverse dependency i.e. the package that depends on it[3]. On Gentoo, reverse dependency is not rebuilt by default, but you can do it manually with revdep-rebuild.

Since the package itself is not changed, we can use ccache to skip these compilations:

# /etc/make.conf
# CCACHE
WITH_CCACHE_BUILD=yes
# /usr/local/etc/poudriere.conf
# Un-comment line 170:

# ccache support. Supply the path to your ccache cache directory.
# It will be mounted into the jail and be shared among all jails.
# It is recommended that extra ccache configuration be done with
# ccache -o rather than from the environment.
CCACHE_DIR=/var/cache/ccache

other poudriere optimization

Some other optimizations for poudriere, customized through /usr/local/etc/poudriere.conf, they are clearly documented in /usr/local/etc/poudriere.conf.sample

Tmpfs

# Use tmpfs(5)
# This can be a space-separated list of options:
# wrkdir    - Use tmpfs(5) for port building WRKDIRPREFIX
# data      - Use tmpfs(5) for poudriere cache/temp build data
# localbase - Use tmpfs(5) for LOCALBASE (installing ports for packaging/testing)
# all       - Run the entire build in memory, including builder jails.
# yes       - Enables tmpfs(5) for wrkdir and data
# no        - Disable use of tmpfs(5)
# EXAMPLE: USE_TMPFS="wrkdir data"
USE_TMPFS=yes

don't use tmpfs for rust:

# List of package globs that are not allowed to use tmpfs for their WRKDIR
# Note that you *must* set TMPFS_BLACKLIST_TMPDIR
# EXAMPLE: TMPFS_BLACKLIST="rust"
TMPFS_BLACKLIST="rust"

Parallel building

On /usr/local/etc/poudriere.conf

ALLOW_MAKEJOBS=yes
PARALLEL_JOBS=2

and on /etc/make.conf, set MAKE_JOBS_NUMBER=8, this yields 2x8 = 16 threads which matches my 16 CPU threads

Fetch binary packages for huge packages

There are several packages that just takes way too much time and power to build (cough cough lang/rust), since running an optimized compiler won't outweigh the heavy compile time it costs (especially with lto), we can fetch binary packages for them instead.

On Gentoo, this is handled really nicely with slot system, --getbinpkg and -rust suffix with virtual/ category (Gentoo's package manager is definitely superior, there's no doubt about that).

We can have kind of the same thing for FreeBSD by using PACKAGE_FETCH_WHITELIST by simply edit the last line in /usr/local/etc/poudriere.conf:

PACKAGE_FETCH_WHITELIST="gcc* rust llvm* node*"

This make poudriere try to use binary if the options match, to make sure of that, I have lang_rust_UNSET+=LTO set in /etc/make.conf also, you can append -v flag when running poudriere bulk to see why it's rejected


  1. Gentoo Website

  2. FreeBSD Bug tracker

  3. Poudriere compiles too many packages when I update