Synopsis #
Application data does not need to live inside a jail’s root filesystem. A dedicated host-managed ZFS dataset gives the data its own snapshot, quota, replication, and retention policy while Bastille adds a persistent nullfs mount into the jail.
This procedure does not delegate ZFS administration to the jail. The host retains control, which is the simpler boundary for most application jails. Delegated datasets require additional jail permissions and a separate threat review.
Define ownership and recovery first #
This example uses jail web, host dataset tank/jaildata/web, host mount point /srv/jaildata/web, and jail path /srv/data. Confirm that none of these names already serve another workload:
$ zfs list tank/jaildata/web
$ ls -ld /srv/jaildata/web
# bastille list jail
Record the numeric UID and GID used by the service inside the jail. A nullfs mount preserves numeric ownership; matching account names do not repair different numeric identifiers.
Create the host dataset #
Create a parent and child dataset with an explicit mount point:
# zfs create -o mountpoint=/srv/jaildata tank/jaildata
# zfs create -o mountpoint=/srv/jaildata/web tank/jaildata/web
$ zfs get mountpoint,mounted,readonly tank/jaildata/web
If the parent already exists, create only the child. Apply quotas or reservations only from measured capacity requirements. Compression may be set according to the host’s existing storage policy; it is not required for the mount.
Set ownership using the service’s numeric identity, then verify it:
# chown 80:80 /srv/jaildata/web
$ ls -ldn /srv/jaildata/web
The value 80:80 is only an example. Substitute the actual UID and GID obtained from the jail.
Add the persistent Bastille mount #
Stop the application before moving existing data. Add a read-write nullfs mount:
# bastille mount web /srv/jaildata/web /srv/data nullfs rw 0 0
# bastille cmd web mount
# bastille cmd web ls -ldn /srv/data
For configuration or reference data that the jail must not modify, use ro instead of rw. Do not mount host secrets, /etc, device trees, or broad parent directories merely for convenience.
Inspect the jail-specific fstab after the command:
# less /usr/local/bastille/jails/web/fstab
The exact Bastille prefix can be changed in bastille.conf; use the configured prefix rather than assuming the default on a customized installation.
Move existing data deliberately #
When the jail already contains application data, stop the service and jail, copy the data into the dataset while preserving ownership and metadata, then mount and verify before restarting. Keep the original copy until the application has been checked and an independent backup exists.
Do not copy into an actively mounted empty dataset over the original path and assume the hidden files were transferred. Work from an alternate path or a stopped jail so that source and destination are unambiguous.
Back up both boundaries #
A Bastille export covers the managed jail root. A separately mounted dataset is an independent storage object and must have its own backup policy. Use Snapshot and replicate a ZFS dataset
for tank/jaildata/web, and use Back up and restore a Bastille jail
for the jail configuration and root.
Coordinate snapshots with the application. A filesystem-consistent snapshot of a running database may still require database-specific recovery or quiescing.