How to Manage FreeBSD Jails with iocage
A current iocage workflow for selecting a supported FreeBSD release, creating a ZFS-backed jail, managing networking, updates, snapshots, and removal.
FreeBSD jails share the host kernel while isolating processes, filesystems, identities, and selected network resources. iocage adds ZFS-backed release management, properties, snapshots, clones, and lifecycle commands. It does not turn a jail into a virtual machine, and it does not remove the need to understand the host’s network, ZFS layout, or jail security model.
This procedure uses a shared-IP jail for a simple first deployment. VNET gives a jail its own network stack, but requires a deliberate bridge, firewall, routing, and kernel-support design. Do not combine fragments from both models without testing them from console access.
Verify the host and install the maintained package
iocage requires ZFS. Confirm the host, pools, free space, package repository, and current jail state first:
freebsd-version -kru
zpool list
zfs list
pkg search -o iocage
jls
The maintained FreeBSD port is sysutils/iocage; its package name has a Python flavor prefix that changes over time. Install by origin so an old py311-iocage string is not copied forever:
pkg install sysutils/iocage
iocage --version
Review pkg audit -F and the package plan before accepting it. Migrating from legacy iocage is a separate workflow that requires stopped jails and backups; do not install one implementation over another and hope that existing datasets will be converted automatically.
Select and activate the ZFS pool
List pools and choose intentionally. Activation marks the pool whose dataset will hold releases, jails, templates, and snapshots:
zpool list
iocage activate zroot
iocage get -p
Replace zroot with the actual pool. On many systems the primary pool is selected automatically, but checking avoids creating a large jail tree on the wrong storage. Inspect the resulting datasets and confirm that backup, quota, encryption, and replication policies cover the new location.
Do not assume snapshots are backups. They remain in the same pool and fail with it. Replicate important jail configuration and application data to independent storage.
Fetch a supported release, not a copied version
The original example used 14.0-RELEASE, which is end-of-life. Consult FreeBSD’s supported-release page and choose a release compatible with the host kernel. A jail normally uses the host kernel, so a newer jail userland can require interfaces the older host does not provide.
For a host that has been verified to support 15.1-RELEASE:
RELEASE=15.1-RELEASE
iocage fetch -r "$RELEASE"
iocage list -r
Substitute the release actually approved for the host. iocage fetch without -r provides an interactive selection. Preserve the fetch output and verify the release appears before creation. Avoid LATEST in reproducible automation because its meaning changes.
Create and inspect a shared-IP jail
Choose an unused address from the correct subnet and verify the real interface with ifconfig. The documentation’s interface names and example addresses are placeholders:
iocage create -n mywebserver -r "$RELEASE" \
ip4_addr="em0|192.0.2.50/24" boot=off
iocage get all mywebserver
iocage list -l
192.0.2.0/24 is documentation space; replace it. A duplicate production address can disrupt another system. Validate VLAN, prefix, default routing, DNS, and host firewall policy before starting the jail.
The shared-IP model uses the host network stack and restricts the jail to assigned addresses. VNET is not enabled by merely adding an epair name. If the service needs its own routing table or firewall, design a VNET jail from the iocage and FreeBSD jail documentation and test bridge filtering carefully.
Set only properties you understand. Broad permissions such as raw sockets, mounting filesystems, or host-level administrative capabilities expand the jail’s attack surface. Review effective properties after every change:
iocage get ip4_addr mywebserver
iocage get vnet mywebserver
iocage get allow_raw_sockets mywebserver
Start, enter, and configure the jail
Start it, verify state, and run a noninteractive identity check before opening a console:
iocage start mywebserver
iocage list -l
iocage exec mywebserver freebsd-version -u
iocage exec mywebserver ifconfig
iocage console mywebserver
Inside the jail, bootstrap pkg only from the configured trusted repository, update the catalogue, audit packages, and install the service. For example, the host can invoke:
iocage exec mywebserver pkg update
iocage exec mywebserver pkg audit -F
iocage exec mywebserver pkg install nginx
Enable services inside the jail with its own rc.conf. Do not expose SSH merely for convenience when iocage console or iocage exec satisfies administration needs. If remote access is required, configure authentication, firewalling, logging, and patching as a real security boundary.
Once startup and application checks pass, enable boot persistence explicitly:
iocage set boot=on mywebserver
iocage get boot mywebserver
Reboot testing during a maintenance window is the only proof that host ordering, networking, storage, and the jail service all recover together.
Snapshot, update, and roll back deliberately
Name a snapshot so its purpose is evident:
iocage snapshot -n pre-nginx-upgrade mywebserver
iocage snaplist mywebserver
A filesystem snapshot does not make an application-consistent backup by itself. Quiesce databases or use their documented backup mechanism before snapshotting. Also remember that rolling back discards later filesystem changes.
iocage update mywebserver applies patch-level updates and creates a safety snapshot according to iocage’s documentation. A release upgrade is different and requires an explicit target:
iocage update mywebserver
iocage upgrade mywebserver -r 15.1-RELEASE
Do not copy that target blindly; verify host compatibility and the supported-release status on the day of the change. Test package ABI, application startup, DNS, and real client traffic afterward. Keep the rollback snapshot until acceptance is complete, then remove obsolete snapshots intentionally so changed blocks do not consume the pool unnoticed.
Cloning is useful for a rehearsal:
iocage clone mywebserver@pre-nginx-upgrade --name mywebserver-test
iocage list -l
Clones depend on their origin snapshot until promoted. Track that dependency before destroying a source or pruning snapshots.
Stop and destroy without losing data
Before removal, inventory application data, delegated datasets, snapshots, clones, exports, DNS, firewall rules, and monitoring. Stop and confirm no clients still depend on the jail:
iocage stop mywebserver
iocage list -l
iocage snaplist mywebserver
iocage destroy mywebserver is destructive. Run it only after an independently restorable backup and a reviewed decommission record exist. Finally verify that datasets, addresses, routes, package-monitoring entries, and backup jobs are in the intended state.
iocage makes lifecycle operations convenient; it cannot decide release compatibility, application consistency, or acceptable privileges. Those remain the administrator’s responsibility.
Related:
- FreeBSD Jails: Lightweight OS-Level Virtualization Done Right
- Fixing FreeBSD Jail Networking When VNET Jails Can’t Reach the Network
Sources: