An essential tool in the FreeBSD porter’s arsenal (“porters” are the people who package third-party software, software like KDE Plasma, Haskell, ..) is poudriere, which is an evolution of the old tinderbox. It leverages ZFS and FreeBSD jails to do clean, consistent builds even on an otherwise occupied workstation, and can build for OS versions and architectures you’re not even running. Using the packages you’ve built can be slightly harder, so here’s some notes.

Poudriere has a chapter in the porter’s handbook. There are straightfoward guides to setting it up, also on DigitalOcean.

Most of those guides describe setting up nginx to serve the lovely and detailed build progress and results. I tend to follow the build progress in konsole, so I’m not interested in that part. What I do need to do is serve the resulting packages to other machines on my local network (e.g. my laptop) so that everything can enjoy the latest packages. That is doubly useful when trying out things like KDE Plasma on Wayland on FreeBSD, which needs plenty of testing and doesn’t work on all my hardware.

tl;dr Install lighttpd, write 2-line configuration file, run lighttpd; on client, configure pkg to use what lighttpd serves.

Server side

On the server side (that is, the workstation, that is going to serve up the packages to the rest of the network), I like to use lighttpd. It’s simpler-yet to setup and configure than nginx inside my LAN. I need to know three things:

  • hostname of the server (actually this is to tell the client)
  • free port number on the server (also tell the client this)
  • path to the package repository to serve

Poudriere builds packages in sets for a given ports tree on a given jail. That triple is important; the default set is an empty string, but ports and jail do not have a default. I have a jail called 13amd64 and a ports tree called area51. The ports tree is the KDE-FreeBSD experimental tree which is where we do our packaging work.

Sets can be important to keep incompatible or breaking changes apart from the regular stuff: I have a default set which is what production desktop machines run, and a set qt6 for future work and some other sets where I don’t want to break / destroy my day-to-day packages.

Poudriere puts packages under /usr/local/poudriere/data/packages/ by default, and there’s directories under there called <jail>-<ports tree>[-<set>]/ for all the combinations of jails, ports trees and sets that you’ve built. So I have a 13amd64-area51/ (the default set) and 13amd64-area51-qt6/ and others there.

To serve a package repository, serve that directory; nothing else is required. For lighttpd the configuration file is minimal (I picked port 3000):

server.document-root = "/usr/local/poudriere/data/packages/13amd64-area51"
server.port = 3000

That directory is world-readable (it’s just packages, after all) and so I can serve that up by putting those two lines in a file (e.g. /tmp/lighttpd.conf) and running lighttpd -D -f /tmp/lighttpd.conf as a regular user: no further configuration required and I can ctrl-C it to turn it off.

Client side

On the client side, pkg needs to be told to use the LAN repo that is now being served by lighttpd. Configuring package repositories means writing little configuration files (times like this, zypper ar seems like a wonderful thing to have). The configuration files live in /usr/local/etc/pkg/repos. There is extensive documentation on pkg in the FreeBSD user’s handbook.

That documentation doesn’t dive into “other package servers”, so:

  • First, disable the FreeBSD package servers, by creating a file FreeBSD.conf in that configuration directory with the following (one-line) content:
    FreeBSD: { enabled: no }

    Mixing-and-matching repositories was very bad headache-inducing, as of 2021 I’m not sure how well it works; for me, I have all the things I need built locally anyway, so I can switch the main repo off and just pull from my own poudriere server.

  • Add another file for the local repository: the name doesn’t really matter. I called mine beastie.conf after the hostname, and put in the hostname and port that the server uses:
    beastie: { url: "http://beastie:3000/", enabled: yes }

    Since the server is serving up just that one tree, I’m responsible for matching the tree with the machine I’m going to install on. Locally all I have are amd64-compatible machines and everything wants to run 13-STABLE, so my monoculture is just fine.


It is quick and easy to publish package repositories for local-area-network consumption with poudriere and lighttpd. This can save you plenty of builds and outside network bandwidth.