{"id":5960,"date":"2026-07-12T07:21:50","date_gmt":"2026-07-12T11:21:50","guid":{"rendered":"https:\/\/dft.wiki\/?p=5960"},"modified":"2026-07-19T13:31:26","modified_gmt":"2026-07-19T17:31:26","slug":"containerization-with-jails-on-freebsd","status":"publish","type":"post","link":"https:\/\/dft.wiki\/?p=5960","title":{"rendered":"FreeBSD Jails and Container Management Tools"},"content":{"rendered":"<p><strong>FreeBSD<\/strong> [<a href=\"https:\/\/www.freebsd.org\/\">Link<\/a>] and the BSD family of UNIX systems are known for their exceptional security, reliability, and performance (especially in networking).<\/p>\n<p>For greater context, check out the other posts: <strong>Virtualization with Bhyve on FreeBSD<\/strong> [<a href=\"https:\/\/dft.wiki\/?p=5418\">Link<\/a>] and <strong>Introduction to UNIX for Linux Users<\/strong> [<a href=\"https:\/\/dft.wiki\/?p=3395\">Link<\/a>]<\/p>\n<p><strong>Jails<\/strong> and containers in FreeBSD are not virtual machines, even though they look and feel like one [<a href=\"https:\/\/docs.freebsd.org\/en\/books\/handbook\/jails\/\">Link<\/a>]. A jail is nothing more than an isolated process confined to a limited environment using the chroot concept.<\/p>\n<p>Compared to Linux Containers and Application Containers, <strong>Jails<\/strong> first appeared in the year 2000, while <strong>LXC<\/strong> and <strong>Docker<\/strong> followed in 2008 and 2013, respectively.<\/p>\n<hr \/>\n<p><strong>MANAGEMENT TOOLS<\/strong><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-6019\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2026\/07\/Screenshot_2026-07-19_12-55-39.png\" alt=\"\" width=\"337\" height=\"320\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2026\/07\/Screenshot_2026-07-19_12-55-39.png 337w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2026\/07\/Screenshot_2026-07-19_12-55-39-300x285.png 300w\" sizes=\"auto, (max-width: 337px) 100vw, 337px\" \/><\/p>\n<p>Jails can be managed with different tools and methods.<\/p>\n<ul>\n<li><strong>Native jails<\/strong>\n<ul>\n<li>The native low-level tools that rely on manually editing <code>jail.conf<\/code> with a declarative format.<\/li>\n<\/ul>\n<\/li>\n<li><strong>BastilleBSD<\/strong> [<a href=\"https:\/\/github.com\/BastilleBSD\/bastille\">Link<\/a>]\n<ul>\n<li>A higher-level tool known for being the simplest and most widely recommended choice.<\/li>\n<\/ul>\n<\/li>\n<li><strong>AppJail<\/strong> [<a href=\"https:\/\/github.com\/DtxdF\/AppJail\/\">Link<\/a>]\n<ul>\n<li>The closest equivalent to a modern container platform, with reusable application definitions.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Pot<\/strong> [<a href=\"https:\/\/codeberg.org\/bsdpot\/pot\">Link<\/a>]\n<ul>\n<li>Feels remarkably like a <strong>Dockerfile<\/strong> when creating a container build definition called a <strong>Potfile<\/strong>.<\/li>\n<\/ul>\n<\/li>\n<li><strong>iocage<\/strong>\n<ul>\n<li>Worth mentioning but <span style=\"text-decoration: underline;\">out of scope of this post<\/span>, as it is maintained by the TrueNAS team, which no longer runs on FreeBSD. Development has stalled and most users have moved on.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr \/>\n<p><strong>FETCH OFFICIAL RELEASE TARBALLS<\/strong><\/p>\n<p>While some tools can automatically fetch and manage release base images, here is how to do it manually, as required for native jails.<\/p>\n<p>Create a dedicated ZFS dataset for jails and mount it.<\/p>\n<pre>zfs create zroot\/jails\r\nzfs set mountpoint=\/usr\/jails zroot\/jails\r\nzfs create zroot\/jails\/releases\r\nzfs create zroot\/jails\/releases\/15.1-RELEASE\r\nzfs create zroot\/jails\/containers<\/pre>\n<p>Then fetch the base image.<\/p>\n<pre>mkdir -p \/usr\/jails\/releases\/15.1-RELEASE\r\ncd \/usr\/jails\/releases\/15.1-RELEASE\r\nfetch https:\/\/download.freebsd.org\/releases\/amd64\/15.1-RELEASE\/base.txz\r\ntar -xpf base.txz &amp;&amp; rm base.txz\r\necho \"nameserver 1.1.1.1\" &gt; \/usr\/jails\/releases\/15.1-RELEASE\/etc\/resolv.conf<\/pre>\n<p>Create a snapshot and clone it as many times as needed (never use the template directly).<\/p>\n<pre>zfs snapshot zroot\/jails\/releases\/15.1-RELEASE@base\r\nzfs clone zroot\/jails\/releases\/15.1-RELEASE@base zroot\/jails\/containers\/proxy\r\nzfs clone zroot\/jails\/releases\/15.1-RELEASE@base zroot\/jails\/containers\/app\r\nzfs clone zroot\/jails\/releases\/15.1-RELEASE@base zroot\/jails\/containers\/db<\/pre>\n<hr \/>\n<p><strong>NATIVE JAILS<\/strong><\/p>\n<p>Unlike containers on Linux, Jails are part of the FreeBSD system and require no additional packages. They just need to be enabled at boot.<\/p>\n<p>Check if it is enabled.<\/p>\n<pre>sysrc jail_enable<\/pre>\n<p>If not, enable it.<\/p>\n<pre>sysrc jail_enable=YES<\/pre>\n<p>Service basic operations.<\/p>\n<pre>service jail start\r\nservice jail restart\r\nservice jail stop\r\nservice jail status<\/pre>\n<p>Configure the containers.<\/p>\n<pre>nano \/etc\/jail.conf<\/pre>\n<p>Basic example.<\/p>\n<pre>path = \"\/usr\/jails\/containers\/$name\";\r\nmount.devfs;\r\n\r\nexec.start = \"\/bin\/sh \/etc\/rc\";\r\nexec.stop  = \"\/bin\/sh \/etc\/rc.shutdown\";\r\n\r\nproxy {\r\n    host.hostname = proxy.local;\r\n    interface = \"vtnet0\";\r\n    ip4.addr = 10.1.1.10\/24;\r\n}\r\n\r\napp {\r\n    host.hostname = app.local;\r\n    interface = \"vtnet0\";\r\n    ip4.addr = 10.1.1.11\/24;\r\n}\r\n\r\ndb {\r\n    host.hostname = db.local;\r\n    interface = \"vtnet0\";\r\n    ip4.addr = 10.1.1.12\/24;\r\n}<\/pre>\n<p><strong>Note:<\/strong> the root file system must be present at the indicated path.<\/p>\n<p>Managing workloads.<\/p>\n<pre>service jail start\r\nservice jail status proxy\r\nservice jail stop db<\/pre>\n<p>Basic management commands.<\/p>\n<ul>\n<li><code>jls<\/code>\n<ul>\n<li>List configured containers.<\/li>\n<\/ul>\n<\/li>\n<li><code>jexec proxy ls<\/code>\n<ul>\n<li>Execute a command inside a jail, or get a shell on it.<\/li>\n<\/ul>\n<\/li>\n<li><code>freebsd-update -b \/usr\/jails\/releases\/15.1-RELEASE fetch install<\/code>\n<ul>\n<li>Updates the template, so all new clones benefit from it.<\/li>\n<li>Can also be applied to a stopped container, or to a running one that is then restarted to load the new files.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr \/>\n<p><strong>BASTILLE<\/strong><\/p>\n<p>Install and configure Bastille.<\/p>\n<pre>pkg install -y bastille\r\nzfs create zroot\/bastille\r\nnano \/usr\/local\/etc\/bastille\/bastille.conf<\/pre>\n<pre>bastille_prefix=\"\/usr\/local\/bastille\"\r\nbastille_zfs_enable=\"YES\"\r\nbastille_zfs_zpool=\"zroot\"<\/pre>\n<p>Pull the base image of a specific release.<\/p>\n<pre>bastille bootstrap 15.1-RELEASE\r\nbastille list releases<\/pre>\n<p>Deploy a jail from a defined release (or template).<\/p>\n<pre>bastille create web 15.1-RELEASE 10.1.1.20\r\nbastille list<\/pre>\n<p>For reference, <code>bastille<\/code> stores jail configurations at <code>\/usr\/local\/bastille\/jails\/&lt;JAIL_NAME&gt;\/<\/code>, where:<\/p>\n<ul>\n<li><code>root\/<\/code>\n<ul>\n<li>The base file system of the jail.<\/li>\n<\/ul>\n<\/li>\n<li><code>jail.conf<\/code>\n<ul>\n<li>The native jail configuration generated by Bastille.<\/li>\n<\/ul>\n<\/li>\n<li><code>fstab<\/code>\n<ul>\n<li>Mounting and mapping configurations.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille.conf<\/code>\n<ul>\n<li>Bastille-specific configuration (e.g., ZFS, networking).<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Templates (releases) are stored in <code>\/usr\/local\/bastille\/releases\/<\/code>.<\/p>\n<p>Basic commands.<\/p>\n<ul>\n<li><code>bastille bootstrap<\/code>\n<ul>\n<li>Pull images.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille create<\/code>\n<ul>\n<li>Deploy a jail.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille list<\/code>\n<ul>\n<li>List jails.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille start<\/code>\n<ul>\n<li>Start a jail.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille stop<\/code>\n<ul>\n<li>Stop a jail.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille restart<\/code>\n<ul>\n<li>Restart a jail.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille console<\/code>\n<ul>\n<li>Get a shell in a jail.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille cmd<\/code>\n<ul>\n<li>Execute a command inside a jail.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille pkg<\/code>\n<ul>\n<li>Manage packages in a jail.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille cp<\/code>\n<ul>\n<li>Copy files between host and guest.<\/li>\n<\/ul>\n<\/li>\n<li><code>bastille destroy<\/code>\n<ul>\n<li>Delete a jail.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr \/>\n<p><strong>APPJAIL<\/strong><\/p>\n<pre>pkg install -y appjail\r\nsysrc appjail_enable=YES\r\nnano \/usr\/local\/etc\/appjail\/appjail.conf<\/pre>\n<p>Configure.<\/p>\n<pre>EXT_IF=vtnet0\r\nON_IF=vtnet0\r\nFREEBSD_VERSION=15.1-RELEASE\r\nFREEBSD_ARCH=amd64\r\nIMAGE_ARCH=amd64\r\nSHORTEN_DOMAIN_NAMES=1<\/pre>\n<p>Fetch the FreeBSD release.<\/p>\n<pre>appjail fetch\r\nappjail fetch list<\/pre>\n<p>Deploy a jail.<\/p>\n<pre>appjail quick web\r\nappjail jail list<\/pre>\n<p>Manage.<\/p>\n<pre>appjail start web\r\nappjail login web\r\nappjail cmd web ls\r\nappjail stop web\r\nappjail jail destroy -f web<\/pre>\n<p>Create a <strong>Makejail<\/strong> file (the closest equivalent to a <strong>Dockerfile<\/strong>).<\/p>\n<pre>PKG nginx\r\nSYSRC nginx_enable=YES\r\nSERVICE nginx start<\/pre>\n<p>Build the jail.<\/p>\n<pre>appjail makejail -f Makejail -j nginx<\/pre>\n<p>There is also <strong>Director<\/strong>, which works with AppJail to provide something closer to <strong>Docker Compose<\/strong>, and <strong>Overlord<\/strong>, which is the closest equivalent to an orchestrator like <strong>Kubernetes<\/strong> or <strong>Docker Swarm<\/strong>.<\/p>\n<hr \/>\n<p><strong>POT<\/strong><\/p>\n<p>Install Pot.<\/p>\n<pre>pkg install -y pot\r\npot version<\/pre>\n<p>Make sure Jails is enabled.<\/p>\n<pre>zfs list\r\nsysrc jail_enable=YES<\/pre>\n<p>Set the interface Pot shall use with jails.<\/p>\n<pre>ifconfig\r\nnano \/usr\/local\/etc\/pot\/pot.conf<\/pre>\n<pre>POT_EXTIF=<strong>vtnet0<\/strong><\/pre>\n<p>Initialize Pot.<\/p>\n<pre>pot init\r\npot show<\/pre>\n<p>Fetch the base image of your choice.<\/p>\n<pre>pot fetch -r 15.1\r\npot list -a<\/pre>\n<p>Deploy a jail from the fetched base image.<\/p>\n<pre>pot create -p <strong>web<\/strong> -t single -b <strong>15.1<\/strong> -N alias -i <strong>10.7.1.20\r\n<\/strong>pot info -p web<\/pre>\n<p>Start and inspect the new jail.<\/p>\n<pre>pot start -p <strong>web<\/strong>\r\npot show -r\r\npot list -v\r\npot top -p <strong>web<\/strong><\/pre>\n<p>Turn it into a template.<\/p>\n<pre>pot stop -p web\r\npot snapshot -p web\r\nzfs list -t snapshot<\/pre>\n<p>Create a clone from the template (parent pot).<\/p>\n<pre>pot clone -p <strong>web-clone<\/strong> -P <strong>web<\/strong> -i <strong>10.7.1.21\r\n<\/strong>pot start -p <strong>web-clone\r\n<\/strong>pot <strong>term<\/strong> -p web-clone<\/pre>\n<p>Create a <strong>Flavors (Potfiles)<\/strong> file (the closest equivalent to a <strong>Dockerfile<\/strong>).<\/p>\n<pre>pkg nginx\r\nsysrc nginx_enable=YES\r\ncopy nginx.conf \/usr\/local\/etc\/nginx\/nginx.conf<\/pre>\n<p><strong>Potluck<\/strong> [<a href=\"https:\/\/github.com\/bsdpot\/potluck\">Link<\/a>] is a repository of Flavors, similar to <strong>Docker Hub<\/strong>. See <strong>Nomad<\/strong> [<a href=\"https:\/\/github.com\/hashicorp\/nomad\">Link<\/a>] for container orchestration.<\/p>\n<hr \/>\n<p><strong>BASIC COMPARISON<\/strong><\/p>\n<p>The following table lists some of the most basic features expected in container environments.<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Feature<\/th>\n<th><strong>Bastille<\/strong><\/th>\n<th><strong>AppJail<\/strong><\/th>\n<th><strong>Pot<\/strong><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>ZFS<\/strong><\/td>\n<td>Supported<\/td>\n<td>Supported<\/td>\n<td>Required<\/td>\n<\/tr>\n<tr>\n<td><strong>Automation<\/strong><\/td>\n<td>Templates<\/td>\n<td>Makejail, Initscripts, Images<\/td>\n<td>Flavours, Images<\/td>\n<\/tr>\n<tr>\n<td><strong>Jail types<\/strong><\/td>\n<td>clone, thin, thick, vnet, bridged vnet, Linux, empty<\/td>\n<td>clone, copy, tiny, thin, thick, empty, linux+debootstrap<\/td>\n<td>thick<\/td>\n<\/tr>\n<tr>\n<td><strong>VNET<\/strong><\/td>\n<td>Supported<\/td>\n<td>Supported<\/td>\n<td>Supported<\/td>\n<\/tr>\n<tr>\n<td><strong>Resource control<\/strong><\/td>\n<td>Supported<\/td>\n<td>Supported<\/td>\n<td>Limited to CPU and memory<\/td>\n<\/tr>\n<tr>\n<td><strong>IPv6 support<\/strong><\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td><strong>Linux containers<\/strong><\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>OCI support<\/strong><\/td>\n<td><\/td>\n<td>Yes<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Network management<\/strong><\/td>\n<td><\/td>\n<td>Virtual networks<\/td>\n<td>Subnet, requires sysutils\/potnet<\/td>\n<\/tr>\n<tr>\n<td><strong>Healthcheckers<\/strong><\/td>\n<td><\/td>\n<td>Yes<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Log management<\/strong><\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Parse jail.conf for syntax errors<\/strong><\/td>\n<td><\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td><strong>Volume management<\/strong><\/td>\n<td>Basic<\/td>\n<td>Yes<\/td>\n<td>Basic, only with the fscomp feature<\/td>\n<\/tr>\n<tr>\n<td><strong>Parallel startup<\/strong><\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Bridges<\/strong><\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td><strong>VLAN<\/strong><\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><strong>Startup order control<\/strong><\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td><strong>X11 support<\/strong><\/td>\n<td><\/td>\n<td>Yes<\/td>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<hr \/>\n<p><strong>REFLECTIONS<\/strong><\/p>\n<p>I have only scratched the surface here, particularly with the more modern AppJail and Pot, which bring contemporary concepts to container management.<\/p>\n<p>The way I see FreeBSD Jails, they are closer to Linux Containers (LXC\/Incus) in the sense that they are full systems (pets), not ephemeral workloads (cattle) as treated by Kubernetes and Docker.<\/p>\n<p>That does not mean Jails cannot work as application-specific containers, but that was not their original intent. I am curious to keep exploring what is possible in that direction.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>FreeBSD [Link] and the BSD family of UNIX systems are known for their exceptional security, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-5960","post","type-post","status-publish","format-standard","hentry","category-linux"],"_links":{"self":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/5960","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5960"}],"version-history":[{"count":17,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/5960\/revisions"}],"predecessor-version":[{"id":6020,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/5960\/revisions\/6020"}],"wp:attachment":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5960"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5960"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5960"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}