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.commandto: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
Playbook pb_iocage_ansible_clients
Root privileges on the managed nodes
Templates created in 202 Create iocage templates. Clone DHCP jails.
Notes
Templates created in 202 Create iocage templates. Clone DHCP jails. are used in this example.
The dash ‘-’ is used in binary iocage to represent a missing value. See, for example:
The inventory vbotka.freebsd.iocage2 uses it too:
if iocage_ip4_dict['ip4']: iocage_ip4 = ','.join([d['ip'] for d in iocage_ip4_dict['ip4']]) else: iocage_ip4 = '-'
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
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
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