Daily jail management for FreeBSD
- Familiar workflow:
create,start,stop,rm,run,exec,build .dailfiles: declarative jail builds- Presets: one flag to configure common workloads (
--preset postgres) - Thick & thin jails: full copy or shared base with per-jail overlay
- Networking: inherit, IP alias, or VNET with bridge
- Port forwarding:
-p host_port:jail_portvia PF rdr anchors - ZFS support: snapshots, clones, ZFS-backed storage
- Resource limits: rctl-based CPU/memory/process limits
# 1. Install dail
cargo build --release
doas install -m 755 target/release/dail /usr/local/bin/dail
# 2. Initialize (creates /var/db/dail/, writes default config)
doas dail config init
# 3. Download FreeBSD base
doas dail bootstrap
# 4. Run your first jail
doas dail run myjail
# 5. Open a shell inside
doas dail shell myjail
# 6. Stop and remove
doas dail stop myjail
doas dail rm myjail# postgres.dail
FROM 15.0-RELEASE
PARAM allow.sysvipc=true
RUN pkg install -y postgresql18-server
SERVICE postgresql --no-user
RUN service postgresql oneinitdb
COPY postgresql.conf /var/db/postgres/data18/postgresql.conf
COPY pg_hba.conf /var/db/postgres/data18/pg_hba.conf
EXPOSE 5432# Build and run
doas dail run postgres.dail --preset postgres
# Check status
doas dail ls
# View logs
doas dail logs postgresql
# Create a user and database for your app (e.g. "zid")
doas -u postgres createuser --pwprompt zid -h 10.100.0.2
doas -u postgres createdb -O zid zid -h 10.100.0.2
# Connect from host
psql -h 10.100.0.2 -U zid -d zid
# Open shell inside jail
doas dail shell postgresql
# Stop and remove
doas dail stop postgresql
doas dail rm postgresql# filest.dail
FROM 15.0-RELEASE
SERVICE filest
COPY target/release/filest /usr/local/bin/filest
EXPOSE 8090When the service binary accepts its own flags, separate SERVICE flags from command arguments using -- or ::
# pass --port to the binary, not to SERVICE
SERVICE filest --run-user=www -- /usr/local/bin/filest --port 8090 --debug
# colon works the same way
SERVICE filest --run-user=www : /usr/local/bin/filest --port 8090 --debugWithout a separator, tokens after the name are treated as SERVICE flags — passing --port directly would cause a parse error. If no command is specified, SERVICE defaults to /usr/local/bin/<name>.
# Build the binary on host first
cargo build --release
# Build jail and run
# --uid makes mount ownership match host user
# -e passes namespace config to the service
doas dail run filest.dail --uid 1001 \
--mount /home/user/photos:/data/photos \
-e NS_photos=/data/photos
# Check it's running
doas dail ls
curl http://10.100.0.2:8090/
# View logs
doas dail logs filest
# Rebuild after code changes
cargo build --release
doas dail run filest.dail --rebuild
# Stop
doas dail stop filestA .dail file describes how to build and run a jail — similar to a Dockerfile.
# Base FreeBSD release
FROM 15.0-RELEASE
# Install packages
RUN pkg install -y nginx
# Set working directory for subsequent RUN/CMD
WORKDIR /usr/local/www
# Copy config files from build context into the jail
COPY nginx.conf /usr/local/etc/nginx/nginx.conf
# Set environment variables (appended to /etc/profile)
ENV APP_ENV=production
# Jail parameters (FreeBSD jail.conf options)
PARAM allow.raw_sockets=true
# Mount host directories into the jail
MOUNT /home/user/site:/usr/local/www
MOUNT /data:/mnt/data:ro
# Enable a service (creates user/group/dirs, adds to rc.conf)
SERVICE nginx --no-user
# Log file path (used by `dail logs`)
LOG /var/log/nginx/access.log
# Port forwarding (host_port:jail_port)
EXPOSE 8080:80
EXPOSE 443
# Override default startup command
CMD /usr/local/sbin/nginx -g "daemon off;"| Directive | Syntax | Description |
|---|---|---|
FROM |
FROM <release> |
FreeBSD base release |
RUN |
RUN <command> |
Execute command during build |
WORKDIR |
WORKDIR <path> |
Working directory for subsequent RUN and CMD |
COPY |
COPY [--chown=u:g] <src> <dst> |
Copy files into jail (supports globs). Auto-chown to SERVICE user if present |
ENV |
ENV <KEY>=<VALUE> |
Set environment variable |
PARAM |
PARAM <key>=<value> |
Set jail parameter (see jail(8)) |
MOUNT |
MOUNT <src>:<dst>[:ro] |
Mount host directory (optional :ro suffix) |
SERVICE |
SERVICE <name> [--no-user] [--run-user=<user>] [-- | :] [<cmd> [args…]] |
Enable service, create user/group/dirs, set persist. Use -- or : to separate SERVICE flags from the command and its own arguments |
LOG |
LOG <path> |
Log file for dail logs |
EXPOSE |
EXPOSE [host:]<port>[/proto] |
Port forwarding (default tcp, overridden by -p) |
CMD |
CMD <command> |
Startup command (overrides SERVICE default) |
| Parameter | Description |
|---|---|
allow.raw_sockets=true |
Allow ping and raw socket access |
allow.sysvipc=true |
Allow SysV IPC (required by PostgreSQL) |
allow.mlock=true |
Allow memory locking |
allow.chflags=true |
Allow changing file flags |
ip4=inherit |
Share host IPv4 stack |
ip6=inherit |
Share host IPv6 stack |
children.max=5 |
Allow nested jails (up to N) |
securelevel=0 |
Set jail securelevel |
Full list: jail(8)
dail config init — Initialize dail: create directory structure (/var/db/dail/), write default config.
dail config init # directory backend
dail config init --zfs-pool zroot # ZFS backenddail bootstrap — Download and extract a FreeBSD base system.
dail bootstrap # download default (15.0-RELEASE)
dail bootstrap 14.2-RELEASE # specific release
dail bootstrap --list # show bootstrapped basesdail create — Create a jail without starting it.
dail create myjail # thick jail, default base
dail create myjail --type thin --base 14.2-RELEASE
dail create myjail --preset postgres # apply preset
dail create web --vnet --vnet-ip 10.0.0.5/24 --vnet-gateway 10.0.0.1
dail create app --mount /data/app:/app --allow raw_sockets --limit maxproc=256
dail create web -p 8080:80 # port forwarding via PF
dail create app --uid 1001 # service user with specific UID/GIDdail run — Create and start a jail in one step. Same options as create, plus --rm, --build, --rebuild.
dail run myjail # create + start
dail run postgres-jail --preset postgres # with preset
dail run temp --rm # auto-remove on stop
dail run web --vnet --vnet-ip 10.0.0.5/24 --vnet-gateway 10.0.0.1
dail run app --mount /data:/app --preset dev --limit maxproc=512
dail run postgres.dail # build + start, name from filename
dail run postgres.dail --name pg # build with explicit name
dail run postgres.dail --rebuild # rebuild from scratch
dail run https://github.com/user/repo.git --name app # build from git repo
dail run https://github.com/user/repo//jails/web --name web # build from subdirectory
dail run web -p 8080:80 # port forwarding
dail run web -p 8080:80/tcp -p 5432:5432 # multiple ports
dail run app.dail --uid 1001 # service user with specific UID/GID
dail run app.dail -w /app # working dir for RUN/CMD (overrides WORKDIR)dail start / stop / restart — Manage jail state.
dail start myjail
dail stop myjail # if --rm was set, jail is auto-removed
dail stop --all # stop all running jails
dail restart myjail
dail restart --all # restart all running jailsdail rm — Remove a jail and its filesystem.
dail rm myjail # must be stopped
dail rm myjail --force # stop + remove
dail rm --all --force # remove all jailsdail ls — List jails.
dail ls # all jails (colored status)
dail ls --running # only running
dail ls --format json # JSON output
dail ls -q # names only (for scripting)dail inspect — Show jail details.
dail inspect myjail # human-readable
dail inspect myjail --json # raw JSONdail config show — Display current configuration.
dail config showdail exec — Run a command inside a jail.
dail exec myjail ls /etc
dail exec myjail pkg install -y nginxdail shell — Open an interactive shell.
dail shell myjail # default /bin/sh
dail shell myjail --shell /bin/cshdail logs — View jail logs. By default reads CMD stdout/stderr (cmd.log). If LOG is set in the .dail file, reads that file from the jail rootfs instead. The log file is auto-created with write permissions at jail start.
dail logs myjail # CMD output (or LOG file if set in .dail)
dail logs myjail --tail 20 # last 20 lines
dail logs myjail -f # follow (like tail -f)
dail logs myjail --file /var/log/messages # read arbitrary file from jail rootfsdail top — Show running processes inside a jail.
dail top myjail # watch mode (refreshes every 2s)
dail top myjail --once # single snapshotdail build — Build a jail from a .dail file or git URL.
dail build pg.dail --name myapp
dail build ./jails/web.dail --name web
dail build https://github.com/user/repo.git --name app # build from git repo
dail build https://github.com/user/repo//jails/web --name web # build from subdirectorydail cache clean — Remove cached pkg packages and repository metadata.
dail cache cleanDail supports dynamic completions — jail names and other values are completed at runtime.
# Dynamic completions (recommended — live jail name and image completion)
echo 'source <(COMPLETE=zsh dail)' >> ~/.zshrc
echo 'source <(COMPLETE=bash dail)' >> ~/.bashrc
COMPLETE=fish dail > ~/.config/fish/completions/dail.fish
# Static completions (subcommands and flags only, no live names)
dail completions zsh | doas tee /usr/local/share/zsh/site-functions/_dail > /dev/null
dail completions bash | doas tee /usr/local/etc/bash_completion.d/dail > /dev/null
dail completions fish > ~/.config/fish/completions/dail.fishdail snapshot — Create a ZFS snapshot (requires ZFS backend).
dail snapshot myjail # tag: latest
dail snapshot myjail --tag v1.0dail clone — Clone a jail from a snapshot.
dail clone myjail myjail-copy # from latest
dail clone myjail:v1.0 myjail-copy # from tagged snapshotdail preset — List available presets.
dail presetPresets apply common jail parameters in one flag:
| Preset | What it does |
|---|---|
postgres |
allow.sysvipc=true |
dev |
allow.raw_sockets=true, allow.sysvipc=true |
Custom presets: create YAML (or TOML) files in /var/db/dail/presets/:
# /var/db/dail/presets/myapp.yaml
description: "My custom app"
params:
allow.raw_sockets: "true"
limits:
maxproc: "256"Dail supports multiple networking configurations:
When no network flags are specified, dail automatically allocates an IP from the configured pool:
dail run myjail # Auto-allocates 10.100.0.X
# Output: IP allocated: 10.100.0.2 on lo0Pool configured in /usr/local/etc/dail/config.yaml:
ip_pool: 10.100.0.0/24
alias_interface: lo0Assign a specific IP address:
dail create web --ip 10.100.0.50/24Note: Dail validates that the IP is not already in use by another jail.
Full network stack isolation with bridged networking:
dail create app --vnet --vnet-ip 10.0.0.5/24 --vnet-gateway 10.0.0.1 --vnet-bridge bridge0Forward host ports to jail ports using PF rdr anchors:
dail run web -p 8080:80 # forward host:8080 → jail:80
dail run web -p 8080:80/tcp -p 5432:5432 # multiple ports, optional protoRequires PF enabled with the dail anchor in /etc/pf.conf:
rdr-anchor "dail/*"
In .dail files, use EXPOSE to declare default port mappings:
EXPOSE 5432
EXPOSE 8080:80If -p is passed on the CLI, all EXPOSE directives are ignored.
Share the host's network stack:
dail create legacy --network inheritCompletely isolated jail with no network access:
dail create isolated --network noneGlobal config at /usr/local/etc/dail/config.yaml (TOML fallback supported):
root_dir: /var/db/dail
storage_backend: directory # or "zfs"
default_base: "15.0-RELEASE"
alias_interface: lo0
ip_pool: 10.100.0.0/24
mirror: https://download.freebsd.org/releases
# Optional: for ZFS backend
# zfs_pool: zroot- FreeBSD 13+
- Rust (for building from source)
- ZFS (optional, for snapshot/clone features)
cargo build --releaseBSD-3-Clause