[freebsd banner]

Management of FreeBSD jails through Ansible

!! Work in progress !! please come regularly for updates

Table of contents

  1. Management of FreeBSD jails through Ansible
  2. Table of contents
  3. TL; DR.
  4. Prerequisites
  5. Without the jail connector
  6. Enter the jail connector
  7. Hmm does a jail has a name?
  8. Next steps
  9. Local vs remote
  10. BSDPloy?
  11. Credits


Ansible is Configuration Management software, similar in spirit to Chef and Puppet but way more lighter in requirements (no CouchDB like Chef) and in scalability (no agent running, ssh connection and Python installed).

I have become interested in Ansible for a few months now and I just recently found a way to deal with jails on FreeBSD, my operating system of choice for close to 20 years now.


Version of Ansible should not really matter, anything from at least 1.4 has the jail connector we will be using there. As of this writing, it is 1.7.0. I do not think the version of FreeBSD should not matter much either, I have been playing with 8.2 and 9.2 mostly and planning to go to 10 soonish.

Reading Ansible documentation will help of course if you are not familiar with it although the jail connector is really documented unfortunately, I found help on Twitter.

Knowledge of jails on FreeBSD is of course necessary.

Without the jail connector

Say you have several jails configured on your system, either manually or (better) through ezjail. I use ezjail because it makes thing much easier to handle and save space by having a basejail part shared between all jails on the system. Unfortunately, that also means that you can’t mix OS versions between jails (but it does not mater here).

jls might give you something like this:

1273 [16:57] root@centre:local/etc# jls
   JID  IP Address      Hostname                      Path
     4       www.keltia.net                /jails/www
  1233       mail.keltia.net               /jails/mail.keltia.net
  1251       shell.keltia.net              /jails/shell.keltia.net

Note that the JID parameter will change everytime you start a jail so you can not rely on that for your Ansible host inventory so you need way to get the output of jls and give it to Ansible (either statically or through its dynamic inventory feature (which Ihaven’t tested yet).

After you got that list written in LOCALBASE/etc/ansible/hosts (the default), you could expect to issue a series of jexec(8) commands to do things in the jails but as you can see, it is rather limited and you would need to quote all the special characters (for the shell) yadda yadda yadda.

Not particularly fun.

Enter the jail connector

With very (and I mean it) little documentation, if you happen to look into the Ansible sources on GitHub you will notice the connection_plugins directory with some interesting names such as chroot and jail. Now, how to use that?

As I said, I finally asked several times on twitter and IRC (there is a #ansible channel on Freenode) and a few days ago, I got an answer from Julien Dauphant aka @jdauphant leading to the solution.

One need to use the ansible_connection=jail parameter in the hosts file with the first parameter being the name of the jail. Then you can refer to each jail by name as target for an Ansible command or playbook.

Hmm does a jail has a name?

Well yes. You can give a name to each jail, either through /etc/jail.conf (if you are on 9.2+) or the series of rc.conf(5) variables (before 9.2) but, interestingly enough ezjail does NOT give one (maybe I missed it - anyone?).

It is easy to correct that though:

jail -m jid=1251 name=shell_keltia_net

and tadaa! you can now refer JID 1251 as shell_keltia_net in your hosts file. You will need to do it once for each jail and fix the ezjail.conf file.

Next steps

Now that every jail has a name, I can fill in my hosts file:


www_keltia_net ansible_connection=jail

mail_keltia_net ansible_connection=jail

shell_keltia_net ansible_connection=jail

If I want to ping (the Ansible way, not the `ping(8) way) all jails, I just need to use

ansible jails -m ping

result will be similar to the following:

www_keltia_net | success >> {
    "changed": false,
    "ping": "pong"

mail_keltia_net | success >> {
    "changed": false,
    "ping": "pong"

shell_keltia_net | success >> {
    "changed": false,
    "ping": "pong"

Now, I can also use Ansible to upgrade all jails after my poudriere run has finished with:

ansible jails -m shell -a 'pkg upgrade -y'

and so on…

More to come.

Local vs remote

Ansible is great to manage several machines in parallel and run things from remote places but there is something specific with the connection plugins: as you are telling Ansible to use something other than the default (ssh-based), you can not use the jail connector from a remote machine. Which means that the playbooks have to be on the same machine as the jails.

There might be a way to workaround that with using the delegate_to feature inside playbooks but I have not explored that way yet. You could of course use a playbook on your remote machine that runs another playbook (or just commands) on the jail host.


A few weeks ago I also became aware of BSDPloy, an ezjail/Ansible-based package created to automate whole hosts (and jails) provisioning. I shall explore it further.


Thanks to Julien Dauphant!