206 Create DHCP and fixed IP jails

This example extends 203 Create DHCP jails with auto UUID and iocage_tags.

Use case

In the inventory vbotka.freebsd.iocage2 configuration file, use the option hooks_results to get the DHCP IP address. This option is common for all jails in this example:

hooks_results:
  - /var/db/dhclient-hook.address.epair0b

It will silently fail in jails with fixed IP addresses. If the item fails, the result is the dash character ‘-‘:

iocage_hooks:
  - '-'

This use case demonstrates the advantage of silently ignoring failed items over potential explicit error handling. Let the option compose pick what is needed:

compose:
  ansible_host: (iocage_hooks.0 == '-') | ternary(iocage_ip4, iocage_hooks.0)

Fixed IP

One jail with a fixed IP is created from the template ansible-client in this example:

clones:
  test-161:
    clone_from: ansible-client
    properties:
      bpf: 1
      vnet: 1
      ip4_addr: "vnet0|172.16.99.201/24"
      defaultrouter: '172.16.99.1'
      notes: "swarm=sw_01 alias=test_161"

Automatically generated UUID

Two DHCP jails with generated UUIDs are created from the template ansible-client:

swarms:
  sw_01:
    count: 3
    template: ansible-client
    properties:
      bpf: 1
      dhcp: 1
      vnet: 1

Note

The clone test-161 belongs to the swarm sw_01. Set count: 3 to create two more jails in the swarm sw_01.

The module vbotka.freebsd.iocage does not work with multiple names. Use ansible.builtin.command instead. Such a task is not idempotent anyway if the UUID is generated automatically. Example commands:

shell> iocage create --short --template ansible-client --count 2 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_06 swarm=sw_01"
shell> iocage start cd31c2a2 d254f889

The variable iocage_tags

The inventory plugin composes the variable iocage_tags:

iocage_tags: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)'))

For example:

iocage_tags:
  vmm: iocage_06
  swarm: sw_01

This dictionary is used to create groups:

keyed_groups:
  - prefix: swarm
    key: iocage_tags.swarm
  - prefix: vmm
    key: iocage_tags.vmm

Tree

shell> tree .
.
├── ansible.cfg
├── hosts
│   └── 06_iocage2.yml
├── host_vars
│   └── iocage_06
│       └── iocage.yml
├── iocage.ini
└── pb-test.yml

Synopsis

  • On a managed node:

    In the playbook pb_iocage_ansible_clients, use:

    • Module vbotka.freebsd.iocage to:

      • Create one jail with a fixed IP.

      • Start the jail.

    • Module ansible.builtin.command to:

      • Create two DHCP jails with generated UUIDs.

      • Start the jails.

  • On all created jails:

    In the playbook pb-test.yml:

    • Connect to the created jails.

    • Display basic configuration of the jails.

Requirements

Notes

See also

Templates

[iocage_06]# iocage list -lt
+------+------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| JID  |       NAME       | BOOT | STATE |   TYPE   |     RELEASE     |        IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+==================+======+=======+==========+=================+====================+=====+==========+==========+
| None | ansible-apache   | off  | down  | template | 15.1-RELEASE-p1 | DHCP (not running) | -   | -        | no       |
+------+------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible-client   | off  | down  | template | 15.1-RELEASE-p1 | DHCP (not running) | -   | -        | no       |
+------+------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible-init     | off  | down  | template | 15.1-RELEASE-p1 | DHCP (not running) | -   | -        | no       |
+------+------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible-nginx    | off  | down  | template | 15.1-RELEASE-p1 | DHCP (not running) | -   | -        | no       |
+------+------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible-pkg-repo | off  | down  | template | 15.1-RELEASE-p1 | DHCP (not running) | -   | -        | no       |
+------+------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible-repos    | off  | down  | template | 15.1-RELEASE-p1 | DHCP (not running) | -   | -        | no       |
+------+------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+

ansible.cfg

[defaults]
callback_result_format = yaml
deprecation_warnings = False
display_skipped_hosts = False
gathering = explicit
interpreter_python = auto_silent
log_path = /var/log/ansible.log

[connection]
pipelining = true

Inventory iocage.ini

iocage_02 ansible_host=10.1.0.73
iocage_04 ansible_host=10.1.0.29
iocage_06

[iocage]
iocage_06

[iocage:vars]
ansible_user=admin
ansible_become=true
ansible_python_interpreter=auto_silent

hosts

hosts/06_iocage2.yml
plugin: vbotka.freebsd.iocage2
host: iocage_06
user: admin
sudo: true
get_properties: true
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
inventory_hostname_tag: alias

compose:
  iocage_tags: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)'))
  iocage_classes: iocage_properties.notes | regex_findall('(?<=class=)[\w\-]+|(?<=,)[\w\-]+')
# connection plugin vbotka.freebsd.jailexec
  ansible_connection: "'vbotka.freebsd.jailexec'"
  ansible_ssh_user: "'admin'"
  ansible_jail_host: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)')).vmm | d('none')
  ansible_jail_name: iocage_jid
  ansible_jail_privilege_escalation: "'sudo'"
