$RodHat_
Syscall reference · Sandboxing

jail(2)

Create a FreeBSD jail and lock the current process into it (jail_set/jail_attach for more).

Since
FreeBSD 4.0check your kernel →
System
FreeBSD
Man page
jail(2)

Synopsis

#include <sys/param.h>
#include <sys/jail.h>

int jail(struct jail *jail);
int jail_attach(int jid);
int jail_remove(int jid);

#include <sys/uio.h>

int jail_get(struct iovec *iov, u_int niov, int flags);
int jail_set(struct iovec *iov, u_int niov, int flags);

Rod's take

I spent six years saying jails already solved containers and Docker was chroot with a marketing budget. Half right. OCI solved image distribution, and jails never tried. But for one service on hardware you own, a jail on its own ZFS dataset is tighter isolation with less machinery: clone a base snapshot for a copy-on-write root, keep enforce_statfs at 2 so the host's mounts stay invisible, snapshot before every deploy, and roll back in the time it takes ZFS to flip a pointer. If you're shipping to a fleet of strangers, use the container stack. Otherwise, sit with this.

Flags and arguments that matter

NameWhat it does
JAIL_CREATEjail_set(): create a new jail; jid/name must not already exist.
JAIL_UPDATEjail_set(): modify an existing jail. With JAIL_CREATE: create if missing, else modify.
JAIL_ATTACHjail_set(): also attach the calling process, as jail_attach() would.
JAIL_GET_DESC / JAIL_OWN_DESCReturn a jail descriptor fd; an owning descriptor removes the jail when closed.
JAIL_DYINGjail_get(): allow fetching a jail that is being removed (no effect in jail_set()).
enforce_statfsJail parameter limiting what statfs(2) and friends reveal: 0 all mounts, 1 only below the jail root, 2 (default) only the root's own mount.

Errors you will actually hit

errnoMeaning
EPERMNot the super-user, children.max exceeded, or a parameter loosened beyond the current environment.
EEXISTjail_set(): jid or name exists and JAIL_UPDATE is not set.
ENOENTjail_set(): jid or name doesn't exist and JAIL_CREATE is not set.
EINVALWrong API version, bad parameter size/range/name, or neither JAIL_CREATE nor JAIL_UPDATE.
EAGAINNo free jail ID could be found.

Example

# copy-on-write jail root from a clean base snapshot
zfs snapshot zroot/jails/base@clean
zfs clone zroot/jails/base@clean zroot/jails/myapp

# create the jail defined as 'myapp' in /etc/jail.conf
jail -c myapp

# before every deploy
zfs snapshot zroot/jails/myapp@pre-deploy-2026-07-23

# deploy went sideways: stop, roll back, start
jail -r myapp
zfs rollback zroot/jails/myapp@pre-deploy-2026-07-23
jail -c myapp

Trimmed from the full walkthrough in ZFS boot environments + jails will do 90% of what you're using Docker for, and the other 10% is the part you don't actually need.

Linux equivalent

Linux: no single equivalent; namespaces via clone(2)/unshare(2)/setns(2) plus chroot/pivot_root, assembled by container runtimes.

The long version, from the console

Sources

  1. jail(2), FreeBSD
  2. jail(8), FreeBSD
  3. setns(2), man7.org

Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.