441 Redirect SHH to jails
Use case
Create multiple jails with auto UUID names. In the inventory, compose the variables
ansible_host and ansible_port to connect the jails via the redirected SSH
ports. See the example 440 Configure DHCP and pf how pf is configured.
Tree
shell> tree .
.
├── ansible.cfg
├── hosts
│ ├── 05_iocage.yml
│ └── 99_constructed.yml
├── host_vars
│ └── iocage_05.yml
├── iocage.ini
└── pb-test.yml
Synopsis
At a managed node:
In the playbook vbotka.freebsd.pb_iocage_ansible_clients.yml:
create jails
start jails
optionally, stop and destroy the jails.
Create dynamic inventory to redirect SSH to the jails.
At all created jails:
In the playbook
pb-test.yml:connect to the created jails
display basic configuration of the jails.
Requirements
root privilege in the managed nodes
template
ansible_clientcreated in 202 Create iocage templates. Clone DHCP jails.
Notes
The only difference between this example and the example 442 Connection jailexec instead of ssh are the
following two lines in the file hosts/05_iocage.yml
ansible_host: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)')).vmm
ansible_port: iocage_ip4 | split('.') | last | int - 100 + 2200
See also
example 050 Connection jailexec
Templates at iocage_05
[iocage_05]# iocage list -lt
+------+-----------------------+------+-------+----------+--------------+--------------------+-----+----------+----------+
| JID | NAME | BOOT | STATE | TYPE | RELEASE | IP4 | IP6 | TEMPLATE | BASEJAIL |
+======+=======================+======+=======+==========+==============+====================+=====+==========+==========+
| None | ansible_client | off | down | template | 15.0-RELEASE | DHCP (not running) | - | - | no |
+------+-----------------------+------+-------+----------+--------------+--------------------+-----+----------+----------+
| None | ansible_client_apache | off | down | template | 15.0-RELEASE | DHCP (not running) | - | - | no |
+------+-----------------------+------+-------+----------+--------------+--------------------+-----+----------+----------+
| None | ansible_client_pull | off | down | template | 15.0-RELEASE | DHCP (not running) | - | - | no |
+------+-----------------------+------+-------+----------+--------------+--------------------+-----+----------+----------+
ansible.cfg
[defaults]
callback_result_format = yaml
deprecation_warnings = false
display_skipped_hosts = false
gathering = explicit
[connection]
pipelining = true
Inventory iocage.ini
iocage_05
[iocage]
iocage_05
[iocage:vars]
ansible_user=admin
ansible_become=true
ansible_python_interpreter=auto_silent
host_vars
properties:
bpf: 1
dhcp: 1
vnet: 1
notes: "vmm={{ inventory_hostname }}"
defaultrouter: 10.10.99.1
swarms:
sw_01:
count: 3
template: ansible_client
Hint
If the default iocage option defaultrouter=auto doesn’t work set it. This may be
needed if the jails are provided with the DHCP on the bridge. In this case, the
defaultrouter for the jails is the IP address of the bridge. pf must provide NAT and
redirection. See example 440 Configure DHCP and pf.
Playbook output - Create and start swarms
(env) > ansible-playbook vbotka.freebsd.pb_iocage_ansible_clients.yml \
-i iocage.ini \
-t swarm \
-e swarm=true \
-e debug=true
PLAY [Create and start jails. Optionally stop and destroy jails.] **************
TASK [Get iocage facts] ********************************************************
ok: [iocage_05]
TASK [Reject jails with empty notes] *******************************************
ok: [iocage_05]
TASK [Debug cmd_create debug=true] *********************************************
ok: [iocage_05] => (item=sw_01) =>
msg: |-
iocage create --short --template ansible_client --count 3 bpf=1 defaultrouter=10.10.99.1 dhcp=1 notes="vmm=iocage_05 swarm=sw_01" vnet=1
TASK [Create swarms] ***********************************************************
ok: [iocage_05] => (item=sw_01)
TASK [Debug create swarms debug=true] ******************************************
ok: [iocage_05] =>
out:
changed: false
msg: All items completed
results:
- ansible_loop_var: item
changed: false
cmd:
- iocage
- create
- --short
- --template
- ansible_client
- --count
- '3'
- bpf=1
- defaultrouter=10.10.99.1
- dhcp=1
- notes=vmm=iocage_05 swarm=sw_01
- vnet=1
delta: '0:00:00.830750'
end: '2026-05-13 11:04:13.243889'
failed: false
invocation:
module_args:
_raw_params: null
_uses_shell: false
argv: null
chdir: null
cmd: |-
iocage create --short --template ansible_client --count 3 bpf=1 defaultrouter=10.10.99.1 dhcp=1 notes="vmm=iocage_05 swarm=sw_01" vnet=1
creates: null
executable: null
expand_argument_vars: true
removes: null
stdin: null
stdin_add_newline: true
strip_empty_ends: true
item:
key: sw_01
value:
count: 3
template: ansible_client
msg: ''
rc: 0
start: '2026-05-13 11:04:12.413139'
stderr: ''
stderr_lines: []
stdout: |-
cbe505b1 successfully created!
f4073d63 successfully created!
4a8426e6 successfully created!
stdout_lines:
- cbe505b1 successfully created!
- f4073d63 successfully created!
- 4a8426e6 successfully created!
skipped: false
TASK [Get iocage facts] ********************************************************
ok: [iocage_05]
TASK [Reject jails with empty notes] *******************************************
ok: [iocage_05]
TASK [Debug cmd_start debug=true] **********************************************
ok: [iocage_05] => (item=sw_01) =>
msg: |-
iocage start 4a8426e6 cbe505b1 f4073d63
TASK [Start swarms] ************************************************************
ok: [iocage_05] => (item=sw_01)
TASK [Debug start swarms debug=true] *******************************************
ok: [iocage_05] =>
out:
changed: false
msg: All items completed
results:
- ansible_loop_var: item
changed: false
cmd:
- iocage
- start
- 4a8426e6
- cbe505b1
- f4073d63
delta: '0:00:08.396317'
end: '2026-05-13 11:04:27.719685'
failed: false
invocation:
module_args:
_raw_params: null
_uses_shell: false
argv: null
chdir: null
cmd: |-
iocage start 4a8426e6 cbe505b1 f4073d63
creates: null
executable: null
expand_argument_vars: true
removes: null
stdin: null
stdin_add_newline: true
strip_empty_ends: true
item:
key: sw_01
value:
count: 3
template: ansible_client
msg: ''
rc: 0
start: '2026-05-13 11:04:19.323368'
stderr: |-
No default gateway found for ipv6.
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.
- No default gateway found for ipv6.
stdout: |-
* Starting 4a8426e6
+ Started OK
+ Using devfs_ruleset: 1000 (iocage generated default)
+ Configuring VNET OK
+ Using IP options: vnet
+ Starting services OK
+ Executing poststart OK
+ DHCP Address: 10.10.99.130/24
* Starting cbe505b1
+ Started OK
+ Using devfs_ruleset: 1001 (iocage generated default)
+ Configuring VNET OK
+ Using IP options: vnet
+ Starting services OK
+ Executing poststart OK
+ DHCP Address: 10.10.99.161/24
* Starting f4073d63
+ Started OK
+ Using devfs_ruleset: 1002 (iocage generated default)
+ Configuring VNET OK
+ Using IP options: vnet
+ Starting services OK
+ Executing poststart OK
+ DHCP Address: 10.10.99.162/24
stdout_lines:
- '* Starting 4a8426e6'
- ' + Started OK'
- ' + Using devfs_ruleset: 1000 (iocage generated default)'
- ' + Configuring VNET OK'
- ' + Using IP options: vnet'
- ' + Starting services OK'
- ' + Executing poststart OK'
- ' + DHCP Address: 10.10.99.130/24'
- '* Starting cbe505b1'
- ' + Started OK'
- ' + Using devfs_ruleset: 1001 (iocage generated default)'
- ' + Configuring VNET OK'
- ' + Using IP options: vnet'
- ' + Starting services OK'
- ' + Executing poststart OK'
- ' + DHCP Address: 10.10.99.161/24'
- '* Starting f4073d63'
- ' + Started OK'
- ' + Using devfs_ruleset: 1002 (iocage generated default)'
- ' + Configuring VNET OK'
- ' + Using IP options: vnet'
- ' + Starting services OK'
- ' + Executing poststart OK'
- ' + DHCP Address: 10.10.99.162/24'
skipped: false
PLAY RECAP *********************************************************************
iocage_05 : ok=10 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
Jails at iocage_05
[iocage_05]# iocage list -l
+-----+----------+------+-------+------+--------------+----------------------+-----+----------------+----------+
| JID | NAME | BOOT | STATE | TYPE | RELEASE | IP4 | IP6 | TEMPLATE | BASEJAIL |
+=====+==========+======+=======+======+==============+======================+=====+================+==========+
| 31 | 4a8426e6 | off | up | jail | 15.0-RELEASE | epair0b|10.10.99.130 | - | ansible_client | no |
+-----+----------+------+-------+------+--------------+----------------------+-----+----------------+----------+
| 32 | cbe505b1 | off | up | jail | 15.0-RELEASE | epair0b|10.10.99.161 | - | ansible_client | no |
+-----+----------+------+-------+------+--------------+----------------------+-----+----------------+----------+
| 33 | f4073d63 | off | up | jail | 15.0-RELEASE | epair0b|10.10.99.162 | - | ansible_client | no |
+-----+----------+------+-------+------+--------------+----------------------+-----+----------------+----------+
Inventory hosts
plugin: vbotka.freebsd.iocage
host: iocage_05
user: admin
sudo: true
get_properties: true
hooks_results:
- /var/db/dhclient-hook.address.epair0b
compose:
ansible_host: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)')).vmm | d('none')
ansible_port: iocage_ip4 | split('.') | last | int - 100 + 2200
ansible_user: "'admin'"
ansible_python_interpreter: "'auto_silent'"
iocage_tags: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)'))
Note
In the example 440 Configure DHCP and pf, the variables
ssh_rdr_start=2200anddhcp_ip_start=100are used in the playbookpb-pf-setup.ymlto calculate the ports to redirect SSH from, and to create the filepf-rdr-ssh.conf.For example, from the controller, the following command connects to the jail at <bsd_dhcpd_subnet>.106:
shell> ssh -p 2206 admin@iocage_05
See the variable
bsd_dhcpd_subnetin the example 440 Configure DHCP and pf
plugin: ansible.builtin.constructed
keyed_groups:
- prefix: swarm
key: iocage_tags.swarm
- prefix: vmm
key: iocage_tags.vmm
Display inventory
(env) > ansible-inventory -i hosts --graph
@all:
|--@ungrouped:
|--@swarm_sw_01:
| |--4a8426e6
| |--cbe505b1
| |--f4073d63
|--@vmm_iocage_05:
| |--4a8426e6
| |--cbe505b1
| |--f4073d63
Playbook pb-test.yml
- name: Test the SSH redirection to the jails.
hosts: swarm_sw_01
gather_facts: false
tasks:
- ansible.builtin.debug:
msg: |
ansible_connection: {{ ansible_connection }}
ansible_host: {{ ansible_host }}
ansible_port: {{ ansible_port }}
ansible_user: {{ ansible_user }}
iocage_ip4: {{ iocage_ip4 }}
iocage_tags: {{ iocage_tags }}
Playbook output - Test SSH redirection
(env) > ansible-playbook pb-test.yml -i hosts
PLAY [Test the SSH redirection to the jails.] **********************************
TASK [ansible.builtin.debug] ***************************************************
ok: [4a8426e6] =>
msg: |-
ansible_connection: ssh
ansible_host: iocage_05
ansible_port: 2230
ansible_user: admin
iocage_ip4: 10.10.99.130
iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}
ok: [cbe505b1] =>
msg: |-
ansible_connection: ssh
ansible_host: iocage_05
ansible_port: 2261
ansible_user: admin
iocage_ip4: 10.10.99.161
iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}
ok: [f4073d63] =>
msg: |-
ansible_connection: ssh
ansible_host: iocage_05
ansible_port: 2262
ansible_user: admin
iocage_ip4: 10.10.99.162
iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}
PLAY RECAP *********************************************************************
4a8426e6 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
cbe505b1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
f4073d63 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Hint
The below play stops and destroys the jails in swarms
ansible-playbook vbotka.freebsd.pb_iocage_ansible_clients.yml \
-i iocage.ini \
-t swarm_destroy \
-e swarm_destroy=true