Now that we know how to get started with FreeBSD, how to manage users and groups, and how to work with services, we’ll look a bit into Jails today. But before we do this, let’s look at what we have in Linux today when it comes to containerization and virtual machines. Obviously we can manage virtual machines, with either KVM or Xen. This is well known and there are several platforms around those which help in managing whole clusters of virtual machines, e.g. like Proxmox. The more recent trend is containers, which also comes with various options like LXC, Docker, Kubernetes, Rancher, OpenShift, and, and, and. This is somehow all summarized under “Cloud Native” and there is the Cloud Native Computing Foundation (CNCF) which is a kind of umbrella for all those projects. In FreeBSD, there are no containers but there are Jails, which you might see as sitting in the middle of virtual machines and containers, but you can do virtual machines (or virtualization) on FreeBSD (this will be a topic of another post).

Jails build on top of chroot (which is also known on Linux) but do not only limit access to the file system but also other resources such as network or processes. In short, Jails provide isolation from the host system. How much isolation you’ll get depends on the type of Jail you’re using. The FreeBSD handbook provides a good overview of the advantages and disadvantages of the different types of Jails. In short, the types are:

  • Thick – Highest level of isolation
  • Thin – Reduced isolation
  • Service – No file system isolation

The goal of this post is to create a “thin” Jail, because it is very lightweight and we’ll use thin Jails in a later post to provide PostgreSQL instances. Think of this as a simple PostgreSQL as a service.

Before we can create Jails, we need to enable them with “sysrc”:

root@freebsd14:~ $ sysrc jail_enable="YES"
jail_enable: NO -> YES
root@freebsd14:~ $ sysrc jail_parallel_start="YES"
jail_parallel_start: NO -> YES

The second parameter above is not really required for running Jails, but it enables parallel startup of the Jails when the system is booting, what is usually what you want (except you have dependencies between the Jails).

It doesn’t matter where on disk you put the Jails, but it is recommend to create separate file systems for them, e.g.:

root@freebsd14:~ $ zfs create -o mountpoint=/usr/local/jails zroot/jails
root@freebsd14:~ $ zfs create zroot/jails/media
root@freebsd14:~ $ zfs create zroot/jails/templates
root@freebsd14:~ $ zfs create zroot/jails/containers

“/usr/local/jails zroot/containers” will contain the Jails, and “/usr/local/jails/templates” will contain the template for the thin Jail(s) we’ll be creating (usually you would put those on separate disks, and not in the root zpool, of course). “media” is the download of the FreeBSD version we’ll be using. I real live you most probably want a file system per Jail and you’d also put some quotas on them.

The template for the new Jails is actually a ZFS snapshot, but before we can do this we need to prepare it. The first step is to create a new data set (or file system) which will contain the files for the version of FreeBSD we want the Jail to run on:

root@freebsd14:~ $ zfs create -p zroot/jails/templates/14.1-RELEASE

The next step is to download the userland (everything around, but not the kernel itself). This goes into the media data set:

root@freebsd14:~ $ fetch https://download.freebsd.org/ftp/releases/amd64/amd64/14.1-RELEASE/base.txz -o /usr/local/jails/media/14.1-RELEASE-base.txz

Extract that into the data set we’ve created for the template:

root@freebsd14:~ $ tar -xf /usr/local/jails/media/14.1-RELEASE-base.txz -C /usr/local/jails/templates/14.1-RELEASE --unlink
root@freebsd14:~ $ ls -l /usr/local/jails/templates/14.1-RELEASE
total 86
-rw-r--r--   2 root wheel 1011 May 31  2024 .cshrc
-rw-r--r--   2 root wheel  495 May 31  2024 .profile
-r--r--r--   1 root wheel 6109 May 31  2024 COPYRIGHT
drwxr-xr-x   2 root wheel   49 May 31  2024 bin
drwxr-xr-x  14 root wheel   68 May 31  2024 boot
dr-xr-xr-x   2 root wheel    2 May 31  2024 dev
drwxr-xr-x  30 root wheel  103 May 31  2024 etc
drwxr-xr-x   4 root wheel   78 May 31  2024 lib
drwxr-xr-x   3 root wheel    5 May 31  2024 libexec
drwxr-xr-x   2 root wheel    2 May 31  2024 media
drwxr-xr-x   2 root wheel    2 May 31  2024 mnt
drwxr-xr-x   2 root wheel    2 May 31  2024 net
dr-xr-xr-x   2 root wheel    2 May 31  2024 proc
drwxr-xr-x   2 root wheel  150 May 31  2024 rescue
drwxr-x---   2 root wheel    7 May 31  2024 root
drwxr-xr-x   2 root wheel  150 May 31  2024 sbin
lrwxr-xr-x   1 root wheel   11 May 31  2024 sys -> usr/src/sys
drwxrwxrwt   2 root wheel    2 May 31  2024 tmp
drwxr-xr-x  14 root wheel   14 May 31  2024 usr
drwxr-xr-x  24 root wheel   24 May 31  2024 var

What we need in addition are the DNS and timezone files (just copy them from the host system):

root@freebsd14:~ $ cp /etc/resolv.conf /usr/local/jails/templates/14.1-RELEASE/etc/resolv.conf
root@freebsd14:~ $ cp /etc/localtime /usr/local/jails/templates/14.1-RELEASE/etc/localtime

As we want the Jails to start from the latest patch level, let’s patch the template:

oot@freebsd14:~ $ freebsd-update -b /usr/local/jails/templates/14.1-RELEASE/ fetch install
src component not installed, skipped
Looking up update.FreeBSD.org mirrors... 3 mirrors found.
Fetching metadata signature for 14.1-RELEASE from update2.freebsd.org... done.
Fetching metadata index... done.
Inspecting system... done.
Preparing to download files... done.
The following files will be updated as part of updating to
14.1-RELEASE-p6:
/bin/freebsd-version
/lib/libc++.so.1
...
Scanning /usr/local/jails/templates/14.1-RELEASE/usr/share/certs/trusted for certificates...
 done.

Now we have the template ready and create a read only ZFS snapshot out of it:

root@freebsd14:~ $ zfs snapshot zroot/jails/templates/14.1-RELEASE@base
root@freebsd14:~ $ zfs list -t snapshot
NAME                                       USED  AVAIL  REFER  MOUNTPOINT
zroot/ROOT/default@2024-11-26-10:56:43-0   353M      -  1.46G  -
zroot/jails/templates/14.1-RELEASE@base      0B      -   451M  -

All that needs to be done to create a new Jail is to clone this snapshot, e.g.:

root@freebsd14:~ $ zfs clone zroot/jails/templates/14.1-RELEASE@base zroot/jails/containers/thinjail1
root@freebsd14:~ $ ls -l /usr/local/jails/containers/thinjail1/
total 86
-rw-r--r--   2 root wheel 1011 May 31  2024 .cshrc
-rw-r--r--   2 root wheel  495 May 31  2024 .profile
-r--r--r--   1 root wheel 6109 May 31  2024 COPYRIGHT
drwxr-xr-x   2 root wheel   49 Dec  2 13:40 bin
drwxr-xr-x  15 root wheel   69 Dec  2 13:40 boot
dr-xr-xr-x   2 root wheel    2 May 31  2024 dev
drwxr-xr-x  30 root wheel  105 Dec  2 13:40 etc
drwxr-xr-x   4 root wheel   78 Dec  2 13:40 lib
drwxr-xr-x   3 root wheel    5 May 31  2024 libexec
drwxr-xr-x   2 root wheel    2 May 31  2024 media
drwxr-xr-x   2 root wheel    2 May 31  2024 mnt
drwxr-xr-x   2 root wheel    2 May 31  2024 net
dr-xr-xr-x   2 root wheel    2 May 31  2024 proc
drwxr-xr-x   2 root wheel  150 Dec  2 13:40 rescue
drwxr-x---   2 root wheel    7 May 31  2024 root
drwxr-xr-x   2 root wheel  150 Dec  2 13:40 sbin
lrwxr-xr-x   1 root wheel   11 May 31  2024 sys -> usr/src/sys
drwxrwxrwt   2 root wheel    2 May 31  2024 tmp
drwxr-xr-x  14 root wheel   14 May 31  2024 usr
drwxr-xr-x  24 root wheel   24 May 31  2024 var

Both, the template and the new Jail just consume 451MB of disk space:

root@freebsd14:~ $ df -h | grep jails
zroot/jails                            23G     96K     23G     0%    /usr/local/jails
zroot/jails/media                      24G    199M     23G     1%    /usr/local/jails/media
zroot/jails/templates                  23G     96K     23G     0%    /usr/local/jails/templates
zroot/jails/containers                 23G     96K     23G     0%    /usr/local/jails/containers
zroot/jails/templates/14.1-RELEASE     24G    451M     23G     2%    /usr/local/jails/templates/14.1-RELEASE
zroot/jails/containers/thinjail1       24G    451M     23G     2%    /usr/local/jails/containers/thinjail1

The final step is the configuration of the Jail, but this is also pretty straight forward in simple cases. The configuration for our Jail looks like this:

root@freebsd14:~ $ cat /etc/jail.conf.d/thinjail1.conf 
thinjail1 {
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;

  # HOSTNAME/PATH
  host.hostname = "${name}";
  path = "/usr/local/jails/containers/${name}";

  # NETWORK
  ip4.addr = 192.168.122.160;
  interface = vtnet0;
}

That’s it. The most important point for now is the network configuration: What we want is a new virtual IP address on top of our “vtnet0” interface. This is the IP address clients will use to connect to this Jail. Of course the Jail needs a name, a location and we want the normal startup and shutdown to happen with rc (we’ll look at the other parameters in a later post).

Starting up the Jail:

root@freebsd14:~ $ service jail start thinjail1
Starting jails: thinjail1.
root@freebsd14:~ $ service jail status thinjail1
 JID             IP Address      Hostname                      Path
 thinjail1       192.168.122.160 thinjail1                     /usr/local/jails/containers/thinjail1

While it is recommended to manage a Jail from the host system, you can also jump into the Jail:

root@freebsd14:~ $ jexec -u root thinjail1
root@thinjail1:/ $ ifconfig
vtnet0: flags=1008843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,LOWER_UP> metric 0 mtu 1500
      options=4c07bb<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,JUMBO_MTU,VLAN_HWCSUM,TSO4,TSO6,LRO,VLAN_HWTSO,LINKSTATE,TXCSUM_IPV6>
        ether 52:54:00:af:79:80
        inet 192.168.122.160 netmask 0xffffffff broadcast 192.168.122.160
        media: Ethernet autoselect (10Gbase-T <full-duplex>)
        status: active
lo0: flags=1008049<UP,LOOPBACK,RUNNING,MULTICAST,LOWER_UP> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        groups: lo
root@thinjail1:/ $ freebsd-version ; uname -a
14.1-RELEASE-p6
FreeBSD thinjail1 14.1-RELEASE-p5 FreeBSD 14.1-RELEASE-p5 GENERIC amd64

Pretty easy to setup and really lightweight. Put a bit of automation around this and you should have a new Jail up and running in a few seconds.

In the next post we’ll build on this to create a simple PostgreSQL as a service platform.