Persistent Linux 'jails' on TrueNAS SCALE to install software (docker-compose, portainer, podman, etc.) with full access to all files via bind mounts thanks to systemd-nspawn!
Go to file
Jip-Hop 64ee839ec1
Merge pull request #234 from Jip-Hop/develop
2024-07-15 14:34:49 +02:00
.github Debug logging 2024-07-09 13:00:34 +02:00
docs Cleanup networking docs 2024-07-03 15:29:31 +02:00
templates Remove systemctl 2024-07-09 13:10:26 +02:00
test Remove debug logging 2024-07-09 13:43:19 +02:00
LICENSE Update 2024-07-15 14:34:03 +02:00 Fix systemd_nspawn_user_args override 2024-07-09 14:41:35 +02:00


Persistent Linux 'jails' on TrueNAS SCALE to install software (k3s, docker, portainer, podman, etc.) with full access to all files via bind mounts.

Video Tutorial

TrueNAS Scale - Setting up Sandboxes with Jailmaker - YouTube Video
Watch on YouTube




TrueNAS SCALE can create persistent Linux 'jails' with systemd-nspawn. This script helps with the following:

  • Setting up the jail so it won't be lost when you update SCALE
  • Choosing a distro (Debian 12 strongly recommended, but Ubuntu, Arch Linux or Rocky Linux seem good choices too)
  • Will create a ZFS Dataset for each jail if the jailmaker directory is a dataset (easy snapshotting)
  • Optional: configuring the jail so you can run Docker inside it
  • Optional: GPU passthrough (intel, nvidia with drivers bind mounted from the host, AMD reportedly works too)
  • Starting the jail with your config applied


Beginning with 24.04 (Dragonfish), TrueNAS SCALE officially includes the systemd-nspawn containerization program in the base system. Technically there's nothing to install. You only need the script file in the right place. Instructions with screenshots are provided on the TrueNAS website. Start by creating a new dataset called jailmaker with the default settings (from TrueNAS web interface). Then login as the root user and download

cd /mnt/mypool/jailmaker
curl --location --remote-name
chmod +x

The script (and the jails + config it creates) are now stored on the jailmaker dataset and will survive updates of TrueNAS SCALE. If the automatically created jails directory is also a ZFS dataset (which is true for new users), then the script will automatically create a new dataset for every jail created. This allows you to snapshot individual jails. For legacy users (where the jails directory is not a dataset) each jail will be stored in a plain directory.


Optionally you may create a shell alias for the currently logged in (admin) user to conveniently run without having to change into the jailmaker directory or specify the full absolute path. I suggest to create the jlmkr alias like this:

echo "alias jlmkr=\"sudo -E '/mnt/mypool/jailmaker/'\"" >> ~/.bashrc

Please replace /mnt/mypool/jailmaker/ with the actual path to where you stored If you're using zsh instead of bash, then you should replace .bashrc in the command above with .zshrc. If you've created the alias, you may use it instead of ./

The alias will be available the next time you load the shell, but to use the alias immediately you can source ~/.bashrc or source ~/.zshrc, as appropriate.


Create Jail

Creating a jail with the default settings is as simple as:

./ create --start myjail

You may also specify a path to a config template, for a quick and consistent jail creation process.

./ create --start --config /path/to/config/template myjail

Or you can override the default config by using flags. See ./ create --help for the available options. Anything passed after the jail name will be passed to systemd-nspawn when starting the jail. See the systemd-nspawn manual for available options, specifically Mount Options and Networking Options are frequently used.

./ create --start --distro=ubuntu --release=jammy myjail --bind-ro=/mnt

If you omit the jail name, the create process is interactive. You'll be presented with questions which guide you through the process.

./ create

After answering some questions you should have created your first jail (and it should be running if you chose to start it after creating)!

Startup Jails on Boot

# Call startup using the absolute path to
/mnt/mypool/jailmaker/ startup

In order to start jails automatically after TrueNAS boots, run /mnt/mypool/jailmaker/ startup as Post Init Script with Type Command from the TrueNAS web interface. This will start all the jails with startup=1 in the config file.

Start Jail

./ start myjail

List Jails

See list of jails (including running, startup state, GPU passthrough, distro, and IP).

./ list

Execute Command in Jail

You may want to execute a command inside a jail, for example manually from the TrueNAS shell, a shell script or a CRON job. The example below executes the env command inside the jail.

./ exec myjail env

This example executes bash inside the jail with a command as additional argument.

