Jails with nullfs mount of base system on FreeBSD 11

Last edited

When setting up a fresh FreeBSD 11 server, I used the system with nullfs jails as described on my page Jails with nullfs mount of base system on FreeBSD 10 without buildworld.

I did changed the basic file structure a bit, to make it less complex to read.

This is the system that I use now:

/jails : 
  directory for every thing jail related
/jails/master : 
  directory with the base filesystem, that will get read only nullfs 
  mounted in the jails
/jails/skeleton : 
  directory with the files that will be writeable, each jail gets it own set
/jails/files/<jailname> : 
  directory where files from skeleton are copied to 
/jails/<jailname> : 
  empty directory, where everything gets nullfs mounted

The /jails/master is nullfs read only mounted into the jail directory of every jail, so this part exists only once on the hard disk.

Every jail gets a number of files that are writeable, like /etc/rc.conf and /etc/ssh/sshd_config. These files are copied once from /jails/skeleton to /jails/files/<jailname>. Every jail will have its own set, so this part exists as many times on your harddisk as you have jails.

Prepare base system

The base system will be mounted read-only into the jail.

We populate the base system from the install CDROM.

mkdir -p /jails/master
setenv DESTDIR /jails/master
mount_cd9660 /dev/cd0 /mnt
tar -xf /mnt/usr/freebsd-dist/base.txz -C $DESTDIR
tar -xf /mnt/usr/freebsd-dist/doc.txz -C $DESTDIR
umount /mnt
cp /etc/resolv.conf $DESTDIR/etc/
chroot $DESTDIR
pkg install cpdup

With this last step (pkg install cpdup) we force the system to install the pkg utility. Later on in the process this will not be as easy as this, because we are going to move directories like /etc out of the base system.

Prepare template for the read-write part of the jail

First setup the directory for the template:

mkdir /jails/skeleton/ /jails/skeleton/home /jails/skeleton//usr-X11R6 

Now populate it by moving parts of the base system into it:

cd /jails/master
mv etc /jails/skeleton/
mv usr/local /jails/skeleton//usr-local
mv tmp /jails/skeleton/
mv var /jails/skeleton/
mv root /jails/skeleton/

And create symlinks back into the base system:

cd /jails/master
mkdir s
ln -s s/etc etc
ln -s s/home home
ln -s s/root root
ln -s /s/usr-local usr/local
ln -s /s/usr-X11R6 usr/X11R6
ln -s s/tmp tmp
ln -s s/var var

Creating the directories for a new jail

Two directories are created for the new jail:


The /jails/<jailname> will be populated by a nullfs mount

The /jails/files/<jailname> will get real files by copying the skel directory to it.

Populate /jails/files/

cpdup /jails/skeleton /jails/files/<jailname>

Edit /etc/fstab

On the host add the following lines to /etc/fstab:

/jails/master   /jails/<jailname>    nullfs  ro      0       0
/jails/files/<jailname> /jails/<jailname>/s       nullfs  rw      0       0

Edit /etc/jail.conf

/etc/jail.conf has a block with general configuration for all jails:

devfs_ruleset = 4;
allow.raw_sockets = 0;
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
allow.set_hostname = 0;
allow.sysvipc = 0;

Below this part, for each jail a block has to be added to /etc/jail.conf:

<jailname> {
    host.hostname = "<jailname>";
    path = "/jails/<jailname>";
    ip4.addr += "<ipaddress>/32";
    exec.system_user = "root";
    exec.jail_user = "root";
    exec.consolelog = "/var/log/jail_<jailname>_console.log";

Edit sshd_config

The host will have a sshd_config, and every jail will have one, too. It is important that every instance of the sshd-server will listen only to one ip address.

So, first change the /etc/ssh/sshd_config file of the host. Change the line with the ListenAddress by uncommenting it and adding the ip address of the host to it.

Every jail needs also the right listen address in the sshd_config file of the jail.

Create ip address alias for the jail

The configuration of the network interface has to be extended with the ip address of the new jail. So you have to make an alias for it. This can be done in /etc/rc.conf, but then either the network has to be restarted or the alias must also be made manually with the ifconfig command.

Below is a example line for the /etc/rc.conf file:

ifconfig_re0_alias0="inet netmask 0xffffff00"

Start the jail

When the directories are populated and the config files are edited, we can start the jail.

First we mount the nullfs mounts:

mount -a

Now we can chroot into the jail directory and set the root password.

cd /jails/<jailname>
chroot .

And start the jail:

jail -c <jailname>

See if the jail is started, and add a user.

jexec <jailnumber> /bin/sh
ps aux

Script to create a new jail

When the /jails/master and /jails/skeleton is populated, the following script can be used to create a new jail.


# script to set up nullfs jail
# $1 is name of jail
# $2 is ip address of jail

### Check that two arguments are given ###
if [ $# -ne 2 ]
    echo "Not two arguments supplied!"
    echo "Usage: "
    echo "   $0 <jailname> <jail ip-address>"

### Setup jail files ###
mkdir -p /jails/$1
mkdir -p /jails/files/$1
cpdup /jails/skeleton /jails/files/$1

### Configure sshd to listen only to jail ip address ###
echo "ListenAddress $2" >> /jails/files/$1/etc/ssh/sshd_config

### Add jail to /etc/jail.conf ###
cat << EOB | sed "s/JAILNAME/$1/g" | sed "s/IPADDRESS/$2/g" >> /etc/jail.conf
    host.hostname = "";
    path = "/jails/JAILNAME";
    ip4.addr += "IPADDRESS/32";
    exec.system_user = "root";
    exec.jail_user = "root";
    exec.consolelog = "/var/log/jail_JAILNAME_console.log";

### Add jail file system to /etc/fstab ###
cat << EOB1 | sed "s/JAILNAME/$1/g" | sed "s/IPADDRESS/$2/g"  >> /etc/fstab

/jails/master   /jails/JAILNAME    nullfs  ro      0       0
/jails/files/JAILNAME /jails/JAILNAME/s       nullfs  rw      0       0


cat << EOB2 | sed "s/JAILNAME/$1/g" > /jails/files/$1/etc/rc.conf

### Get latest alias line and calculate next alias ###
### Will only work for alias 1 to alias 10         ###
### alias0 has to exist, or this will fail         ###
p=`grep alias /etc/rc.conf | tail -n 1 | cut -d'=' -f 1 | tail -c 2 | sed 's/ //g'` 
p=`expr $p + 1` 
echo "ifconfig_re0_alias$p=\"inet $2 netmask 0xffffff00\"" >> /etc/rc.conf

echo "end of script"
echo ""
echo "Proceed with the following steps"
echo "*  mount -a (to mount jail file system)"
echo ""
echo "*  ifconfig re0 $2 netmask alias (to set alias manualy)"
echo "   (or restart network with: /etc/rc.d/netif restart && /etc/rc.d/routing restart)"
echo ""
echo "*  chroot to /jails/$1 and set root password and add a user"
echo ""
echo "*  jail -c $1 (to start the new jail $1)"

Have fun!