You are here

Build your own FreeBSD ports and make packages out of them using jails, poudriere and portshaker

The FreeBSD logoImage


You make custom ports of your applications. You would like to use pkg to install them on your production and development machines.


Build a poudriere server with portshaker!


  • Custom ports come in the form of a git port tree
  • Everything should run in a jail. Git has many dependencies you don't want on your host systems (I'm looking a you bash).
  • Everything should use ZFS as much as possible

Setting up the base system

Make sure the following kernel modules are loaded:

  • tmpfs
  • linux
  • linux64
  • nullfs
  • procfs
  • fdescfsu
  • linprocfs

Load them now:

# kldload tmpfs
# kldload linux
# kldload linux64
# kldload nullfs
# kldload procfs
# kldload fdescfsu
# kldload linprocfs

Make sure they're loaded on boot:

# sysrc -f /boot/loader.conf tmpfs_load="YES"
# sysrc -f /boot/loader.conf linux_load="YES"
# sysrc -f /boot/loader.conf nullfs_load="YES"
# sysrc -f /boot/loader.conf procfs_load="YES"
# sysrc -f /boot/loader.conf fdescfsu_load="YES"
# sysrc -f /boot/loader.conf linux64_load="YES"
# sysrc -f /boot/loader.conf linprocfs_load="YES"

Make sure jails can use IPC

# sysctl security.jail.sysvipc_allowed=1
# sysrc jail_sysvipc_allow=YES

Create a ZFS dataset for the poudriere system:

# zfs create zroot/poudriere

Create a jail, and make sure the jail is allowed to create build jails and manage its ZFS dataset. Here the relevant configuration for a ezjail jail:

# cat /usr/local/etc/ezjail/poudriere
export jail_poudriere_zfs_datasets="zroot/poudriere"
export jail_poudriere_parameters="children.max=20 allow.mount allow.mount.tmpfs allow.mount.devfs allow.mount.procfs allow.mount.zfs allow.mount.nullfs allow.mount.linprocfs allow.raw_sockets allow.socket_af allow.sysvipc allow.chflags enforce_statfs=1 ip6=inherit ip4=inherit"

Setting up poudriere in the jail

Install poudriere and dialog4ports:

# make -C /usr/ports/ports-mgmt/dialog4ports install clean
# make -C /usr/ports/ports-mgmt/poudriere install clean

Set the moundpoint of the ZFS dataset:

# zfs set mountpoint=/poudriere zroot/poudriere

Make sure the ZFS dataset for the ports and the distfiles exists:

# zfs create zroot/poudriere/ports
# zfs create zroot/poudriere/ports/distfiles

Configure poudriere in /usr/local/etc/poudriere.conf:

ZPOOL: zroot
ZROOTFS: /poudriere
DISTFILES_CACHE: /usr/local/poudriere/ports/distfiles

Create a port tree, but don't populate it. It will be done by portshaker later:

# poudriere ports -c -F -f none -M /usr/local/poudriere/ports/main -p main

Create a build jail:

# poudriere jail -c -j 111x64 -v 11.1-RELEASE -a amd64
# poudriere jail -l
111x64   11.1-RELEASE amd64 ftp    2017-12-08 14:58:09 /usr/local/poudriere/jails/111x64

Hehe, we have a jail in a jail.

Setting up portshaker

We're going to build a port tree consisting of the regular FreeBSD main one (using portsnap) merged with our own (using git).

Portskaker is an awesome tool. It will merge different port tree, handling conflicts intelligently, and even merging the UIDs/GIDs files properly.

This example will only build a single port tree, but you can configure as many as you want.

Install portshaker:

# make -C /usr/ports/ports-mgmt/portshaker install clean

Install git:

# Make -C /usr/ports/devel/git install clean

Write /usr/local/etc/portshaker.conf:

main_merge_from="portsnap custom"

Write the portshaker tree source for portsnap, in /usr/local/etc/portshaker.d/portsnap:

