poudriere in jails with zfs

There are tons of tutorials out there on how to get poudriere running in a jail. But most of them have in common, that they either miss options or have too many of them. So what I try with this, is to get the most condensed version of the whole process to get poudriere up and running, serving the generated packages.

In the following for 4 sections, we create a jail with the name poudriere1, using partitions tank/jails/poudriere1 and tank/jails/ppoudriere1/data. For connectivity the jail will get the IP

setting up zfs

The first step is to create both ZFS partitions on the host system with the following commands:

$ zfs create -o mountpoint=/jails/poudriere1 tank/jails/poudriere1
$ zfs create -o jailed=on tank/jails/poudriere1

The option jailed=on makes the partition completely available for the jail to manipulate, so that poudriere can create new partitions. This also makes it unavailable for the host system to mount as the jail can change the mountpoint.

system preparations

In addition to the ZFS partitions, we also need a separate network address to listen on, so that we can make the packages available using nginx.

For this, we define a new network interface lo1. This is then used by the jail to add its IP.

To make this work, place the following line in rc.conf:


and start the new interface with the command

service netif cloneup

Next the jail must be able to reach the internet. As I had pf already in place, I added the following NAT rule

nat on em0 inet from to any -> (em0)

which redirects all traffic from to the external interface.

Reload pf to make this change working

$ service pf reload

But to make this work, the host must also be told to do packet forwarding in the rc.conf


After that restart the routing service using

$ service routing restart

configuring the jail

The next step is to configure the jail, so that it can start and run poudriere. This is done in /etc/jail.conf. The following section shows the settings needed and a short explanation. More can be looked up in main 8 jail.

