Containerization in FreeBSD with Jails

2021-04-06

Preface

The FreeBSD Jails technology is the predecor of the modern containerazitaion technologies such as Docker, Podman, LXC, etc... Despite it’s age, it offers battle-tested, rock-solid, production-ready "container" abstraction. This guide aims to get you started with FreeBSD Jails.

Before trying out stuff, please head over FreeBSD's Jail chapter from the amazing Handbook.


Requirements

  1. FreeBSD Host
  2. ~20-30 minutes of free time

Building a jail

First, we need to have a host running FreeBSD. You can provision a FreeBSD Host on AWS, or you can grab an ISO from here and install it in VirtualBox, Qemu, or similar virtualization technology. I have one instance on QEMU+KVM.

Once we have an instance with FreeBSD running, let's get the binaries from the Internet to build a jail:

$: bsdinstall jail /here/is/the/jail

Next step is to configure the host. First. let's enable the jails technology in the /etc/rc.conf by adding the following line:

jail_enable="YES"

We need to configure the jail now. The configuration is done by describing jail properties in the /etc/jail.conf file. You can check the following man page for full description of the properties: jail(8).

A simple jail configuration might look like:

www { host.hostname = firstjail.local; # Hostname ip4.addr = 10.10.1.5; # IP address of the jail path = "/usr/jails/www"; # Path to the jail mount.devfs; # Mount devfs inside the jail exec.start = "/bin/sh /etc/rc"; # Start command exec.stop = "/bin/sh /etc/rc.shutdown"; # Stop command exec.clean; allow.raw_sockets; }

Now we can start the jail:

$: service jail start www
Let's see if it's running:
$: jls JID IP Address Hostname Path 4 10.10.1.5 firstjail.local /usr/jails/www

Networking

In order to have a proper networking between the jails and the host, few configurations need to be made. First we need to create a clone of the loopback interface, so we can create a "virtual" network. Let's add the following lines to the /etc/rc.conf too:

cloned_interface="lo1" ipv4_addrs_lo1="10.10.1.1-9/24"

Now, we can have 9 jails, whith own IP address each, i.e. 10.10.1.5.

As a last step of our configuration, we need to enable the pf firewall, so we can allow network traffic, for example on port 80. First, let's enable the pf firewall, by adding the following line in the /etc/rc.conf:

pf_enable="YES"

Then let's add the following firewall configuration to map port 80 of the host to port 80 to the jail:

# External interface ext_if="xn0" # Public IP public_ip="172.31.8.31" # enter xn0 IP from AWS # Jails interface jail_if="lo1" # Jails IPs jail_www="10.10.1.5" jail_net="10.10.1.0/24"# Enable network packet normalization, causing fragmented # packets to be assembled and removing ambiguity scrub in all # Jail traffic NAT nat pass on $ext_if from $jail_net to any -> $public_ip # map port 80 to 80 of the jail rdr on $ext_if proto tcp from any to $public_ip port 80 -> $jail_www port 80

Now let's start the firewall:

$: service pf start

Test installation with NGINX

Now after we have our jail up and running, we can install nginx to it and test if we can reach it from the internet.

Let's log in into the jail:

$: jexec ${jail_id} /bin/sh

Here ${jail_id} is the id from the output of the jls command.

Now let's install the nginx web server:

$: pkg install nginx

After this is done, open the IP of the host and you should see the default nginx page.


Conclusion

In this guide we saw how easy it is to build and run a FreeBSD Jail and a webserver inside it. As it might look odd nowadays, FreeBSD Jails remain important piece of technology that continues to run a big part of the internet.


Sources