SSD Nodes Learn 🎉 VPS from $5.50/mo
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-13

FreeBSD jails vs Docker containers: which one fit work?

FreeBSD jail dey isolate complete userland, while Docker dey run process from layered registry image. See how software, state, networking and limits differ.

FreeBSD jails vs Docker containers, for one paragraph

FreeBSD jails and Docker containers dey solve the same problem for two different forms. Both dey run isolated userlands for one shared kernel, so neither one be virtual machine. The difference na wetin dey inside. Docker container dey run one process from layered image wey you pull from registry. Jail dey run complete FreeBSD userland: e get im own /etc, im own rc startup scripts, im own pkg database, and as many processes as you want. Almost every other difference for this page come from that one thing.

SSD Nodes no dey offer FreeBSD images. You no fit rent FreeBSD server for this platform, and nothing for down here be install guide for machine wey you fit buy here. This na comparison of two isolation models, written so you fit know which one workload really need, and so you fit read FreeBSD team's setup without guessing.

Wetin jail really be

Jails come for FreeBSD 4.0 for March 2000. This make dem older than cgroups and about one decade older than Docker. The mechanism na one kernel call. jail(8) takes directory tree and starts processes inside am with jail ID attached. Then kernel go refuse one fixed set of operations for any process wey carry that ID. Process wey dey inside jail no fit see processes outside the jail. E no fit mount or unmount filesystems, load kernel modules, or bind to network addresses wey dem no give the jail. No separate namespace type dey to learn, and no per-feature opt-in dey. The restrictions come as one unit, adjusted by parameters for the jail config.

For the host, jls lists jails wey dey run, while jexec web sh puts you inside shell for the jail wey dem name web.

You build jail by putting FreeBSD userland inside directory. The base system go do this for you:

sudo bsdinstall jail /usr/local/jails/containers/web

This one fetches the base distribution set for your release and runs the normal post-install steps. So you go set root password and choose timezone exactly as you go do for new server. The result na FreeBSD installation wey dey inside folder. Then you describe am for /etc/jail.conf:

web {
  host.hostname = "web.example.internal";
  path = "/usr/local/jails/containers/web";
  ip4.addr = "10.0.0.10";
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  mount.devfs;
}

Start am, then check am:

sudo service jail start web
jls

jls suppose now list web with JID, hostname, and IP address. If the jail no appear, run sudo jail -c web directly. E applies the same configuration for foreground and prints the parameter wey e no fit accept, instead of leaving the failure inside service output.

The line wey worth reading twice na exec.start = "/bin/sh /etc/rc". Starting jail runs FreeBSD normal boot script inside am. So the jail go start every service wey enable inside its own /etc/rc.conf. Docker container no get equivalent step, because e runs the image entrypoint process and stops when that process stops.

How you dey get software: images and registries against userland wey you fill

Na this difference you go notice from day one.

With Docker, you name software and receive am. docker pull nginx dey fetch layered, content-addressed image wey another person build and test, while docker compose up -d start am with the volumes and network wey connect to am. The registry na the main product. Most value for Docker workflow come from thousands of projects wey publish working image. Na this one make running Docker for VPS be small task instead of full project.

FreeBSD no ship with default public registry for jail images. You create empty userland and install software inside am, the same way you go set up bare server. E require more typing. But e dey more transparent, because wetin dey run for the jail na wetin pkg put there, from the same package set wey the host dey use.

Tooling make the process short. BastilleBSD na the common jail manager, and e dey available as package:

sudo pkg install bastille
sudo sysrc bastille_enable=YES
sudo bastille setup
sudo bastille bootstrap 15.1-RELEASE

bastille setup go configure networking, storage, and firewall for you. bastille bootstrap go download release once, and every jail wey you create later go reuse am. FreeBSD 15.1 na the current production release, wey come out for June 2026; replace am with the release wey you dey run.

To create jail na one command, and to fill am na one more:

sudo bastille create web 15.1-RELEASE 10.17.89.10/24
sudo bastille pkg web install nginx
sudo bastille service web nginx start
sudo bastille console web

bastille console web go give you login shell inside the jail, while bastille list go show wetin dey exist for the host. To repeat build, Bastille templates dey keep the steps for file and apply dem to jail. Na this dey closest to Dockerfile for this environment. Dem replay template for every jail. Nothing dey arrive prebuilt.

