FreeBSD.software
Home/Guides/How to Manage FreeBSD Jails with Bastille
tutorial·2026-04-09·12 min read

How to Manage FreeBSD Jails with Bastille

Complete guide to managing FreeBSD jails with Bastille: installation, bootstrapping, creating jails, templates, networking modes, import/export, and production deployment patterns.

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

sh
pkg 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:

sh
sysrc cloned_interfaces+="lo1" sysrc ifconfig_lo1_name="bastille0" service netif cloneup

Verify the interface exists:

sh
ifconfig bastille0

Enable Bastille at Boot

sh
sysrc 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.

sh
bastille 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

sh
bastille list releases

Bootstrap a Specific Architecture

sh
bastille bootstrap 14.2-RELEASE update amd64

Update a Bootstrapped Release

Apply security patches to an existing release:

sh
bastille 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:

sh
bastille 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:

sh
bastille 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:

sh
bastille 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

sh
bastille list

Output shows jail name, state (Up/Down), IP address, and release version.

Start and Stop Jails

sh
bastille start webserver bastille stop webserver bastille restart webserver

Enter a Jail Console

sh
bastille 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

sh
bastille 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:

sh
ext_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:

sh
sysrc pf_enable="YES" service pf start

Enable IP forwarding:

sh
sysrc 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.

sh
bastille 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:

sh
bastille 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

sh
bastille pkg webserver install nginx bastille pkg webserver install -y nginx php83 php83-extensions

Update Packages

sh
bastille pkg webserver update bastille pkg webserver upgrade -y

Enable and Start Services

sh
bastille 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:

sh
mkdir -p /usr/local/bastille/templates/myorg/webserver

Create /usr/local/bastille/templates/myorg/webserver/Bastillefile:

sh
PKG 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:

sh
mkdir -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

sh
bastille 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

sh
bastille create webserver 14.2-RELEASE 10.0.0.1 -T myorg/webserver

Community Templates

Bastille supports templates from Git repositories:

sh
bastille 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:

sh
bastille export webserver

This creates a compressed archive (.xz) in /usr/local/bastille/backups/. The archive includes the jail's filesystem, configuration, and metadata.

sh
ls -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

sh
bastille 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:

sh
kern.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:

sh
rctl -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:

sh
rdr 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:

sh
bastille 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

sh
bastille 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:

sh
bastille cmd webserver tail -20 /var/log/messages

Or access from the host:

sh
tail -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:

sh
bastille 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'

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.