./ exec myjail bash -c 'echo test; echo $RANDOM;'

Edit Jail Config

./ edit myjail

Once you've created a jail, it will exist in a directory inside the jails dir next to For example /mnt/mypool/jailmaker/jails/myjail if you've named your jail myjail. You may edit the jail configuration file using the ./ edit myjail command. This opens the config file in your favorite editor, as determined by following Debian's guidelines on the matter. You'll have to stop the jail and start it again with jlmkr for these changes to take effect.

Remove Jail

Delete a jail and remove it's files (requires confirmation).

./ remove myjail

Stop Jail

./ stop myjail

Restart Jail

./ restart myjail

Jail Shell

Switch into the jail's shell.

./ shell myjail

Jail Status

./ status myjail

Jail Logs

View a jail's logs.

./ log myjail

Additional Commands

Expert users may use the following additional commands to manage jails directly: machinectl, systemd-nspawn, systemd-run, systemctl and journalctl. The jlmkr script uses these commands under the hood and implements a subset of their functions. If you use them directly you will bypass any safety checks or configuration done by jlmkr and not everything will work in the context of TrueNAS SCALE.


By default the root user in the jail with uid 0 is mapped to the host's uid 0. This has obvious security implications. If this is not acceptable to you, you may lock down the jails by limiting capabilities and/or using user namespacing or use a VM instead.


Seccomp is a Linux kernel feature that restricts programs from making unauthorized system calls. This means that when seccomp is enabled there can be times where a process run inside a jail will be killed with the error "Operation not permitted." In order to find out which syscall needs to be added to the --system-call-filter= configuration you can use strace.

For example:

# /usr/bin/intel_gpu_top
Failed to initialize PMU! (Operation not permitted)

# strace /usr/bin/intel_gpu_top 2>&1 |grep Operation\ not\ permitted
perf_event_open({type=0x10 /* PERF_TYPE_??? */, size=PERF_ATTR_SIZE_VER7, config=0x100002, sample_period=0, sample_type=0, read_format=PERF_FORMAT_TOTAL_TIME_ENABLED|PERF_FORMAT_GROUP, precise_ip=0 /* arbitrary skid */, use_clockid=1, ...}, -1, 0, -1, 0) = -1 EPERM (Operation not permitted)
write(2, "Failed to initialize PMU! (Opera"..., 52Failed to initialize PMU! (Operation not permitted)

The syscall that needs to be added to the --system-call-filter option in the jailmaker config in this case would be perf_event_open. You may need to run strace multiple times.

Seccomp is important for security, but as a last resort can be disabled by setting seccomp=0 in the jail config.


By default a jails will use the same networking namespace, with access to all (physical) interfaces the TrueNAS host has access to. No further setup is required. You may download and install additional packages inside the jail. Note that some ports are already occupied by TrueNAS SCALE (e.g. 443 for the web interface), so your jail can't listen on these ports.

Depending on the service this may be o.k. For example Home Assistant will bind to port 8123, leaving the 80 and 443 ports free from clashes for the TrueNAS web interface. You can then either connect to the service on 8123, or use a reverse proxy such as traefik.

But clashes may happen if you want some services (e.g. traefik) inside the jail to listen on port 443. To workaround this issue when using host networking, you may disable DHCP and add several static IP addresses (Aliases) through the TrueNAS web interface. If you setup the TrueNAS web interface to only listen on one of these IP addresses, the ports on the remaining IP addresses remain available for the jail to listen on.

See the networking docs for more advanced options (bridge and macvlan networking).


Using the docker config template is recommended if you want to run docker inside the jail. You may of course manually install docker inside a jail. But keep in mind that you need to add --system-call-filter='add_key keyctl bpf' (or disable seccomp filtering). It is not recommended to use host networking for a jail in which you run docker. Docker needs to manage iptables rules, which it can safely do in its own networking namespace (when using bridge or macvlan networking for the jail).


Additional documentation can be found in the docs directory (contributions are welcome!).


TODO: write comparison between systemd-nspawn (without jailmaker), LXC, VMs, Docker (on the host).

Incompatible Distros

The rootfs image downloads comes from the Linux Containers Image server. These images are made for LXC. We can use them with systemd-nspawn too, although not all of them work properly. For example, the alpine image doesn't work well. If you stick with common systemd based distros (Debian, Ubuntu, Arch Linux...) you should be fine.

Filing Issues and Community Support

When in need of help or when you think you've found a bug in jailmaker, please start with reading this.