So the honest summary short. Docker dey give you builds wey other people make. Jails dey give you installs wey you make yourself. If the software for your shortlist dey ship only as container image and nothing else, that one don answer the question before any other factor fit influence am.

State and upgrades: the part wey ZFS dey change

Docker dey split state on purpose. Container filesystem fit throw away, your data dey live for named volume or bind mount, and upgrade na docker compose pull followed by docker compose up -d. Dem dey replace the container, and anything wey you no put for volume go disappear. This na feature when you follow the rule, but e go cause data loss when you forget am. Na why the choice between bind mounts and named volumes important well-well for Compose stack.

Jail no dey split state, and na ZFS make this possible. The whole jail na one dataset:

sudo zfs snapshot zroot/jails/containers/web@pre-upgrade
sudo pkg -j web upgrade
sudo zfs rollback zroot/jails/containers/web@pre-upgrade

Use zfs list check the real dataset name before you run this. The path above na the layout wey the handbook dey use. The snapshot dey take about one second, and e almost no use disk space until the jail contents change. If upgrade spoil the service, rollback go return the whole userland to the earlier state. This include package database and config files wey you edit by hand for 2am. Docker no get built-in equivalent because its model assume say you no need this kind snapshot.

zfs clone na the other half. Clone of snapshot na new writable jail wey dey share unchanged blocks with the parent. So, staging copy of 3 GB jail almost no cost disk space until you start to change am. Na so FreeBSD admin dey build jail wey be "same as production" to practise upgrade.

Base system upgrade separate from packages. For jail wey get its own copy of the userland:

sudo freebsd-update -b /usr/local/jails/containers/web fetch install

Thin jails no dey repeat this work. Dem mount one shared read-only base through nullfs and give each jail small writable layer of its own. So you patch the base once, and every jail go see the result. Bastille dey create thin jails by default.

Networking: published ports vs addressing decision

Docker dey decide networking for you, and e ask you to publish only the exceptions. Containers land for a bridge, dem fit reach each other by service name for a user-defined network, and -p 8080:80 exposes one of dem to the host. Docker writes im own packet filter rules to make this work. Na this same method make published container port fit pass straight through ufw.

Jail make you choose the model from the beginning, and two options dey.

Shared IP. ip4.addr = "10.0.0.10" adds that address to an existing host interface and restricts the jail to am. The jail no get im own network stack, so e no fit run im own firewall. E also no fit really bind to every address. If a jailed socket ask for 0.0.0.0, kernel go rewrite am to the jail own address. Two jails no fit both listen on port 80 for the same address. So you give each jail one address, or put reverse proxy for front.

VNET. Add vnet; to the jail and e go get full network stack: im own interfaces, im own routing table, and im own firewall rules. You connect am to the host with an epair, wey be virtual cable with one end for each side, then put the host end for a bridge. This one resemble wetin Docker dey provide pass, and na the mode behind Bastille's -V and -B jail types.

To forward host port enter a jail, use a pf redirect rule. Bastille wrap am:

sudo bastille rdr web tcp 80 80

No EXPOSE dey here and no automatic publishing dey happen. Nothing fit reach a jail unless im address or a redirect rule allow am. This one take more time to set up, but e make the firewall quieter.

Resource limits: cgroups vs rctl

Docker dey limit container with cgroups, and the limits dey where dem define the container: --memory=1g --cpus=1.5 for command line, or the matching keys for Compose file. If you already dey keep your stack for Docker Compose file for VPS, the limit dey beside the service wey e apply to and e dey travel with am inside git.

FreeBSD dey use rctl, and na subsystem wey you need switch on. Resource accounting dey off by default because e dey add small cost to every allocation. Add the tunable to /boot/loader.conf and reboot:

kern.racct.enable=1

Then set rule and monitor am:

sudo rctl -a jail:web:vmemoryuse:deny=1g
rctl -hu jail:web

rctl -hu jail:web dey print the jail current usage with human-readable units, so you fit see how close e dey to the limit before anything fail. The deny action dey make allocation wey pass the limit fail inside the jail, so you go see the application own allocation error instead of kill message for the host.

