The Poudriere is one of the topics I kinda omitted. I thought that official pkg(8) packages are more then enough and that occasional needed package recompilations are not the reasons to use a harvester such as Poudriere for that task. What was my needed recompilations? Usually the audio/lame because FreeBSD did not provided package for it for more then a decade while OpenBSD did … and multimedia/ffmpeg just to include that lame(1) support. Also at the beginning of exFAT format the sysutils/exfat-utils and sysutils/fusefs-exfat needed to be built by hand because of additional license agreement.

The Table of Contents for this article:
- Poudriere Name
- Poudriere Features
- FreeBSD Host Setup
- Poudriere Setup
- Poudriere Jails
- Poudriere Ports Tree
- Poudriere Packages to Build List
- Poudriere Options
- Nginx
- Memcached
- Ccache
- Poudriere Packages Build Process
- Our Repository Client Setup
- Next Builds
- Summary
There was as time when I had a dedicated script that would do just that – rebuild several ports as packages after the pkg upgrade cycle.
% cat pkg-recompile.sh
#! /bin/sh
# OPTIONS
PORTS='audio/lame multimedia/ffmpeg sysutils/exfat-utils sysutils/fusefs-exfat'
# ONLY root CAN BUILD PACKAGES/PORTS
if [ "$( whoami )" != "root" ]
then
echo "ER: only 'root' may use this script"
exit 1
fi
# BUILD PACKAGES
case ${1} in
# REBUILD PACKAGES
(b|build)
for PORT in ${PORTS}
do
pkg unlock -y ${PORT} 1> /dev/null 2> /dev/null
env BATCH=yes DISABLE_VULNERABILITIES=yes \
make -C /usr/ports/${PORT} build deinstall install clean &
MAKE=${!}
rctl -a process:${MAKE}:pcpu:deny=40
wait ${MAKE}
pkg lock -y ${PORT}
done
;;
# CLEAN
(c|clean)
for PORT in ${PORTS}
do
make -C /usr/ports/${PORT} clean
done
;;
# USAGE
(*)
echo "usage: ${0##*/} b|c|build|clean"
exit 1
;;
esac
Now none of that is needed anymore … unless you want to Connect FreeBSD to FreeIPA/IDM server … this is the case where Poudriere comes pretty handy. You may configure/rebuild needed packages by hand or use a tool that will do that for you and you will then just use its custom build packages repository to install them on multiple systems. Scale often changes many things and this is not different with Poudriere tool.
Poudriere Name
… its quite unfortunate to say the least. I needed some time to actually learn to remember that name properly. Not sure I have any useful tips here – I just split it in half to make it easier to remember – as poud and riere parts. In the beginning I interpreted the pond part as British Pound … uncorrected of course. I really wish the author would name it simpler – like Rebuild or Bob the Builder for example π
So what does Poudriere really means? Its a French translation of Powder Keg – which means a place where gunpowder was stored. There is some logic in that … as all the power (gun power) is in the packages … and Poudriere creates that place.
UPDATE: Dan Langille just reminded me that Poudriere replaced Powder Keg which was a similar tool – that explains the French translation for the name π
Poudriere Features
The Poudriere is a bulk package builder and port tester. It uses a ‘clean’ FreeBSD Jails containers to build packages for defined FreeBSD version (supported or not) and a ‘snapshot’ of a FreeBSD Ports tree. Most of the time it will be the latest FreeBSD Ports tree ‘snapshot’ but nothing prevents you from using older ‘snapshots’ with older packages versions when needed.
Then the results (and logs) of these builds are available as HTML pages and you can (and probably should) host them as some WWW server.
All of this seems scary, complicated and pointless bloat to some … but it gets simple and obvious once you try it. You know me – I have an allergy for bullshit and bloat and Poudriere is really far from both of them.
Its kinda like with the famous UNIX co-creator Dennis Ritchie quote – “UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity.” – just kidding – Poudriere actually is quite simple once you start using it – and that is probably the biggest obstacle to knowing it better. Just start using it (in a VM or in a Jail) to get to know it better and once you know it better – setup it properly and just use. Its far from over-complicated solutions such as SELinux, systemd(1) or Kubernetes.
FreeBSD Host Setup
First things first. The ‘three kings’ of and FreeBSD system configuration:
- /boot/loader.conf
- /etc/rc.conf
- /etc/sysctl.conf
Here they are:
The /boot/loader.conf file.
# cat /boot/loader.conf
security.bsd.allow_destructive_dtrace=0
kern.geom.label.disk_ident.enable=0
kern.geom.label.gptid.enable=0
cryptodev_load=YES
zfs_load=YES
The /etc/rc.conf file.
# cat /etc/rc.conf
clear_tmp_enable=YES
syslogd_flags="-ss"
sendmail_enable=NONE
hostname=fbsdpr
ifconfig_em0="inet 10.0.10.123/24"
defaultrouter="10.0.10.1"
sshd_enable=YES
dumpdev=AUTO
zfs_enable=YES
nginx_enable=YES
memcached_enable=YES
memcached_flags="-l localhost -m 8192"
The /etc/sysctl.conf file.
# cat /etc/sysctl.conf
vfs.zfs.min_auto_ashift=12
From the three above only the /etc/rc.conf is important as the other two only have settings from the bsdinstall(8) installer – as used with the Auto ZFS option.
We will also need to populate /etc/resolv.conf file to have DNS configured.
# echo nameserver 1.1.1.1 > /etc/resolv.conf
Pick your personal DNS server favorite here if the Cloudflare one does not suit your needs.
Poudriere Setup
First we need to install some packages – especially the Poudriere package – to make them more up-to-date we would prefer the latest branch of pkg(8) packages.
# sed -i '' -e 's|quarterly|latest|g' /etc/pkg/FreeBSD.conf
# grep latest /etc/pkg/FreeBSD.conf
url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
Now we can add needed packages.
# env ASSUME_ALWAYS_YES=yes \
pkg install -y \
poudriere \
portmaster \
screen \
tmux \
zsh \
beadm \
lsblk \
nginx \
git-lite \
htop \
tree \
ccache-memcached \
memcached \
groff
Some of them are less needed but they definitely does not hurt for workflow. Absolute minimum are:
- poudriere
- nginx
- git-lite
- ccache-memcached
- groff
Now … lets prepare some dirs and certs to make our packages signed.
# SSL=/usr/local/etc/ssl
# mkdir -p ${SSL}/{keys,certs} /usr/ports/distfiles
# chmod 0600 ${SSL}/keys
# openssl genrsa -out ${SSL}/keys/poudriere.key 4096
# openssl rsa -in ${SSL}/keys/poudriere.key -pubout -out ${SSL}/certs/poudriere.cert
As we will be using ZFS for Poudriere we will now create dedicated dataset for it.
# zfs create -o mountpoint=/usr/local/poudriere zroot/poudriere
Now we will create the Poudriere config file.
# cat << EOF > /usr/local/etc/poudriere.conf
ZPOOL=zroot
BASEFS=/usr/local/poudriere
ZROOTFS=/usr/local/poudriere
FREEBSD_HOST=ftp://ftp.freebsd.org
POUDRIERE_DATA=/usr/local/poudriere/data
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/poudriere.key
URL_BASE=http://10.0.10.123/
USE_TMPFS=yes
TMPFS_LIMIT=8
MAX_MEMORY=8
MAX_FILES=2048
DISTFILES_CACHE=/usr/ports/distfiles
KEEP_OLD_PACKAGES=yes
KEEP_OLD_PACKAGES_COUNT=3
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
CCACHE_DIR=/var/ccache
RESTRICT_NETWORKING=no
EOF
Its quite basic – yet it will do the job.
Poudriere Jails
Next we will create Poudriere Jails for each FreeBSD version we want to create pkg(8) repositories with packages.
I will create Jails for all FreeBSD 13.x versions – supported or not.
# poudriere jail -c -j 13-2-R-amd64 -v 13.2-RELEASE
# poudriere jail -c -j 13-1-R-amd64 -v 13.1-RELEASE
# poudriere jail -c -j 13-0-R-amd64 -v 13.0-RELEASE -m ftp-archive
Keep in mind that you need to specify additional -m ftp-archive argument for unsupported FreeBSD versions.
After some time you will end up with ready to use Poudriere FreeBSD Jails containers as shown below.
# poudriere jail -l
13-0-R-amd64 13.0-RELEASE-p13 amd64 ftp-archive 2023-04-28 03:22:05 /usr/local/poudriere/jails/13-0-R-amd64
13-1-R-amd64 13.1-RELEASE-p7 amd64 http 2023-04-27 23:17:13 /usr/local/poudriere/jails/13-1-R-amd64
13-2-R-amd64 13.2-RELEASE amd64 http 2023-04-27 23:15:30 /usr/local/poudriere/jails/13-2-R-amd64
Poudriere Ports Tree
You may already have an up-to-date FreeBSD Ports tree on your disk at usual /usr/ports location – but we need Poudriere to get its own one too.
# poudriere ports -c
After fetching one you can list it like that.
# poudriere ports -l
default git+https 2023-04-27 06:16:42 /usr/local/poudriere/ports/default
Poudriere Packages to Build List
Here is the best part – you do not need to build all 33000+ ports – you may specify just several of them. This is what we would do now.
# cat << EOF > /usr/local/etc/poudriere.d/list
sysutils/beadm
sysutils/lsblk
devel/m4
EOF
Accepting all possible licenses is also a good idea.
# echo DISABLE_LICENSES=yes >> /usr/local/etc/poudriere.d/make.conf
We will also specify which options should (and should not) be included in our built packages.
# cat << EOF > /usr/local/etc/poudriere.d/13-0-R-amd64-make.conf
OPTIONS_UNSET+= DOCS NLS X11 EXAMPLES
ALLOW_UNSUPPORTED_SYSTEM=yes
DISABLE_LICENSES=yes
EOF
# cat << EOF > /usr/local/etc/poudriere.d/13-1-R-amd64-make.conf
OPTIONS_UNSET+= DOCS NLS X11 EXAMPLES
ALLOW_UNSUPPORTED_SYSTEM=yes
DISABLE_LICENSES=yes
EOF
# cat << EOF > /usr/local/etc/poudriere.d/13-2-R-amd64-make.conf
OPTIONS_UNSET+= DOCS NLS X11 EXAMPLES
ALLOW_UNSUPPORTED_SYSTEM=yes
DISABLE_LICENSES=yes
EOF
Poudriere Options
You may choose the Poudriere packages options the same interactive way you do when you use the FreeBSD Ports tree.
To do that – here are the needed spells.
# poudriere options -c -j 13-0-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere options -c -j 13-1-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere options -c -j 13-2-R-amd64 -f /usr/local/etc/poudriere.d/list
Nginx
To make most of the Poudriere you will also need some web server. I have chosen Nginx for that task as its currently ‘the’ standard for the Internet.
Its setup is not complicated – just repeat steps below and you are done.
# service nginx enable
# sed -i '' -E 's|text/plain[\t\ ]*txt|text/plain txt log|g' /usr/local/etc/nginx/mime.types
# cat << EOF > /usr/local/etc/nginx/nginx.conf
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80 default;
server_name ${IP};
root /usr/local/share/poudriere/html;
location /data {
alias /usr/local/poudriere/data/logs/bulk;
autoindex on;
}
location /packages {
root /usr/local/poudriere/data;
autoindex on;
}
}
}
EOF
# service nginx restart
The /usr/local/etc/nginx/mime.types part will allow you to display the *.log files in the browser instead of ‘forcing’ the browser to pointlessly download them.
Our web server seems to work properly.
# sockstat -l4
USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
www nginx 95263 7 tcp4 *:80 *:*
root nginx 95262 7 tcp4 *:80 *:*
root sshd 706 5 tcp4 *:22 *:*
Memcached
As we will be using devel/ccache-memcached package to speed up builds – we would also need memcached to be running.
We already have needed configuration in /etc/rc.conf file so we only need to start it.
# grep memcached /etc/rc.conf
memcached_enable="YES"
memcached_flags="-l localhost -m 8192"
# service memcached restart
# sockstat -l4
USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
nobody memcached 37844 13 tcp4 127.0.0.1:11211 *:*
www nginx 69727 7 tcp4 *:80 *:*
root nginx 69726 7 tcp4 *:80 *:*
root sshd 706 5 tcp4 *:22 *:*
Ccache
We will now configure ccache(1) service.
Like success have many father the same ccache(1) have many configs – to be sure – we will propagate all of them.
We will use /var/ccache dir for the ccache(1) cache dir = but feel free to put it somewhere else or even with a dedicated ZFS dataset.
# mkdir -p /var/ccache
# cat << EOF > /usr/local/etc/ccache.conf
max_size = 0
cache_dir = /var/ccache
base_dir = /var/ccache
memcached_conf = --SERVER=localhost:11211
memcached_only = true
EOF
# cat << EOF > /root/.ccache/ccache.conf
max_size = 0
cache_dir = /var/ccache
base_dir = /var/ccache
memcached_conf = --SERVER=localhost:11211
memcached_only = true
EOF
# cat << EOF > /var/ccache/ccache.conf
max_size = 0
cache_dir = /var/ccache
base_dir = /var/ccache
memcached_conf = --SERVER=localhost:11211
memcached_only = true
EOF
The ccache(1) stats output after several builds below.
# ccache -s
cache directory /var/ccache
primary config /var/ccache/ccache.conf
secondary config (readonly) /usr/local/etc/ccache.conf
stats updated Fri Apr 28 04:10:17 2023
cache hit (direct) 4510
cache hit (preprocessed) 713
cache miss 2481
cache hit rate 67.80 %
called for link 5616
called for preprocessing 1476
multiple source files 18
compile failed 1143
preprocessor error 351
bad compiler arguments 72
unsupported source language 9
autoconf compile/link 3147
no input file 441
cleanups performed 0
files in cache 6303
cache size 26.6 MB
Poudriere Packages Build Process
Now as you have everything configured and ready – you may build your custom packages.
These next commands will build repositories with your configured packages.
# poudriere bulk -j 13-0-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere bulk -j 13-1-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere bulk -j 13-2-R-amd64 -f /usr/local/etc/poudriere.d/list
The Poudriere console output is pretty colorful and nice – here is how it looks.