. /usr/local/share/portshaker/portshaker.subr
if [ "$1" != '--' ]; then
  err 1 "Extra arguments"
run_portshaker_command $*

Write the portshaker tree source for custom, in /usr/local/etc/portshaker.d/custom:

. /usr/local/share/portshaker/portshaker.subr
if [ "$1" != '--' ]; then
  err 1 "Extra arguments"
run_portshaker_command $*

Make sure both files are executable.

Fetch both port trees:

# portshaker -U

You should see portsnap and a git clone being executed.

Merge the port trees:

# portshaker -M

You should have a complete port tree in /usr/local/poudriere/ports/main/. Look for your custom ports and check that they're here.

Setting up nginx

We want to use poudriere's web interface.

Install nginx:

# make -C /usr/ports/www/nginx install clean

Add a server in /usr/local/etc/nginx/nginx.conf

server {
    listen 80 default;
    server_name _;
    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;

That's it! No CGI, no proxy, no whatever. Just plain files to serve.

Test it all

Write a list of package into a pkglist file (/usr/local/etc/poudriere.d/someports-pkglist):


Set the port options:

poudriere options -j 111x64 -p main -f /usr/local/etc/poudriere.d/someports-pkglist

Start the build:

# poudriere bulk -J 8 -j 110x64 -p main -f /usr/local/etc/poudriere.d/someports-pkglist -v -v -v
[00:00:00] ====>> Creating the reference jail... done
[00:00:00] ====>> Mounting system devices for 111x64-main
[00:00:00] ====>> Mounting ports/packages/distfiles
[00:00:00] ====>> Using packages from previously failed build
[00:00:00] ====>> Mounting packages from: /usr/local/poudriere/data/packages/111x64-main
[00:00:00] ====>> Copying /var/db/ports from: /usr/local/etc/poudriere.d/111x64-options
[00:00:00] ====>> Appending to make.conf: /usr/local/etc/poudriere.d/111x64-make.conf
/etc/resolv.conf -> /usr/local/poudriere/data/.m/111x64-main/ref/etc/resolv.conf
[00:00:00] ====>> Starting jail 111x64-main
[00:00:00] ====>> Logs: /usr/local/poudriere/data/logs/bulk/111x64-main/2017-12-08_16h59m32s
[00:00:00] ====>> Loading MOVED
[00:00:01] ====>> Calculating ports order and dependencies
Creating repository in /tmp/packages: 100%
Packing files for repository: 100%
[00:16:07] ====>> Committing packages to repository
[00:16:07] ====>> Removing old packages
[00:16:07] ====>> Built ports: ports-mgmt/pkg devel/automake-wrapper devel/autoconf-wrapper print/indexinfo devel/pkgconf devel/pcre devel/gettext-runtime www/nginx lang/perl5.24 devel/gettext-tools devel/p5-Locale-gettext devel/gmake misc/help2man print/texinfo devel/m4 devel/libtool devel/autoconf devel/automake devel/libevent sysutils/tmux
[111x64-main] [2017-12-08_16h59m32s] [committing:] Queued: 20 Built: 20 Failed: 0  Skipped: 0  Ignored: 0  Tobuild: 0   Time: 00:16:07
[00:16:07] ====>> Logs: /usr/local/poudriere/data/logs/bulk/111x64-main/2017-12-08_16h59m32s
[00:16:07] ====>> Cleaning up
[00:16:07] ====>> Unmounting file systems

Connect to the web interface:
Look at the build lists:
Look at the build report:

Use your pkg repository on other systems

Now that our packages are built, let's use them on other systems:

Make sure the config directory exists:

# mkdir -p /usr/local/etc/pkg/repos

Write a pkg repository config file in /usr/local/etc/pkg/repos/poudriere.conf:

poudriere: {
    url: "",
    mirror_type: "http",
    enabled: yes,
    priority: 100

Writing a secure version of this file is left as an exercise for the reader.

Update the local database:

# pkg update

Install your custom ports:

# pkg install custom-port

Enjoy your new custom ports infrastructure!