# ansible options
  ansible_python_interpreter: "'auto_silent'"

keyed_groups:
  - prefix: state
    key: iocage_state
  - prefix: vmm
    key: iocage_tags.vmm
  - prefix: swarm
    key: iocage_tags.swarm

Note

The option get_properties: true is needed to compose the dictionary iocage_tags.

host_vars

host_vars/iocage_06/iocage.yml
properties:
  notes: "vmm={{ inventory_hostname }}"

clones:
  test-161:
    clone_from: ansible-client
    properties:
      bpf: 1
      vnet: 1
      ip4_addr: 'vnet0|172.16.99.201/24'
      defaultrouter: '172.16.99.1'
      notes: 'swarm=sw_01 alias=test_161'

swarms:
  sw_01:
    count: 3
    template: ansible-client
    properties:
      bpf: 1
      dhcp: 1
      vnet: 1

start: [test-161]

Create and start clones

(env) > ansible-playbook -i iocage.ini \
                         -t clone -e clone=true \
                         vbotka.freebsd.pb_iocage_ansible_clients.yml
PLAY [Create and start jails. Optionally stop and destroy jails.] **************

TASK [Create clones from template] *********************************************
changed: [iocage_06] => (item=test-161 ansible-client)

TASK [Set iocage_jails] ********************************************************
ok: [iocage_06]

TASK [Start clones] ************************************************************
ok: [iocage_06]

PLAY RECAP *********************************************************************
iocage_06                  : ok=3    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

Create and start swarms

(env) > ansible-playbook -i iocage.ini \
                         -t swarm -e swarm=true -e debug=true \
                         vbotka.freebsd.pb_iocage_ansible_clients.yml
PLAY [Create and start jails. Optionally stop and destroy jails.] **************

TASK [Get iocage facts] ********************************************************
ok: [iocage_06]

TASK [Reject jails with empty notes] *******************************************
ok: [iocage_06]

TASK [Debug cmd_create debug=true] *********************************************
ok: [iocage_06] => (item=sw_01) => 
    msg: |-
        iocage create --short --template ansible-client --count 2  bpf=1  dhcp=1  vnet=1  notes="vmm=iocage_06 swarm=sw_01"

TASK [Create swarms] ***********************************************************
ok: [iocage_06] => (item=sw_01)

TASK [Debug create swarms debug=true] ******************************************
ok: [iocage_06] => 
    out:
        changed: false
        failed: false
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: false
            changed_when_result: false
            cmd:
            - iocage
            - create
            - --short
            - --template
            - ansible-client
            - --count
            - '2'
            - bpf=1
            - dhcp=1
            - vnet=1
            - notes=vmm=iocage_06 swarm=sw_01
            delta: '0:00:01.121444'
            end: '2026-09-15 05:34:00.787220'
            failed: false
            item:
                key: sw_01
                value:
                    count: 3
                    properties:
                        bpf: 1
                        dhcp: 1
                        vnet: 1
                    template: ansible-client
            msg: ''
            rc: 0
            start: '2026-09-15 05:33:59.665776'
            stderr: ''
            stderr_lines: []
            stdout: |-
                283d850f successfully created!
                df0394cb successfully created!
            stdout_lines:
            - 283d850f successfully created!
            - df0394cb successfully created!

TASK [Get iocage facts] ********************************************************
ok: [iocage_06]

TASK [Reject jails with empty notes] *******************************************
ok: [iocage_06]

TASK [Debug cmd_start debug=true] **********************************************
ok: [iocage_06] => (item=sw_01) => 
    msg: |-
        iocage start df0394cb 283d850f

TASK [Start swarms] ************************************************************
ok: [iocage_06] => (item=sw_01)

TASK [Debug start swarms debug=true] *******************************************
ok: [iocage_06] => 
    out:
        changed: false
        failed: false
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: false
            changed_when_result: false
            cmd:
            - iocage
            - start
            - df0394cb
            - 283d850f
            delta: '0:00:07.336939'
            end: '2026-09-15 05:34:22.384399'
            failed: false
            item:
                key: sw_01
                value:
                    count: 3
                    properties:
                        bpf: 1
                        dhcp: 1
                        vnet: 1
                    template: ansible-client
            msg: ''
            rc: 0
            start: '2026-09-15 05:34:15.047460'
            stderr: |-
                No default gateway found for ipv6.
                No default gateway found for ipv6.
            stderr_lines:
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            stdout: |-
                * Starting df0394cb
                  + Started OK
                  + Using devfs_ruleset: 1005 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 172.16.99.182/24
                * Starting 283d850f
                  + Started OK
                  + Using devfs_ruleset: 1006 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 172.16.99.183/24
            stdout_lines:
            - '* Starting df0394cb'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1005 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 172.16.99.182/24'
            - '* Starting 283d850f'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1006 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 172.16.99.183/24'

PLAY RECAP *********************************************************************
iocage_06                  : ok=10   changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

Graph