Lets check how it look on the browser side.

Lets ‘click’ the 13-2-R-amd64-default name for some details.

It shows that 14 packages has been built in the process and all of them succeed.
Lets now click the date_time build name.

You will now see the details about that build run – with logs and time needed for each package build.
You can ‘click’ on the package name to get the build log details.

Here is how our three repositories look after the build process.
# ls -l /usr/local/poudriere/data/packages
total 26K
drwxr-xr-x 4 root wheel 14 2023-04-28 04:10 13-0-R-amd64-default/
drwxr-xr-x 4 root wheel 14 2023-04-28 04:06 13-1-R-amd64-default/
drwxr-xr-x 4 root wheel 14 2023-04-28 04:07 13-2-R-amd64-default/
# tree /usr/local/poudriere/data/packages
/usr/local/poudriere/data/packages
βββ 13-0-R-amd64-default
βΒ Β βββ All -> .latest/All
βΒ Β βββ Latest -> .latest/Latest
βΒ Β βββ meta.conf -> .latest/meta.conf
βΒ Β βββ meta.pkg -> .latest/meta.pkg
βΒ Β βββ meta.txz -> .latest/meta.txz
βΒ Β βββ packagesite.pkg -> .latest/packagesite.pkg
βΒ Β βββ packagesite.txz -> .latest/packagesite.txz
βββ 13-1-R-amd64-default
βΒ Β βββ All -> .latest/All
βΒ Β βββ Latest -> .latest/Latest
βΒ Β βββ meta.conf -> .latest/meta.conf
βΒ Β βββ meta.pkg -> .latest/meta.pkg
βΒ Β βββ meta.txz -> .latest/meta.txz
βΒ Β βββ packagesite.pkg -> .latest/packagesite.pkg
βΒ Β βββ packagesite.txz -> .latest/packagesite.txz
βββ 13-2-R-amd64-default
βββ All -> .latest/All
βββ Latest -> .latest/Latest
βββ meta.conf -> .latest/meta.conf
βββ meta.pkg -> .latest/meta.pkg
βββ meta.txz -> .latest/meta.txz
βββ packagesite.pkg -> .latest/packagesite.pkg
βββ packagesite.txz -> .latest/packagesite.txz
10 directories, 15 files
Our Repository Client Setup
Now we need to configure our FreeBSD clients to start using our Poudriere created repositories.
Here is what we need to do on such client.
# mkdir -p /usr/local/etc/pkg/repos
# cat << EOF > /usr/local/etc/pkg/repos/13-2-R-amd64.conf
13-2-R-amd64: {
url: "http:/10.0.10.123/packages/13-2-R-amd64-default/",
mirror_type: "http",
signature_type: "pubkey",
pubkey: "/usr/local/etc/ssl/certs/poudriere.cert",
enabled: yes,
priority: 100
}
EOF
# pkg update -f
Updating FreeBSD repository catalogue...
Fetching meta.conf: 100% 163 B 0.2kB/s 00:01
Fetching packagesite.pkg: 100% 7 MiB 626.5kB/s 00:11
Processing entries: 100%
FreeBSD repository update completed. 32980 packages processed.
Updating 13-2-R-amd64-HEAD repository catalogue...
Fetching meta.conf: 100% 163 B 0.2kB/s 00:01
Fetching packagesite.pkg: 100% 6 KiB 5.7kB/s 00:01
Processing entries: 100%
13-2-R-amd64-HEAD repository update completed. 14 packages processed.
All repositories are up to date.
We now have our first FreeBSD client system configured against our Poudriere created repository.
Lets install/update the m4 package for a test.
# pkg install m4
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
Updating 13-2-R-amd64-HEAD repository catalogue...
13-2-R-amd64-HEAD repository is up to date.
All repositories are up to date.
The following 2 package(s) will be affected (of 0 checked):
New packages to be INSTALLED:
m4: 1.4.19,1 [13-2-R-amd64-HEAD]
Installed packages to be REINSTALLED:
pkg-1.19.1_1 [13-2-R-amd64-HEAD] (options changed)
Number of packages to be installed: 1
Number of packages to be reinstalled: 1
The process will require 2 MiB more space.
9 MiB to be downloaded.
Proceed with this action? [y/N]: y
[1/2] Fetching pkg-1.19.1_1.pkg: 100% 8 MiB 8.7MB/s 00:01
[2/2] Fetching m4-1.4.19,1.pkg: 100% 214 KiB 218.7kB/s 00:01
Checking integrity... done (0 conflicting)
[1/2] Reinstalling pkg-1.19.1_1...
[1/2] Extracting pkg-1.19.1_1: 100%
[2/2] Installing m4-1.4.19,1...
[2/2] Extracting m4-1.4.19,1: 100%
Now some FreeBSD client notes … if you are a Linux fan you probably know that – for example on Red Hat Linux (and its clones) – its relatively easy to just list the configured repositories with yum repolist command.
# yum repolist
repo id repo name
appstream CentOS Linux 8 - AppStream
baseos CentOS Linux 8 - BaseOS
epel Extra Packages for Enterprise Linux 8 - x86_64
epel-modular Extra Packages for Enterprise Linux Modular 8 - x86_64
extras CentOS Linux 8 - Extras
… unfortunately there is not 1:1 equivalent on FreeBSD side for pkg(8) repositories.
The closest one that is available out of the box are:
# pkg -vv | grep -A 999 '^Repositories:'
Repositories:
FreeBSD: {
url : "pkg+http://pkg.FreeBSD.org/FreeBSD:13:amd64/latest",
enabled : yes,
priority : 0,
mirror_type : "SRV",
signature_type : "FINGERPRINTS",
fingerprints : "/usr/share/keys/pkg"
}
13-2-R-amd64: {
url : "http://10.0.10.123/packages/13-2-R-amd64-default/",
enabled : yes,
priority : 100,
mirror_type : "HTTP",
signature_type : "PUBKEY",
pubkey : "/usr/local/etc/ssl/certs/poudriere.cert"
}
# grep -h '^[^#]' /etc/pkg/* /usr/local/etc/pkg/repos/*
FreeBSD: {
url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
13-2-R-amd64: {
url: "http://10.0.10.123/packages/13-2-R-amd64-default/",
mirror_type: "http",
signature_type: "pubkey",
pubkey: "/usr/local/etc/ssl/certs/poudriere.cert",
enabled: yes,
priority: 100
}
Not very pretty.
After asking at GitHub for potential solution or pkg(8)man page overlook I was informed that there is no such option … so I came with my own POSIX /bin/sh shell scripts/functions for the rescue π
For example pkg-repo-var-en.sh that will list all pkg(8) repositories with their enabled/disabled status.
# ./pkg-repo-var-en.sh
REPO ENABLED PRIO URL
FreeBSD yes 0 pkg+http://pkg.FreeBSD.org/${ABI}/latest
13-2-R-amd64 yes 100 http://10.0.10.123/packages/13-2-R-amd64
# cat pkg-repo-var-en.sh
#! /bin/sh
( # HEADER
echo -e "\nREPO ENABLED PRIO URL"
for REPO in /etc/pkg/* /usr/local/etc/pkg/repos/*
do
REPOCUR=$( grep '^[^#]' "${REPO}" )
# REPO
echo "${REPOCUR}" | awk -F ':' '/\{[\ \t]*$/ {printf(" %s ",$1)}'
# ENABLED
echo "${REPOCUR}" | awk '/enabled:/ {printf(" %s ",$NF)}' | tr -cd '[a-zA-Z ]'
# PRIO
if echo "${REPOCUR}" | grep -q priority
then
echo "${REPOCUR}" | awk '/priority:/ {printf(" %s ",$NF)}' | tr -cd '[0-9 ]'
else
echo -n " 0 "
fi
# URL
echo "${REPOCUR}" | grep '^[^#]' | awk -F'"' '/url:/ {print $2}' | tr -d ','
done 2> /dev/null
) | column -t 2> /dev/null
Next Builds
So … you have successfully build your custom repository once … these are the steps to create new up-to-date package every next time.
// UPDATE JAILS
# poudriere jail -u -j 13-0-R-amd64
# poudriere jail -u -j 13-1-R-amd64
# poudriere jail -u -j 13-2-R-amd64
// UPDATE PORTS
# poudriere ports -u
// BUILD REPOSITORIES
# poudriere bulk -j 13-0-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere bulk -j 13-1-R-amd64 -f /usr/local/etc/poudriere.d/list
# poudriere bulk -j 13-2-R-amd64 -f /usr/local/etc/poudriere.d/list
For example – if there are not new packages/changes – this is how the Poudriere output would look like.

Summary
Not sure what should I add here more … I definitely need to compare Poudriere against ports-mgmt/synth alternative one day … but it is not this day – as Aragorn once said π
Pick your packages to customize and have fun.
Take care.
Like this:
Like Loading...