Bastille vs iocage: FreeBSD Jail Manager Comparison
FreeBSD jails are one of the most powerful isolation features in any operating system, but managing them by hand -- creating datasets, configuring networking, maintaining base systems, applying updates -- gets tedious fast. Jail managers automate this. Bastille and iocage are the two most popular options, and they take meaningfully different approaches to the same problem.
Bastille is the newer project, actively maintained, template-driven, and designed with a DevOps mindset. Iocage is older, feature-rich, and was for years the default recommendation. But iocage's development has slowed considerably, and the community has shifted toward Bastille.
This comparison covers both tools in depth so you can make an informed choice.
TL;DR -- Quick Verdict
Choose Bastille if you want active development, a template system for repeatable jail configurations, good documentation, a responsive maintainer, and a tool that is gaining community momentum. Bastille is the safer long-term bet.
Choose iocage if you need features that Bastille lacks (like VNET thin jails or some advanced ZFS snapshot workflows), you already have an iocage-managed infrastructure, or your workflow depends on iocage-specific behavior. But be aware that iocage's development pace has slowed significantly.
Project Status and Maintenance
This matters more than features. A jail manager that stops receiving updates eventually breaks when FreeBSD changes.
Bastille
Bastille is maintained by Christer Edwards and has an active contributor community. As of early 2026, Bastille receives regular commits, responds to issues promptly, and tracks FreeBSD releases. The project has clear documentation and a growing ecosystem of community templates.
Bastille is written in shell script (POSIX sh), which makes it easy to understand, debug, and contribute to. Every command is auditable -- you can read exactly what Bastille does when you run any operation.
Iocage
Iocage has a complicated history. The original iocage was a shell script by James O'Gorman. It was rewritten in Python (iocage / py-iocage) by Brandon Schneider. The Python version became the standard, but its development has slowed. Commits are infrequent, issues accumulate, and FreeBSD version support sometimes lags.
Iocage is not abandoned -- it still works and is in the FreeBSD ports tree. But the gap between Bastille's development velocity and iocage's is growing.
Installation
Bastille
shpkg install bastille sysrc bastille_enable="YES"
Bootstrap a FreeBSD release for jail use:
shbastille bootstrap 14.2-RELEASE update
This downloads the FreeBSD base system and stores it on ZFS. The update flag applies the latest security patches automatically.
Iocage
shpkg install py311-iocage sysrc iocage_enable="YES"
Activate iocage on a ZFS pool:
shiocage activate zroot iocage fetch --release 14.2-RELEASE
Both tools require ZFS. Neither works well without it -- ZFS clones and snapshots are fundamental to how both tools create and manage jails.
Creating Jails
Bastille
shbastille create webserver 14.2-RELEASE 10.0.0.10
This creates a thick jail named webserver running FreeBSD 14.2, with IP address 10.0.0.10 on the default interface. The jail starts automatically if bastille_enable is set.
For a VNET jail with its own network stack:
shbastille create -V webserver 14.2-RELEASE 10.0.0.10/24
Start and stop jails:
shbastille start webserver bastille stop webserver bastille restart webserver bastille list
Iocage
shiocage create -n webserver -r 14.2-RELEASE ip4_addr="em0|10.0.0.10/24"
For a VNET jail:
shiocage create -n webserver -r 14.2-RELEASE vnet=on \ ip4_addr="vnet0|10.0.0.10/24" \ defaultrouter=10.0.0.1
Start and stop:
shiocage start webserver iocage stop webserver iocage restart webserver iocage list
The syntax differs but the result is similar. Iocage's property-based configuration (key=value) is more verbose but also more explicit about what each setting does.
Templates and Automation
This is where Bastille has a clear advantage.
Bastille Templates
Bastille templates are declarative configuration files that define everything a jail needs. They look like Dockerfiles for jails:
shbastille create webserver 14.2-RELEASE 10.0.0.10 bastille template webserver cedwards/nginx
A Bastille template (Bastillefile) looks like this:
shellARG DOMAIN=example.com PKG nginx PKG openssl SYSRC nginx_enable=YES CP usr/local/etc/nginx/nginx.conf CMD nginx -t SERVICE nginx start FSTAB /usr/local/www/mysite usr/local/www/mysite nullfs rw 0 0
Templates can be stored in Git repositories and applied with a URL:
shbastille template webserver https://gitlab.com/bastillebsd-templates/nginx
The template system supports:
PKG-- Install packagesSYSRC-- Set rc.conf valuesCP-- Copy files into the jailCMD-- Run commands inside the jailSERVICE-- Start servicesFSTAB-- Mount host directories into the jailRDR-- Set up port redirections (with pf)ARG-- Template variablesINCLUDE-- Compose templates from other templatesLIMITS-- Set resource limits (rctl)CONFIG-- Apply sysctl settings
This is a complete infrastructure-as-code system for jails. You can version-control your templates, share them, and reproduce environments reliably.
Iocage Templates
Iocage has a template concept, but it works differently. An iocage template is a stopped jail that is cloned to create new jails:
sh# Create a jail and configure it iocage create -n nginx-template -r 14.2-RELEASE iocage exec nginx-template "pkg install -y nginx" # Convert to template iocage stop nginx-template iocage set template=yes nginx-template # Create jails from template iocage create -n web1 -t nginx-template ip4_addr="em0|10.0.0.11/24" iocage create -n web2 -t nginx-template ip4_addr="em0|10.0.0.12/24"
This is ZFS-clone-based templating -- fast and space-efficient (clones share blocks with the template). But it is not declarative. You cannot look at a file and know what the template contains. You cannot version-control it in Git. You cannot compose templates.
ZFS Integration
Both tools are built on ZFS, but they use it differently.
Bastille ZFS
Bastille stores jails under a configurable ZFS dataset. The default layout:
shellzroot/bastille/jails/webserver # Jail root zroot/bastille/releases/14.2-RELEASE # Base release zroot/bastille/templates # Downloaded templates zroot/bastille/backups # Exported jails
Bastille supports thick jails (full copy of the base system) and thin jails (nullfs mounts from the release). Thick jails use more disk but are independent of the release. Thin jails save disk but share the base system read-only.
Export and import jails:
shbastille export webserver # Creates /usr/local/bastille/backups/webserver-*.xz bastille import /path/to/webserver-*.xz
ZFS snapshots are used for exports but are not directly exposed as a jail management feature (you use zfs snapshot directly).
Iocage ZFS
Iocage has deeper ZFS integration. Its snapshot and clone management is a first-class feature:
sh# Snapshot a jail iocage snapshot webserver # List snapshots iocage snaplist webserver # Rollback to a snapshot iocage rollback webserver@ioc-2026-04-09 # Clone a jail from a snapshot iocage clone webserver@ioc-2026-04-09 -n webserver-test
Iocage also uses ZFS clones for its template system, making jail creation from templates nearly instantaneous.
The ZFS-native snapshot management is one area where iocage genuinely excels over Bastille.
Networking
Bastille Networking
Bastille supports both shared-IP and VNET networking.
Shared IP (aliases on host interface):
shbastille create web 14.2-RELEASE 10.0.0.10
VNET (dedicated virtual network stack per jail):
shbastille create -V web 14.2-RELEASE 10.0.0.10/24
Bastille integrates with pf for port redirection:
shbastille rdr web tcp 80 80 bastille rdr web tcp 443 443
This adds pf rdr rules automatically. The generated rules go into /usr/local/etc/bastille/pf.conf which you include from your main pf.conf.
Iocage Networking
Iocage also supports shared-IP and VNET:
sh# Shared IP iocage create -n web ip4_addr="em0|10.0.0.10/24" # VNET iocage create -n web vnet=on ip4_addr="vnet0|10.0.0.10/24" \ defaultrouter=10.0.0.1
VNET in iocage supports bridge configuration:
shiocage create -n web vnet=on \ ip4_addr="vnet0|10.0.0.10/24" \ vnet_default_interface=em0 \ defaultrouter=10.0.0.1
Both tools handle VNET competently. The configuration syntax differs but the underlying FreeBSD networking is identical.
Package Management Inside Jails
Bastille
shbastille pkg webserver install nginx postgresql16-server bastille pkg webserver upgrade bastille pkg ALL upgrade # Upgrade packages in all jails
The ALL target is powerful -- it applies operations to every jail simultaneously.
Iocage
shiocage exec webserver "pkg install -y nginx postgresql16-server" iocage exec webserver "pkg upgrade -y"
Iocage does not have a dedicated package subcommand -- you use exec to run pkg inside the jail. This works fine but is less ergonomic than Bastille's approach.
Resource Limits
Bastille
Bastille supports rctl resource limits:
shbastille limits webserver memoryuse:deny=2G bastille limits webserver maxproc:deny=200 bastille limits webserver cputime:devctl=3600
Limits can also be set in templates:
shellLIMITS memoryuse:deny=2G LIMITS maxproc:deny=200
Iocage
Iocage exposes resource limits as jail properties:
shiocage set memoryuse=2G:deny webserver iocage set maxproc=200:deny webserver
Both tools use the same underlying rctl framework. The interface differs but the result is identical.
Updating Jails
Bastille
Update the base release, then apply to all jails:
shbastille update 14.2-RELEASE bastille upgrade 14.2-RELEASE 14.3-RELEASE # Major version upgrade
For thin jails, updating the release updates all jails sharing it. For thick jails, each jail must be updated individually.
Iocage
shiocage update webserver iocage upgrade webserver -r 14.3-RELEASE
Iocage updates jails individually, which is more granular but slower when you have many jails.
Command Comparison
| Operation | Bastille | Iocage |
|---|---|---|
| Create jail | bastille create name rel ip | iocage create -n name -r rel ip4_addr=... |
| List jails | bastille list | iocage list |
| Start jail | bastille start name | iocage start name |
| Enter console | bastille console name | iocage console name |
| Run command | bastille cmd name command | iocage exec name command |
| Install package | bastille pkg name install pkg | iocage exec name "pkg install pkg" |
| Destroy jail | bastille destroy name | iocage destroy name |
| Export jail | bastille export name | iocage export name |
| Apply template | bastille template name tpl | Manual or clone-based |
| Port redirect | bastille rdr name tcp 80 80 | Manual pf rules |
| Resource limits | bastille limits name ... | iocage set ... name |
| Operate on all | bastille cmd ALL ... | No equivalent |
Community and Ecosystem
Bastille
- Active maintainer and community
- Growing template library (BastilleBSD-Templates on GitLab)
- Regular conference talks and documentation updates
- Shell-script codebase invites contributions
- Integration with Ansible via community roles
Iocage
- Slower development but still maintained
- Larger existing user base (legacy installations)
- Python codebase is harder for casual contributors
- TrueNAS CORE used iocage (but TrueNAS SCALE moved to Linux)
- More Stack Overflow answers and forum posts (historical)
Performance
Both tools have negligible management overhead. The jails themselves perform identically regardless of which tool manages them -- the jail is a kernel feature, not a property of the management tool.
The only measurable difference is in jail creation time:
| Operation | Bastille | Iocage |
|---|---|---|
| Create thick jail | ~30s | ~30s |
| Create thin jail | ~2s | ~3s |
| Create from template | N/A (apply template ~5-30s) | ~2s (ZFS clone) |
| Start jail | <1s | <1s |
| Destroy jail | <1s | <1s |
Iocage's ZFS-clone-based templating is faster for jail creation, but Bastille's template application is more flexible (it can modify an existing jail rather than only creating from scratch).
Migration Between Tools
Iocage to Bastille
There is no automated migration tool. The process:
sh# Export from iocage iocage export webserver # Note the jail's configuration iocage get all webserver > webserver-config.txt # Create in Bastille bastille create webserver 14.2-RELEASE 10.0.0.10 # Copy data from the iocage export into the Bastille jail tar xf /iocage/images/webserver_*.zip -C /usr/local/bastille/jails/webserver/root/
In practice, it is cleaner to recreate jails from scratch using Bastille templates -- which is itself a good reason to adopt templates.
Bastille to Iocage
shbastille export webserver # Extract and import manually into iocage
Neither direction is seamless. Plan for recreation rather than migration.
FAQ
Is iocage abandoned?
Not formally abandoned, but development has slowed dramatically. The Python rewrite still works on current FreeBSD releases, but bug fixes and new features arrive slowly. If you are starting fresh, Bastille is the better investment.
Can I run both Bastille and iocage on the same system?
Technically yes, but do not do it. Both tools manage ZFS datasets and jail configurations, and having two tools manipulate the same system invites conflicts. Choose one and use it exclusively.
Do Bastille templates work like Dockerfiles?
They are inspired by Dockerfiles but operate differently. Docker builds layers; Bastille applies commands to an existing jail. There is no layer caching or image registry. Templates are more like Ansible playbooks than Docker builds -- they describe desired state and apply it.
Which tool does TrueNAS use?
TrueNAS CORE (FreeBSD-based) used iocage for jail management. TrueNAS SCALE moved to Linux and Docker. The future TrueNAS direction does not depend on either Bastille or iocage, so this should not influence your choice.
Can I use Bastille with Ansible?
Yes. Bastille jails are standard FreeBSD jails. Ansible can connect to them via SSH (if sshd runs inside the jail) or via the jail connection plugin. There are also community Ansible roles that wrap Bastille commands for jail lifecycle management.
How does Bastille handle jail dependencies?
Bastille does not have built-in dependency management between jails. If jail B depends on jail A, you manage startup order through rc.conf or a wrapper script. Iocage has a priority property for boot ordering, which is slightly more structured.
Which tool is better for large-scale deployments (50+ jails)?
Bastille's ALL target and template system make it better suited for managing many jails. Updating, patching, and configuring dozens of jails is straightforward with Bastille templates. Iocage requires more manual orchestration at scale, though scripting around iocage list and iocage exec is possible.
Can either tool manage jails remotely?
Neither Bastille nor iocage has built-in remote management. Both are local tools. For remote management, use Ansible, SSH, or CBSD (which has its own remote node management).