(env) > ansible-inventory -i hosts --graph
@all:
  |--@ungrouped:
  |--@state_up:
  |  |--df0394cb
  |  |--repos
  |  |--283d850f
  |  |--test_161
  |  |--repos_devel
  |  |--pkg_repo
  |  |--log_server_01
  |--@vmm_iocage_06:
  |  |--df0394cb
  |  |--repos
  |  |--283d850f
  |  |--test_161
  |  |--repos_devel
  |  |--pkg_repo
  |  |--log_server_01
  |--@swarm_sw_01:
  |  |--df0394cb
  |  |--283d850f
  |  |--test_161

Jails

[iocage_06]# iocage list -l
+-----+---------------+------+-------+------+-----------------+------------------------+-----+------------------+----------+
| JID |     NAME      | BOOT | STATE | TYPE |     RELEASE     |          IP4           | IP6 |     TEMPLATE     | BASEJAIL |
+=====+===============+======+=======+======+=================+========================+=====+==================+==========+
| 10  | 283d850f      | off  | up    | jail | 15.1-RELEASE-p1 | epair0b|172.16.99.183  | -   | ansible-client   | no       |
+-----+---------------+------+-------+------+-----------------+------------------------+-----+------------------+----------+
| 9   | df0394cb      | off  | up    | jail | 15.1-RELEASE-p1 | epair0b|172.16.99.182  | -   | ansible-client   | no       |
+-----+---------------+------+-------+------+-----------------+------------------------+-----+------------------+----------+
| 1   | log-server-01 | on   | up    | jail | 15.1-RELEASE-p1 | vnet0|172.16.99.10/24  | -   | ansible-init     | no       |
+-----+---------------+------+-------+------+-----------------+------------------------+-----+------------------+----------+
| 2   | pkg-repo      | on   | up    | jail | 15.1-RELEASE-p1 | vnet0|172.16.99.23/24  | -   | ansible-pkg-repo | no       |
+-----+---------------+------+-------+------+-----------------+------------------------+-----+------------------+----------+
| 3   | repos         | on   | up    | jail | 15.1-RELEASE-p1 | vnet0|172.16.99.21/24  | -   | ansible-repos    | no       |
+-----+---------------+------+-------+------+-----------------+------------------------+-----+------------------+----------+
| 4   | repos-devel   | on   | up    | jail | 15.1-RELEASE-p1 | vnet0|172.16.99.22/24  | -   | ansible-repos    | no       |
+-----+---------------+------+-------+------+-----------------+------------------------+-----+------------------+----------+
| 8   | test-161      | off  | up    | jail | 15.1-RELEASE-p1 | vnet0|172.16.99.201/24 | -   | ansible-client   | no       |
+-----+---------------+------+-------+------+-----------------+------------------------+-----+------------------+----------+

Playbook pb-test.yml

- name: Display jails in the swarm.
  hosts: swarm_sw_01
    
  tasks:

    - ansible.builtin.command: hostname
      register: out

    - ansible.builtin.debug:
        msg: |
          out.stdout: {{ out.stdout }}
          ansible_host: {{ ansible_host }}
          iocage_hooks: {{ iocage_hooks }}
          iocage_ip4: {{ iocage_ip4 }}
          iocage_tags:
            {{ iocage_tags | to_nice_yaml(2) | indent(2) }}

Playbook output - Display jails in the swarm

(env) > ansible-playbook -i hosts pb-test.yml
PLAY [swarm_sw_01] *************************************************************

TASK [ansible.builtin.command] *************************************************
changed: [df0394cb]
changed: [test_161]
changed: [283d850f]

TASK [ansible.builtin.debug] ***************************************************
ok: [test_161] => 
    msg: |-
        out.stdout: test-161
        ansible_host: test_161
        iocage_hooks: ['-']
        iocage_ip4: 172.16.99.201
        iocage_tags:
          alias: test_161
          swarm: sw_01
          vmm: iocage_06
ok: [283d850f] => 
    msg: |-
        out.stdout: 283d850f
        ansible_host: 283d850f
        iocage_hooks: ['172.16.99.183']
        iocage_ip4: 172.16.99.183
        iocage_tags:
          swarm: sw_01
          vmm: iocage_06
ok: [df0394cb] => 
    msg: |-
        out.stdout: df0394cb
        ansible_host: df0394cb
        iocage_hooks: ['172.16.99.182']
        iocage_ip4: 172.16.99.182
        iocage_tags:
          swarm: sw_01
          vmm: iocage_06

PLAY RECAP *********************************************************************
283d850f                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
df0394cb                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
test_161                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Playbook output - Destroy swarms

Destroy the swarms if you do not need them anymore.

(env) > ansible-playbook -i iocage.ini \
                         -t swarm_destroy -e swarm_destroy=true \
                         vbotka.freebsd.pb_iocage_ansible_clients.yml
PLAY [Create and start jails. Optionally stop and destroy jails.] **************

TASK [Get iocage facts] ********************************************************
ok: [iocage_06]

TASK [Stop swarms] *************************************************************
ok: [iocage_06] => (item=sw_01)

TASK [Destroy swarms] **********************************************************
ok: [iocage_06] => (item=sw_01)

PLAY RECAP *********************************************************************
iocage_06                  : ok=3    changed=0    unreachable=0    failed=0    skipped=5    rescued=0    ignored=0