How to Manage FreeBSD Jails with Bastille
Bastille is a jail management framework for FreeBSD that simplifies the creation, configuration, and maintenance of jails. FreeBSD jails are operating system-level virtualization containers that provide isolation with near-native performance. Bastille adds automation, templates, networking helpers, and a consistent interface on top of the native jail infrastructure. This guide covers installation, bootstrapping releases, creating and managing jails, templates, networking modes, import/export, and practical deployment patterns.
What Bastille Does
FreeBSD jails are powerful but require manual configuration of networking, filesystems, and service management. Bastille provides:
- One-command jail creation and destruction
- Release bootstrapping (downloading and managing FreeBSD base systems)
- Template system for automated jail provisioning
- Multiple networking modes (shared IP, VNET, loopback with NAT)
- Import/export for jail portability
- Thin jails using ZFS clones (fast creation, minimal disk usage)
- Thick jails for fully independent environments
- Integration with pf for NAT and port forwarding
Bastille does not replace jails. It is a management layer on top of FreeBSD's native jail implementation.
Prerequisites
- FreeBSD 14.0 or later
- Root access
- ZFS recommended (for thin jails and snapshots)
- pf recommended (for VNET and NAT networking)
Step 1: Install Bastille
shpkg install bastille
Configure Bastille
Edit /usr/local/etc/bastille/bastille.conf:
sh# View the default configuration cat /usr/local/etc/bastille/bastille.conf
Key settings to review:
sh## ZFS options (recommended) bastille_zfs_enable="YES" bastille_zfs_zpool="zroot" ## Default networking bastille_network_loopback="bastille0" bastille_network_shared="" ## Bastille storage prefix bastille_prefix="/usr/local/bastille"
If you are using ZFS (recommended), enable ZFS support. Bastille will create a ZFS dataset at zroot/bastille (or whatever pool you specify).
Create the Loopback Interface
Bastille uses a cloned loopback interface for jail networking:
shsysrc cloned_interfaces+="lo1" sysrc ifconfig_lo1_name="bastille0" service netif cloneup
Verify the interface exists:
shifconfig bastille0
Enable Bastille at Boot
shsysrc bastille_enable="YES"
This ensures jails configured for auto-start boot with the system.
Step 2: Bootstrap a FreeBSD Release
Before creating jails, bootstrap a FreeBSD release. This downloads the base system files that jails will use.
shbastille bootstrap 14.2-RELEASE update
This downloads the FreeBSD 14.2-RELEASE base system and applies the latest security patches. The files are stored under /usr/local/bastille/releases/14.2-RELEASE/.
List Available Releases
shbastille list releases
Bootstrap a Specific Architecture
shbastille bootstrap 14.2-RELEASE update amd64
Update a Bootstrapped Release
Apply security patches to an existing release:
shbastille update 14.2-RELEASE
Step 3: Create Jails
Create a Thin Jail (ZFS Clone)
Thin jails share the base system via ZFS clones. They are fast to create and use minimal disk space:
shbastille create webserver 14.2-RELEASE 10.0.0.1
This creates a jail named webserver based on FreeBSD 14.2-RELEASE with IP address 10.0.0.1 on the bastille0 loopback interface.
Create a Thick Jail
Thick jails have a full independent copy of the base system. They are larger but completely self-contained:
shbastille create -T database 14.2-RELEASE 10.0.0.2
The -T flag creates a thick jail.
Create a VNET Jail
VNET jails have their own network stack with a dedicated virtual interface:
shbastille create -V appserver 14.2-RELEASE em0
The -V flag enables VNET networking. The jail gets its own epair interface bridged to em0.
List Jails
shbastille list
Output shows jail name, state (Up/Down), IP address, and release version.
Start and Stop Jails
shbastille start webserver bastille stop webserver bastille restart webserver
Enter a Jail Console
shbastille console webserver
This drops you into a root shell inside the jail. Use exit to return to the host.
Run a Command in a Jail
shbastille cmd webserver uname -a bastille cmd webserver pkg update
Step 4: Configure Networking
Bastille supports multiple networking modes. The choice depends on your isolation and routing requirements.
Mode 1: Shared IP (Loopback + NAT)
This is the default and most common mode. Jails get IP addresses on the bastille0 loopback interface. Traffic is NATed through the host's external interface using pf.
Configure pf for NAT. Edit /etc/pf.conf:
shext_if="igb0" jail_net="10.0.0.0/24" nat on $ext_if from $jail_net to any -> ($ext_if) # Port forwarding to jail rdr on $ext_if proto tcp from any to ($ext_if) port 80 -> 10.0.0.1 port 80 rdr on $ext_if proto tcp from any to ($ext_if) port 443 -> 10.0.0.1 port 443
Enable and start pf:
shsysrc pf_enable="YES" service pf start
Enable IP forwarding:
shsysrc gateway_enable="YES" sysctl net.inet.ip.forwarding=1
Mode 2: VNET
VNET gives each jail its own network stack with a virtual interface. The jail can have its own IP address on the physical network, run its own firewall, and bind to any port without conflict.
shbastille create -V webserver 14.2-RELEASE em0
VNET jails appear as separate machines on the network. They get IP addresses via DHCP or static configuration inside the jail.
Configure a static IP inside the jail:
shbastille sysrc webserver ifconfig_e0b_bastille0="inet 192.168.1.50 netmask 255.255.255.0" bastille sysrc webserver defaultrouter="192.168.1.1"
Mode 3: Shared IP on Host Interface
Assign a jail an IP alias on the host's physical interface:
sh# Add IP alias to the host interface ifconfig igb0 alias 203.0.113.11/32 # Create jail with that IP bastille create webserver 14.2-RELEASE 203.0.113.11/32 igb0
This gives the jail a public IP address directly on the host's interface. No NAT required.
Step 5: Package Management in Jails
Install Packages
shbastille pkg webserver install nginx bastille pkg webserver install -y nginx php83 php83-extensions
Update Packages
shbastille pkg webserver update bastille pkg webserver upgrade -y
Enable and Start Services
shbastille sysrc webserver nginx_enable="YES" bastille service webserver nginx start
Bulk Operations Across Jails
sh# Update packages in all jails bastille pkg ALL update bastille pkg ALL upgrade -y # Restart a service in all jails bastille service ALL nginx restart
The ALL target applies the command to every running jail.
Step 6: Templates
Bastille templates automate jail provisioning. A template is a set of instructions that install packages, copy files, configure services, and execute commands.
Template Format
Templates are directories containing a Bastillefile (similar to a Dockerfile). Create a template directory:
shmkdir -p /usr/local/bastille/templates/myorg/webserver
Create /usr/local/bastille/templates/myorg/webserver/Bastillefile:
shPKG nginx php83 php83-extensions php83-curl php83-gd php83-mbstring php83-mysqli php83-xml SYSRC nginx_enable=YES SYSRC php_fpm_enable=YES CP usr SERVICE nginx start SERVICE php-fpm start CMD pw useradd deploy -m -s /bin/sh
Template Commands
| Command | Purpose |
|---------|---------|
| PKG | Install packages |
| SYSRC | Set rc.conf variables |
| SERVICE | Start/stop/restart services |
| CP | Copy files from template overlay directory |
| CMD | Execute a shell command |
| RDR | Configure port redirection (pf) |
| MOUNT | Mount a filesystem or nullfs |
| FSTAB | Add fstab entries |
| CONFIG | Apply sysctl settings |
| LIMITS | Set resource limits |
File Overlay
The CP usr command copies files from the template's usr/ directory into the jail's filesystem. Create the overlay:
shmkdir -p /usr/local/bastille/templates/myorg/webserver/usr/local/etc/nginx/
Place your nginx.conf in the overlay directory. When the template runs, the file is copied into the jail at the same path.
Apply a Template
shbastille template webserver myorg/webserver
This runs the Bastillefile against the webserver jail, installing packages, copying configuration, and starting services.
Apply a Template During Jail Creation
shbastille create webserver 14.2-RELEASE 10.0.0.1 -T myorg/webserver
Community Templates
Bastille supports templates from Git repositories:
shbastille template webserver https://gitlab.com/bastillebsd-templates/nginx
This clones the repository and applies the template.
Step 7: Import and Export
Export a Jail
Create a portable archive of a jail:
shbastille export webserver
This creates a compressed archive (.xz) in /usr/local/bastille/backups/. The archive includes the jail's filesystem, configuration, and metadata.
shls -la /usr/local/bastille/backups/
Export with a Specific Format
sh# Export as raw ZFS stream (faster, requires ZFS on target) bastille export --raw webserver # Export as tar.xz (portable, works on any FreeBSD) bastille export webserver
Import a Jail
shbastille import /usr/local/bastille/backups/webserver_2026-04-09.xz
The imported jail retains its original name and configuration. If a jail with the same name exists, you need to rename or destroy it first.
Migration Between Servers
sh# On source server bastille export webserver scp /usr/local/bastille/backups/webserver_*.xz target-server:/tmp/ # On target server bastille import /tmp/webserver_2026-04-09.xz bastille start webserver
Step 8: Resource Limits
FreeBSD's RCTL (Resource Control) provides per-jail resource limits. Enable RCTL in /boot/loader.conf:
shkern.racct.enable=1
Reboot for this to take effect. Then apply limits via Bastille:
sh# Limit memory to 2 GB bastille limits webserver memoryuse:deny=2G # Limit CPU to 200% (2 cores equivalent) bastille limits webserver pcpu:deny=200 # Limit open files bastille limits webserver openfiles:deny=4096 # View current limits bastille limits webserver
Or use rctl directly:
shrctl -a jail:webserver:memoryuse:deny=2G rctl -a jail:webserver:pcpu:deny=200 rctl -u jail:webserver
Step 9: ZFS Integration
Bastille's ZFS integration provides significant advantages for jail management.
Snapshots
sh# Snapshot a jail zfs snapshot zroot/bastille/jails/webserver@pre-upgrade # List jail snapshots zfs list -t snapshot -r zroot/bastille/jails/webserver # Rollback a jail bastille stop webserver zfs rollback zroot/bastille/jails/webserver@pre-upgrade bastille start webserver
Clone a Jail
Create a new jail from an existing one using ZFS clones:
sh# Snapshot the source jail zfs snapshot zroot/bastille/jails/webserver@clone-source # Clone to a new jail (manual process) zfs clone zroot/bastille/jails/webserver@clone-source zroot/bastille/jails/webserver2
Note: you need to update the jail configuration (IP address, hostname) in the cloned jail.
Disk Usage
sh# Check disk usage per jail zfs list -r zroot/bastille/jails -o name,used,refer,compressratio # Check total Bastille usage zfs list zroot/bastille
Step 10: Production Deployment Patterns
Pattern 1: Web Application Stack
Create separate jails for each service:
sh# Web server jail bastille create web 14.2-RELEASE 10.0.0.1 bastille pkg web install -y nginx # Application jail bastille create app 14.2-RELEASE 10.0.0.2 bastille pkg app install -y php83 php83-extensions # Database jail bastille create -T db 14.2-RELEASE 10.0.0.3 bastille pkg db install -y mariadb1011-server # Cache jail bastille create cache 14.2-RELEASE 10.0.0.4 bastille pkg cache install -y redis
Configure pf for port forwarding:
shrdr on $ext_if proto tcp from any to ($ext_if) port {80, 443} -> 10.0.0.1
Auto-Start Jails at Boot
Jails created with Bastille are automatically configured to start at boot when bastille_enable="YES" is set. To prevent a specific jail from auto-starting:
shbastille stop webserver # Edit the jail's configuration vi /usr/local/bastille/jails/webserver/jail.conf # Set: exec.start = "" to prevent auto-start
Or selectively manage which jails start:
sh# Start specific jails in a script bastille start web app db cache
Updating Jails
Update the Base System
sh# Update the release (applies to new thin jails) bastille update 14.2-RELEASE # Apply updates to a running jail bastille cmd webserver freebsd-update fetch install
Upgrade to a New Release
sh# Bootstrap the new release bastille bootstrap 14.3-RELEASE update # Upgrade a jail (thick jails) bastille cmd webserver freebsd-update -r 14.3-RELEASE upgrade bastille cmd webserver freebsd-update install bastille restart webserver bastille cmd webserver freebsd-update install
For thin jails, recreate the jail against the new release and reapply the template.
Monitoring Jails
View Running Jails
shbastille list jls
Resource Usage
sh# CPU and memory per jail (requires RCTL) rctl -u jail:webserver # Process list inside a jail bastille cmd webserver top -b -n 1 | head -20 # Network connections bastille cmd webserver sockstat -4
Jail Logs
Jails write logs to their own /var/log/ inside the jail filesystem:
shbastille cmd webserver tail -20 /var/log/messages
Or access from the host:
shtail -20 /usr/local/bastille/jails/webserver/root/var/log/messages
Frequently Asked Questions
What is the difference between thin and thick jails?
Thin jails use ZFS clones and share the base system with the release. They are fast to create (seconds), use minimal disk space, and are ideal for most use cases. Thick jails have a full independent copy of the base system. They are slower to create, use more disk space, but are completely self-contained and can be independently updated.
Can I run Docker inside a FreeBSD jail?
No. Docker requires a Linux kernel. If you need Docker, use bhyve to run a Linux VM. Jails are FreeBSD's native container technology and serve a similar purpose to Docker for FreeBSD applications.
Can jails communicate with each other?
Yes. Jails on the same loopback interface (bastille0) can communicate directly via their assigned IP addresses. For VNET jails, network communication follows normal networking rules.
How do I give a jail access to a host directory?
Use nullfs mounts:
sh# Mount a host directory into the jail mount -t nullfs /data/shared /usr/local/bastille/jails/webserver/root/mnt/shared
Or use Bastille's fstab integration. Add to the jail's fstab:
shbastille mount webserver /data/shared /mnt/shared nullfs ro 0 0
Can I limit a jail's network bandwidth?
Not directly through Bastille. Use IPFW dummynet or pf's queueing on the host to shape traffic for specific jail IP addresses. See the IPFW setup guide for dummynet configuration.
How do I back up all jails?
sh# Export all jails for jail in $(bastille list -a | tail -n +2 | awk '{print $1}'); do bastille export "${jail}" done
Combined with ZFS snapshots on the zroot/bastille dataset, this provides comprehensive backup coverage.
Can I run a GUI application in a jail?
Technically possible with X11 forwarding, but not practical. Jails are designed for server workloads. For GUI isolation, use bhyve VMs.
How many jails can I run on one host?
There is no hard limit. The practical limit depends on RAM, CPU, and disk I/O. A moderately sized server (32 GB RAM, 8 cores) can comfortably run 20-50 lightweight jails. Thin jails have negligible base overhead since they share the base system.
Is Bastille the only jail manager?
No. Alternatives include pot (another jail management tool), iocage (older, less maintained), and manual jail.conf configuration. Bastille is the most actively developed and feature-complete option as of 2026.
How do I assign a jail a hostname resolvable by other jails?
Configure DNS inside each jail, or add entries to /etc/hosts in each jail. Bastille can distribute hosts files via templates. Alternatively, run a DNS server (Unbound or dnsmasq) in a dedicated jail and point other jails to it.
sh# Set hostname for a jail bastille sysrc webserver hostname="webserver.jail.local" # Add hosts entries bastille cmd webserver sh -c 'echo "10.0.0.1 web.jail.local" >> /etc/hosts' bastille cmd webserver sh -c 'echo "10.0.0.3 db.jail.local" >> /etc/hosts'