dail: running services in freebsd jails, docker-style
I'm new to FreeBSD. Most of my experience is with Linux, but at some point the FreeBSD philosophy caught my eye: stability, a coherent system, and the fact that many things were done there long ago — well before Linux. Jails, for example — they've been around since 2000. So I decided to build my home server on FreeBSD.
The first question was: how do I run services in isolation? On Linux there's Docker — describe it and run it. I wanted the same here: keep the host clean and skip the hassle.
Why build my own
FreeBSD already has jails — isolation built right into the system. What was missing was a convenient way to use them.
I looked at the existing tools, but none of them clicked. Bastille looked decent, but like most of the others it's a set of scripts around jails. I like the idea of a static binary: download it and run it.
And of course I wanted to build my own (what's life without reinventing the wheel).
That's how dail — daily jail management (github) came about.
Sensible defaults
I carried over what I liked about Docker, with a focus on having to specify almost nothing. If something can be reasonably guessed, dail guesses it.
For example, here's the entire file for zid, my home OIDC server:
FROM 15.0-RELEASE
SERVICE zid
EXPOSE 5555
COPY target/release/zid /usr/local/bin/zid
COPY .env /usr/local/etc/zid/zid.env
And to run it:
doas dail run zid.dail --ip 10.100.0.4 --rebuild
What I didn't have to write:
- the jail name — taken from the file name
- the start command —
SERVICE zidruns/usr/local/bin/zidby default - the user, group, rc.conf —
SERVICEhandles them
An IP is assigned automatically by default, but I keep zid on a fixed one — other services depend on it.
The defaults came from practice. Most of what I run are services, so SERVICE was the first directive.
With it I needed to create a user inside the jail — and every time I named it after the service.
So I made that the default.
How I use it day to day
The usual loop looks like this:
# edit the code
cargo build --release
doas dail run zid.dail --ip 10.100.0.4 --rebuild
doas dail logs zid
Even though the name says daily, I actually don't use dail that often. I log in, build the service, rebuild the jail with one command — and forget about it. After that it just keeps running:
$ dail ls
NAME STATUS JID IP PORTS BASE TYPE UPTIME
--------------------------------------------------------------------------------------------------------
valkey running 2 10.100.0.6/24 6379->6379/tcp 15.0-RELEASE thin 44d 4h
pg running 1 10.100.0.2/24 15.0-RELEASE thin 44d 4h
filest running 6 10.100.0.5/24 8090->8090/tcp, 8090->8090/udp 15.0-RELEASE thin 44d 4h
zid-pg running 7 10.100.0.3/24 15.0-RELEASE thin 44d 4h
cfgy-pg running 9 10.100.0.7/24 15.0-RELEASE thin 44d 4h
cfgy running 22 10.100.0.8/24 8080->8080/tcp 15.0-RELEASE thin 21d 0h
aurora running 72 inherit 4000->4000/tcp 15.1-RELEASE thin 9d 0h
zid running 78 10.100.0.4/24 5555->5555/tcp 15.0-RELEASE thin 5d 7h
domdev running 148 10.100.0.12/24 8300->8080/tcp 15.0-RELEASE thin 4d 0h
Some of these jails haven't been restarted in over 44 days.
I count that as a success. And it shows that same FreeBSD philosophy: set it up, start it, forget it.
What's running
Currently living in jails:
- zid — identity provider (OIDC)
- cfgy — configuration management
- aurora — home media center
- filest — a simple REST file server
- domdev — this blog
- pg, zid-pg, cfgy-pg — a separate postgres for each service
- valkey — cache for the services
Each service in its own jail, the host stays clean.
What's still missing
Not everything is perfect:
- Autostart. After a server reboot, all jails have to be brought up by hand. An autostart mode is planned — it will most likely need some kind of daemon.
- Networking. By default a jail gets an IP alias on
lo0, and dail writes port forwarding rules into PF itself. But outbound access (NAT) and the bridge for VNET are still configured by hand — I'd like dail to handle that too.
Wrapping up
dail is my way to run services on FreeBSD as easily as with Docker.
One binary, a short .dail file and one command to rebuild.
If you've also come to FreeBSD from the world of Linux and Docker — give it a try, feedback is welcome: github.com/zeslava/dail