Skip to content

Repository files navigation

Dail

Daily jail management for FreeBSD

Features

  • Familiar workflow: create, start, stop, rm, run, exec, build
  • .dail files: 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_port via PF rdr anchors
  • ZFS support: snapshots, clones, ZFS-backed storage
  • Resource limits: rctl-based CPU/memory/process limits

Getting Started

# 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

Examples

PostgreSQL

# 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

Custom Rust service (filest)

# filest.dail
FROM 15.0-RELEASE

SERVICE filest

COPY target/release/filest /usr/local/bin/filest

EXPOSE 8090

When 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 --debug

Without 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 filest

.dail File Reference

A .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)

Common PARAM values

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)

Commands

Setup

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 backend

dail 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 bases

Jail Lifecycle

dail 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/GID

dail 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 jails

dail 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 jails

Inspection

dail 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 JSON

dail config show — Display current configuration.

dail config show

Execution

dail exec — Run a command inside a jail.

dail exec myjail ls /etc
dail exec myjail pkg install -y nginx

dail shell — Open an interactive shell.

dail shell myjail                 # default /bin/sh
dail shell myjail --shell /bin/csh

Logs

dail 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 rootfs

Monitoring

dail top — Show running processes inside a jail.

dail top myjail                     # watch mode (refreshes every 2s)
dail top myjail --once              # single snapshot

Build

dail 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 subdirectory

Cache

dail cache clean — Remove cached pkg packages and repository metadata.

dail cache clean

Shell Completions

Dail 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.fish

Snapshots

dail snapshot — Create a ZFS snapshot (requires ZFS backend).

dail snapshot myjail                # tag: latest
dail snapshot myjail --tag v1.0

dail clone — Clone a jail from a snapshot.

dail clone myjail myjail-copy              # from latest
dail clone myjail:v1.0 myjail-copy         # from tagged snapshot

Presets

dail preset — List available presets.

dail preset

Presets

Presets 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"

Network Modes

Dail supports multiple networking configurations:

Auto IP Allocation (default)

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 lo0

Pool configured in /usr/local/etc/dail/config.yaml:

ip_pool: 10.100.0.0/24
alias_interface: lo0

Explicit IP Alias

Assign a specific IP address:

dail create web --ip 10.100.0.50/24

Note: Dail validates that the IP is not already in use by another jail.

VNET (Virtual Network)

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 bridge0

Port Forwarding

Forward 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 proto

Requires 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:80

If -p is passed on the CLI, all EXPOSE directives are ignored.

Host Network (inherit)

Share the host's network stack:

dail create legacy --network inherit

No Network (isolated)

Completely isolated jail with no network access:

dail create isolated --network none

Configuration

Global 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

Requirements

  • FreeBSD 13+
  • Rust (for building from source)
  • ZFS (optional, for snapshot/clone features)

Building

cargo build --release

License

BSD-3-Clause

About

Daily jail management for FreeBSD

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages