Bastille on FreeBSD: Jail Manager Review
Bastille is a jail management framework for FreeBSD that brings container-like workflows to the jail ecosystem. It uses templates for repeatable deployments, integrates with ZFS, and provides a clean CLI. This review covers installation, jail lifecycle management, networking, templates, ZFS integration, and how Bastille compares to iocage and pot. If you manage FreeBSD jails and want something more structured than raw jail.conf, Bastille deserves evaluation.
For background on FreeBSD jails themselves, see our FreeBSD jails guide. For a broader comparison of jail managers, see best jail manager for FreeBSD.
What Bastille Brings to the Table
FreeBSD jails are powerful but managing them manually involves repetitive configuration: creating directory trees, extracting base systems, writing jail.conf entries, configuring networking, and maintaining updates. Bastille automates all of this.
Key features:
- Template system for declarative jail configuration
- ZFS-aware with clone-based jail creation
- VNET networking support
- Thin and thick jail support
- Import/export for jail migration
- Firewall integration (PF and IPFW)
- Active development and responsive maintainer
Installation
Package Install
shpkg install bastille
Ports Install
shcd /usr/ports/sysutils/bastille make install clean
Initial Configuration
Bastille's configuration lives in /usr/local/etc/bastille/bastille.conf. Review and adjust:
sh# /usr/local/etc/bastille/bastille.conf # Base directory for all Bastille data bastille_prefix="/usr/local/bastille" # ZFS support bastille_zfs_enable="YES" bastille_zfs_zpool="zroot" # Default networking bastille_network_loopback="bastille0" bastille_network_shared="" # Default FreeBSD release bastille_release="14.2-RELEASE"
Enable the service:
shsysrc bastille_enable="YES"
Setting Up the Network Interface
Bastille uses a cloned loopback interface for jail IP addresses by default:
shsysrc cloned_interfaces="lo1" sysrc ifconfig_lo1_name="bastille0" service netif cloneup
If using PF, add NAT rules for jail traffic in /etc/pf.conf:
shext_if="em0" bastille_if="bastille0" nat on $ext_if from ($bastille_if:network) to any -> ($ext_if) # Allow jail traffic pass quick on $bastille_if
Reload PF:
shpfctl -f /etc/pf.conf
Bootstrapping a Release
Before creating jails, you need a FreeBSD release to base them on:
shbastille bootstrap 14.2-RELEASE update
This downloads the FreeBSD base system and applies pending security patches. The files are stored under /usr/local/bastille/releases/14.2-RELEASE/. On ZFS, this is a dedicated dataset.
You can bootstrap multiple releases:
shbastille bootstrap 13.4-RELEASE update bastille bootstrap 14.2-RELEASE update
Creating Jails
Basic Jail Creation
shbastille create webserver 14.2-RELEASE 10.0.0.1
This creates a jail named "webserver" based on FreeBSD 14.2, with IP 10.0.0.1 on the bastille0 interface. On ZFS, Bastille clones the release dataset, making creation nearly instant.
Thick vs Thin Jails
By default, Bastille creates thin jails that share the base system via nullfs mounts. This saves disk space but means jails share the same base binaries.
For thick jails (full independent copy):
shbastille create --thick dbserver 14.2-RELEASE 10.0.0.2
Thick jails use more disk but can be independently updated and modified at the base level.
VNET Jails
For jails that need their own network stack (e.g., running DHCP clients, custom routing):
shbastille create -V vnetjail 14.2-RELEASE em0
VNET jails get a virtual epair interface bridged to the specified physical interface. They appear as independent hosts on the network.
Jail Lifecycle Management
Starting and Stopping
shbastille start webserver bastille stop webserver bastille restart webserver
Console Access
shbastille console webserver
This opens a shell inside the jail. Equivalent to jexec webserver /bin/sh.
Running Commands
shbastille cmd webserver pkg update bastille cmd webserver pkg install -y nginx
Listing Jails
shbastille list
Output includes jail name, status, IP address, and resource usage.
Destroying Jails
shbastille destroy webserver
On ZFS, this destroys the associated dataset. The operation is clean and complete.
Templates
Templates are Bastille's strongest feature. They define a sequence of commands to configure a jail declaratively.
Template Structure
A Bastille template is a directory containing a Bastillefile:
sh# Example Bastillefile for an Nginx jail PKG nginx SYSRC nginx_enable=YES CP usr/local/etc/nginx/nginx.conf usr/local/etc/nginx/nginx.conf SERVICE nginx start
Built-in Commands
| Command | Description |
|---------|-------------|
| PKG | Install packages |
| SYSRC | Set rc.conf variables |
| CP | Copy files into the jail |
| CMD | Run arbitrary commands |
| SERVICE | Manage services |
| MOUNT | Mount filesystems |
| FSTAB | Add fstab entries |
| RDR | Port redirection (PF) |
| INCLUDE | Include another template |
Applying Templates
shbastille template webserver bastillebsd/nginx
This fetches and applies the nginx template from the BastilleBSD repository. You can also apply local templates:
shbastille template webserver /path/to/my/template
Custom Templates
Create a directory structure:
shmkdir -p /usr/local/bastille/templates/myorg/postgres
Write the Bastillefile:
sh# /usr/local/bastille/templates/myorg/postgres/Bastillefile PKG postgresql16-server postgresql16-client SYSRC postgresql_enable=YES CMD /usr/local/etc/rc.d/postgresql initdb SERVICE postgresql start CMD psql -U postgres -c "ALTER USER postgres PASSWORD 'changeme';"
Apply it:
shbastille create dbserver 14.2-RELEASE 10.0.0.3 bastille template dbserver myorg/postgres
Template Composition
Templates can include other templates:
sh# Bastillefile INCLUDE myorg/base-config INCLUDE myorg/monitoring-agent PKG nginx SYSRC nginx_enable=YES SERVICE nginx start
This enables layered configurations: a base template for common settings (DNS, NTP, users), a monitoring template for your agent, and service-specific configuration on top.
ZFS Integration
Bastille's ZFS support is well-implemented and gives you meaningful operational advantages.
Dataset Layout
When ZFS is enabled, Bastille creates:
shellzroot/bastille zroot/bastille/releases zroot/bastille/releases/14.2-RELEASE zroot/bastille/jails zroot/bastille/jails/webserver zroot/bastille/templates
Snapshots
sh# Snapshot a jail zfs snapshot zroot/bastille/jails/webserver@pre-upgrade # Rollback if something breaks zfs rollback zroot/bastille/jails/webserver@pre-upgrade
Export and Import
Bastille can export jails as compressed archives:
shbastille export webserver
This creates a file in /usr/local/bastille/backups/ that can be imported on another host:
shbastille import webserver.xz
On ZFS, export uses zfs send under the hood, which is efficient and consistent.
Updating Jails
Updating the Base Release
shbastille update 14.2-RELEASE
This fetches and applies security patches to the release. For thin jails, all jails based on this release get the updates immediately (they share the base via nullfs).
Upgrading Between Releases
shbastille upgrade 14.1-RELEASE 14.2-RELEASE
Package Updates Inside Jails
sh# Update packages in a specific jail bastille cmd webserver pkg upgrade -y # Update packages in all running jails bastille cmd ALL pkg upgrade -y
Firewall Integration
PF Port Redirection
Bastille integrates with PF for port forwarding:
sh# In a Bastillefile RDR tcp 80 80 RDR tcp 443 443
Or manually:
shbastille rdr webserver tcp 80 80 bastille rdr webserver tcp 443 443
This adds redirect rules to PF, forwarding traffic from the host's external IP to the jail.
IPFW Support
If you use IPFW instead of PF, Bastille supports it via configuration:
sh# In bastille.conf bastille_network_pf_ext_if="" # Clear PF settings
You then manage IPFW rules manually or via templates using CMD directives.
Comparison with Alternatives
Bastille vs iocage
iocage was the dominant jail manager for years but development has stalled. Bastille is actively maintained and has a clearer design.
| Feature | Bastille | iocage |
|---------|----------|--------|
| Active development | Yes | Minimal |
| Template system | Yes (Bastillefiles) | No |
| ZFS support | Yes | Yes |
| VNET support | Yes | Yes |
| Thick/thin jails | Both | Primarily thick |
| Documentation | Good | Dated |
| Learning curve | Low | Medium |
Bastille vs pot
pot is another modern jail manager with a focus on orchestration (Nomad integration). If you need Nomad/Consul integration for orchestrated workloads, pot is worth considering. For standalone jail management, Bastille's template system is more practical.
Bastille vs Raw jail.conf
If you manage 1-3 jails and want maximum control, raw jail.conf is fine. Once you manage more than 5 jails, or need repeatable deployments, Bastille saves time and reduces errors. The template system alone justifies the dependency.
Production Tips
Resource Limits
Use rctl to limit jail resource consumption:
shbastille cmd webserver rctl -a jail:webserver:memoryuse:deny=2G bastille cmd webserver rctl -a jail:webserver:cputime:sigterm=3600
Enable RACCT in the kernel:
shecho 'kern.racct.enable=1' >> /boot/loader.conf
Monitoring
Monitor jail resource usage:
shbastille top webserver
Or use rctl -u jail:webserver for detailed resource accounting.
Backup Strategy
With ZFS, snapshot-based backups are the most efficient approach:
sh# Daily snapshot script for jail in $(bastille list -q); do zfs snapshot "zroot/bastille/jails/${jail}@daily-$(date +%Y%m%d)" done # Prune snapshots older than 30 days zfs list -t snapshot -o name -H | grep "bastille/jails" | while read snap; do # Add date parsing and pruning logic done
Verdict
Bastille is the best jail manager currently available for FreeBSD. It is actively maintained, well-documented, and the template system provides genuine value for repeatable deployments. ZFS integration works correctly, VNET support is solid, and the CLI is intuitive.
The only weakness is that the ecosystem of community templates is still growing. You will likely write custom templates for your specific needs. But the framework for doing so is clean and well-designed.
Rating: 8/10 -- The best option in its category. Minor deductions for a still-maturing template ecosystem and occasional rough edges with VNET configuration on complex network topologies.
Frequently Asked Questions
Can I run Docker inside a Bastille jail?
No. Docker requires a Linux kernel. FreeBSD jails are an OS-level virtualization mechanism, not a Linux container runtime. If you need Docker, use bhyve to run a Linux VM, or use podman with Linux compatibility.
How do I give a jail internet access?
Configure NAT in PF (as shown in the installation section). The jail uses the bastille0 loopback interface, and PF NATs traffic to the external interface. Ensure the jail has correct DNS configuration in /usr/local/bastille/jails/JAILNAME/root/etc/resolv.conf.
Can I migrate jails between hosts?
Yes. Use bastille export on the source and bastille import on the destination. Both hosts must run compatible FreeBSD versions. For live migration, you will need a shared ZFS pool or manual zfs send | zfs receive workflow.
How do I access a service running in a jail from outside?
Use PF port redirection via bastille rdr, or configure the jail with a VNET interface on a routable network. The PF approach is simpler for most use cases.
Does Bastille support IPv6?
Yes. Assign IPv6 addresses when creating jails:
shbastille create webserver 14.2-RELEASE 10.0.0.1 fd00::1
How many jails can I run on a single host?
The practical limit depends on memory and CPU. Thin jails share the base system and add minimal overhead (a few MB per idle jail). Production deployments commonly run 20-50 jails on a single host. With adequate resources, hundreds are possible.