Jails is a very stable and mature feature in FreeBSD. However, the standard interface for jails is slightly too cumbersome for my taste. The Iocage project provides a higher level interface for administrating jails.
Currently, the project is in a bit of flux,
the original (now legacy) version
was written in shell script.
The new version is written in python.
The new version is not available as a binary package,
it’s only available from ports,
under the name
sysutils/py3-iocage, and requires Python 3.6.
Assuming that the host is on the network
and that the host network interface is called
(another typical name would be
This tutorial will use the “shared IP” method for assigning IP addresses to the jails. IP addresses created on the jails will be created as additional IP addresses on the hosts network interface.
There is another option, which is called
This allows creating bridge interfaces on the host,
and vnet interfaces in the jails.
The main benefit of this is that the jails
do not see the interfaces of the host, they only see the vnet interfaces.
However this is experimental, and requires compiling the kernel with
Lets add the following to
And then install the package.
$ cd /usr/ports/sysutils/py3-iocage/ $ sudo make install
Assuming no errors, iocage can now be activated.
Lets activate it on the pool
This will create a filesystem structure under
which will contain all data for releases, jails, templates, etc.
$ sudo iocage activate tank ZFS pool 'tank' successfully activated.
A big benefit that iocage has over standard FreeBSD jails, is that all the necessary data for a jail, including all of its properties (such as IP address), are stored under this filesystem. Where as with standard jails you would have to backup both the jail, as well as its config files located elsewhere in the root filesystem.
Fetch a release
Before creating a jail, a release must be downloaded.
$ sudo iocage fetch -r 11.0-RELEASE
Create a template from the release
Templates are not mandatory to use, but it is one way to have your personal global settings applied to subsequent jails.
Lets create a jail called
$ sudo iocage create tag=template -r 11.0-RELEASE b650df9e-f7e8-48b8-b18b-1992cd005473 (template) successfully created!
$ sudo iocage set ip4_addr="igb0|10.0.1.100/24" template Property: ip4_addr has been updated to igb0|10.0.1.100/24
$ sudo iocage set host_hostname="template" template Property: host_hostname has been updated to template
$ sudo iocage start template * Starting b650df9e-f7e8-48b8-b18b-1992cd005473 (template) + Started OK + Starting services OK
list command should show something like this.
$ sudo iocage list +-----+----------+-------+----------+--------------+------------+ | JID | UUID | STATE | TAG | RELEASE | IP4 | +=====+==========+=======+==========+==============+============+ | 1 | b650df9e | up | template | 11.0-RELEASE | 10.0.1.100 | +-----+----------+-------+----------+--------------+------------+
Configure the template
Lets open a shell inside the jail
$ sudo iocage console template
Lets confirm that this is the right machine.
$ hostname template
Configure any universal settings, for example:
- Create users.
- Make sure the uid/gid in the jail matches the uid/gid on the host.
This is useful if using
nullfsto mount file systems from the host in the jail.
- Add SSH keys to users.
- Configure shell for users.
Make the jail into a template
Lets stop the jail, and set
template=yes on it.
$ sudo iocage stop template * Stopping b650df9e-f7e8-48b8-b18b-1992cd005473 (template) + Stopping services OK + Removing jail process OK
$ sudo iocage set template=yes template b650df9e-f7e8-48b8-b18b-1992cd005473 (template) converted to a template.
list command should now not show the jail
list -t command should show the template.
$ sudo iocage list -t +-----+----------+-------+----------+--------------+------------+ | JID | UUID | STATE | TAG | RELEASE | IP4 | +=====+==========+=======+==========+==============+============+ | - | b650df9e | down | template | 11.0-RELEASE | 10.0.1.100 | +-----+----------+-------+----------+--------------+------------+
Create a jail from the template
Lets create a jail called
www based on the previously created template.
$ sudo iocage create tag=www -t template 2ae9abbc-4070-4af1-8056-432e81ee1f84 (www) successfully created! $ sudo iocage set ip4_addr="igb0|10.0.1.2/24" www
list command should now show something like this:
$ sudo iocage list +-----+----------+-------+-----+--------------+----------+ | JID | UUID | STATE | TAG | RELEASE | IP4 | +=====+==========+=======+=====+==============+==========+ | - | 2ae9abbc | down | www | 11.0-RELEASE | 10.0.1.2 | +-----+----------+-------+-----+--------------+----------+
JID is empty,
that is because a jail only receives an ID once its started.
At this point all that really exists is a ZFS filesystem,
with all the files and directories for the jail’s root filesystem in it.
Lets see what that looks like.
$ zfs list NAME USED AVAIL REFER MOUNTPOINT tank/iocage/jails/2ae9abbc-4070-4af1-8056-432e81ee1f84 202M 3.51T 100K /iocage/jails/2ae9abbc-4070-4af1-8056-432e81ee1f84 tank/iocage/jails/2ae9abbc-4070-4af1-8056-432e81ee1f84/root 202M 3.51T 811M /iocage/jails/2ae9abbc-4070-4af1-8056-432e81ee1f84/root
Lets start the jail.
$ sudo iocage start www * Starting 2ae9abbc-4070-4af1-8056-432e81ee1f84 (www) + Started OK + Starting services OK
start command has finished,
list command will show that
JID has been set to a procedurally generated Jail ID.
$ sudo iocage list +-----+----------+-------+-----+--------------+----------+ | JID | UUID | STATE | TAG | RELEASE | IP4 | +=====+==========+=======+=====+==============+==========+ | 2 | 2ae9abbc | up | www | 11.0-RELEASE | 10.0.1.2 | +-----+----------+-------+-----+--------------+----------+
And its IP address will be visible under
as an additional address next to the host machine’s address.
$ ifconfig igb0 igb0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500 options=6403bb<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,JUMBO_MTU,VLAN_HWCSUM,TSO4,TSO6,VLAN_HWTSO,RXCSUM_IPV6,TXCSUM_IPV6> ether 0c:c4:7a:86:64:da inet 10.0.0.11 netmask 0xff000000 broadcast 10.255.255.255 inet 10.0.1.2 netmask 0xffffff00 broadcast 10.0.1.255 nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL> media: Ethernet autoselect (1000baseT <full-duplex>) status: active
An alternative to templates
Even though iocage templates work perfectly fine, the concept of templates does have some limitations. Since jails are created from templates only once, any updates to a template will not be reflected in the concrete jails. This means that you most likely want to run some configuration management tool on your concrete jails, at least if you have more than a handful of them to manage. If that is the case, it begs the question: why use templates at all? All of the universal settings can be handled by the configuration management tool, a template would be unnecessary at that point.
Ansible actually has support for iocage connections
(which is based on the jail connection).
This is as far as I can see not documented at all,
but I have tested it and it works perfectly fine.
It does not support
become, but that is sort of an anti pattern anyway,
no big deal.
In order to use it, all you do is set
For example in your
group_vars for a group called
For this use case, lets create jails like this instead.
$ sudo iocage create tag=www -r 11.0-RELEASE
This creates the jail directly from a release,
instead of creating it from a template.
There is no need to create a template at all,
all the universal settings can be moved into ansible tasks instead,
www can be added as a host in the ansible inventory.
The iocage connection in ansible connects to the jail via the
so there is not even a need to add an IP address to it.
However, the connection works by using
which means that ansible must be run on the host machine
where the jails exist,
and it must be run as root.