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.