poudriere1 {
  # first we set the permissions for the jail
  # enforce_statfs allows the jail to get information about the mountpoint. With
  # 1 it is able to see its root mountpoint and below. This is needed to be able
  # to mount any file system.
  # This option allows the jail to mount file systems.
  # The following options enable mounting the specific file systems, needed to
  # get poudriere running.
  # nullfs is used for remounting the ports tree into the child jails.
  # tmpfs can be disabled when poudriere is told to not use it.
  # This is needed to mount ZFS file systems.
  # As poudriere is using chflags, the jails needs to be allowed its usage.
  # This option needs to be set, as poudriere grants that permission its jails.
  # Allow this jail to run its own child childs up to the number.

  # Set a hostname visiable through jls.
  host.hostname = "$name";
  # Set the path to the jails root directory.
  path = "/jails/$name";

  # Automatically mount and unmount the dev file system, needed for ZFS and also
  # used in poudriere jails.
  # Set the IPs to use. is handled automatically, localhost
  # is reused from the host system. 

  # Boot up the jail at start using the RC system. This enables the use of rc.conf.
  exec.start += "/bin/sh /etc/rc";
  # After the jail is started, grant the ZFS partition to the jail, so that it
  # can manage the work partition itself.
  exec.poststart += "zfs jail $name rpool/jails/$name";
  # On stopping the jail, go through the RC system.
  exec.stop += "/bin/sh /etc/rc.shutdown";
  # This option makes sure, that the jail is running without any environment
  # variables set.

installing and starting the jail

To install the jail we need to fetch a release, extract it into the jail root, make some last adjustments and then start it up.

To fetch a release the following command can be used (adjust the version to your need):

$ fetch -o /tmp/base.txz

Then extract the base package into the root using the following command

$ tar -xf /tmp/base.txz -C /jails/poudriere1

Next the timezone has to be set, the resolv.conf copied and the hostname set.

$ echo 'hostname="poudriere1"' > /jails/poudriere1/etc/rc.conf
$ cp /etc/localtime /jails/poudriere1/etc/localtime
$ cp /etc/resolv.conf /jails/poudriere1/etc/resolv.conf

Instead of using the resolv.conf of the host system it would also be possible to use unbound on the host and use that as the DNS server in the jail.

With that done, we can now start the jail using the command:

$ jail -c poudriere1

If the command fails with the message that it can’t find /jails/poudriere1, check that the ZFS partition hasn’t jailed set to on and that it is mounted in the correct place.

Accessing the jail using jexec it is possible to check if the basic setup works.

$ jexec poudriere1
root@poudriere1:/ # echo 'GET index.html' | nc 80
root@poudriere1:/ # zfs list

If you do not get html output, check if the interface has the IPs defined and that you set up the routing correctly. If the ZFS partitions are missing, check if the permissions are set in /etc/jail.conf.

configuring poudriere

To install poudriere, build it from ports or install it through pkg.

root@poudriere1/ # portsnap fetch extract
root@poudriere1/ # cd /usr/ports/ports-mgmt/poudriere
root@poudriere1/ # make install clean

Before starting with setting up poudriere, we have to mount the work directory somewhere where we can actually use it:

root@poudriere1/ # zfs set mountpoint=/poudriere tank/jails/poudriere1/work
root@poudriere1/ # zfs mount tank/jails/poudriere1/work

Now we can set up poudriere the environment. Change the following settings in /usr/local/etc/poudriere.conf:

# relative to the zpool
# enable when you have set mount.allow.tmpfs in the jail.conf
# size in GB to allow for the ram drive
# set to no when you have the linux driver enabled
# set to no when you do not want to keep old versions of packages around

With that done, we can build the first jail for poudriere to work with. I mostly follow the FreeBSD handbook

root@poudriere1/ # # create a jail with the 10.2-RELEASE
root@poudriere1/ # poudriere jail -c -j 102amd64 -v 10.2-RELEASE
root@poudriere1/ # # list available jails
root@poudriere1/ # poudriere jail -l

If there is a problem with the jail creation, you can run the command using -x to get the debug output.

poudriere -x jail -c -j 102amd64 -v 10.2-RELEASE

If it happens that you get the error Unable to execute id(1) in jail. a permission is missing in /etc/jail.conf. To find out which is missing, check the debug output for the jail command. All permissions are added on the command line, so it is easier to compare the list of permissions with what poudriere wants to grant its jails.

Next we create the ports tree for poudriere to use:

root@poudriere1/ # # create a new ports tree
root@poudriere1/ # poudriere ports -c -p local
root@poudriere1/ # # list the installed port trees
root@poudriere1/ # poudriere ports -l

The next step is to create the list of packages poudriere should build into /usr/local/etc/poudriere.d/base-pkglist:


It is also possible to use sets, for example for build options. The next code would be the make.conf for the base set, when placed in /usr/local/etc/base-make.conf:


Using these files, the ports can be configured using the command:

root@poudriere1/ # poudriere options -j 102amd64 -p local -z base -f /usr/local/etc/poudriere.d/base-pkglist

To start a bulk run, which build all packages in the list, use the bulk command

root@poudriere1/ # poudriere bulk -j 102amd64 -p local -z base -f /usr/local/etc/poudriere.d/base-pkglist

You can find the created packages in the directory /poudriere/data/packages.

configuration of nginx in the jail

The configuration of nginx in the jail is done in a moment.

For that nginx has to be installed. Using the freshly built packages us the following command (adjust the path according to your setup):

pkg install /poudriere/data/packages/102amd64-local-base/All/nginx-

After that you can configure nginx in the file /usr/local/etc/nginx/nginx.conf.

The server configuration needs adjustment and nginx must be told where the data resides:

server {

  location / {
    root      /poudriere/data/packages;
    autoindex on;

This will create an automatic index of the directory content and make it available for download. With this, it can be consumed by pkg on other systems.

If you also want to serve the logs, you can enable them with the following code

location /logs {
  root      /poudriere/data/logs/bulk;
  autoindex on;

configuration of nginx outside of the jail

To forward incoming requests to the jail nginx instance, the following location option can be used:

location / {
  include proxy_params;

more information

This should help to get things up and running. If you need further information, please see the following man pages

There is also good documentation found on

There are also some tools to run jails, instead of making it raw like I did in this entry.