Rules wey you add with rctl -a go disappear for the next reboot. FreeBSD rctl service dey reload dem from /etc/rctl.conf, so write the rule inside that file and enable the service:

sudo sysrc rctl_enable=YES

Na for here Docker dey clearly easier to use. Limit for Compose file dey reviewed together with the service wey e dey constrain. An rctl rule na line for separate file wey name a jail wey dem define for another place.

When answer na virtual machine: bhyve

Jail dey share host kernel, so some things no dey possible for am. E no fit run different kernel version, load kernel module, or run Linux binaries the way Linux container dey do am. FreeBSD get Linux compatibility layer, wey dem call linuxulator, but e only implement some Linux system calls. E no be general solution for any kind Linux image.

bhyve na FreeBSD hypervisor, and na the correct tool when you need real machine boundary: different operating system, different kernel, or tenant wey you no wan make e share kernel. You go use more memory because dem reserve am instead of sharing am, and you go need patch second kernel. Na the same decision you dey make for Linux between containers and full virtual machines. Na this decision go determine whether you need VPS wey support nested virtualization underneath.

Ecosystem wey be the honest reason most teams dey use Docker

Everything wey we don talk so far na about the model. Wetin dey decide the choice for most teams na how big the world around each option be.

Docker bring Docker Hub and GHCR, docker compose, Kubernetes when one box no dey enough again, CI runners wey container support don already wire inside, and one-command quickstart for almost every project README. Jails bring FreeBSD ports tree, wey big and dem dey maintain am well, plus much smaller number of application bundles wey ready to run. When project publish container image and nothing else, the FreeBSD way na to read the documentation and assemble all the parts by yourself.

Jails get their place for the other side of this trade-off. You go want use dem when you already dey run ZFS and you value snapshot and rollback for complete service, when your services na FreeBSD native, when you want complete userland for each tenant instead of just one process, or when you want make kernel, packet filter, filesystem, and documentation dey maintained together as one system. Na this last point people mean when dem call FreeBSD coherent, and e get more explanation for the wider comparison of Linux and FreeBSD as server platforms and for wetin FreeBSD 15 change for server use.

One final judgement. If your team already sabi Docker, the cost of moving dey real and the benefit must get specific reason. No switch because of isolation quality; the two models dey close enough, so your configuration matter more. Switch because you want ZFS-backed rollback for complete services, or because you dey use FreeBSD already.

FAQ

I fit run Docker images for FreeBSD?

No be Linux images, and no be supported method. FreeBSD get OCI container support: sudo pkg install -y podman-suite go install Podman, wey dey run containers through ocijail, a runtime wey dey create real jails underneath. E need fdescfs mounted for /dev/fd for the container monitor, and pf for container NAT (network address translation). OCI images wey native to FreeBSD dey work best. Linux images still need the Linux compatibility layer, and as of August 2026, dem still describe the FreeBSD Podman port as experimental. If your deployment na stack of Linux images, run am for Linux.

FreeBSD jails dey more secure pass Docker containers?

Both dey share one host kernel, so kernel bug fit affect both, and neither one be boundary wey you go choose for genuinely untrusted code. The difference dey for how dem start. Jail dey start with broad set of operations refused, then you re-enable dem one parameter at a time. Docker container dey start as root inside set of namespaces, with some capabilities dropped, and extra hardening na opt-in. For real use, configuration decide pass the model: jail wey get allow.mount and allow.raw_sockets enabled no safer pass container wey dem configure carefully.

How I go back up a jail?

Take snapshot of the dataset and send am. sudo zfs snapshot zroot/jails/containers/web@backup, then zfs send that snapshot go another pool or put am inside file wey you copy comot from the box. Because jail keep the whole userland for one dataset, the snapshot capture the installed packages and data for one consistent point, together with every config file wey you edit by hand. This opposite of Docker habit, where you back up the named volumes and the Compose file, then rebuild everything else from the image.

I need BastilleBSD, or the base system dey enough?

The base system dey enough, and na better place to start. jail.conf, jls, jexec and service jail start cover the whole model, and once you sabi dem, you fit read any FreeBSD host without first learning that host tooling. Bastille na convenience layer on top of am: e bootstrap releases, create thin jails, apply templates and write pf redirect rules for you. Learn the base commands first, then add Bastille when the number of jails make the typing too tedious.