Gentoo in a systemd-nspawn chroot

Submitted by wraeth on Sun, 06/11/2017 - 15:18
Screenshot of terminal output

There may come a time when you need to do some testing in a Gentoo environment that is not affected by, and doesn't affect, your "regular" usage. This might include testing new ebuilds or configuring services in some unusual or exotic manner; or possibly even just an isolated environment to run some program. Traditionally this is done in a chroot, the Linux method of making a sub-directory believe it's its own system; or for more rigorous or isolated testing, a virtual machine, complete with its own kernel. For systemd users, however, there is systemd-nspawn1, the chroot-on-steroids2.

This guide will step you through the process of getting a chroot environment set up using systemd-nspawn and configuring permissions to allow for quick access to the environment. Once completed, you will have a full chroot-container environment running under its own namespace, complete with an interactive instance of systemd and basic network connectivity. The chroot can be configured to be started on-boot (which, given the low overhead, is not unreasonable), or started on-demand.

It's worth noting that the first half of this process will essentially be a cut-down version of the Gentoo Handbook. While this will be suitable for our purposes, I do highly recommend going through the full Handbook for your first install (or fifth, or fiftieth, for that matter). Also notable is that a similar process to this can be used when performing a proper Gentoo installation from a systemd environment, allowing you to configure start-up services and other systemd-centric configurations before the first-boot.

Setting up the environment

The first step to setting up the chroot environment is to extract the stage3 tarball into the chroot directory. For this I will use the standard directory /var/lib/machines as this is one of the systemd-nspawn search paths, however this could be done in any directory with just a little tweaking; and will use the container name "testing".

Note: With the exception of the download, all of this configuration is done as root.

First, Download a stage3 archive from or your local mirror3. Then we create the container root directory and extract the stage3:

mkdir -p /var/lib/machines/testing
tar xvjpf stage3-amd64-systemd-20170608.tar.bz2 -C /var/lib/machines/testing --numeric-owner --xattrs

This has our very basic Gentoo environment set up, and you could actually enter a chroot right now and begin messing around, but there are still a few things to do to get a truly useful environment. Specifically, we want to copy our DNS information across, as well as the global portage configuration in make.conf - this way we keep useful things like CPU_FLAGS_X86 and CFLAGS set, and can remove any unneeded global USE flags once copied.

cp -L /etc/resolv.conf /var/lib/machines/testing/etc/
cp /etc/portage/make.conf /var/lib/machines/testing/etc/portage/
$EDITOR /var/lib/machines/testing/etc/portage/make.conf

Setting up bind mounts

Now that the base environment is set up, we will configure some bind mounts - why download completely separate portage repositories and copies of distfiles when you already have them? This will also start setting up the systemd-nspawn unit file.

The systemd-nspawn system configuration directory is /etc/systemd/nspawn. This will contain unit files for all nspawn-based containers for which some confiuration is required4. In this directory, you will need to create a file named NAME.nspawn where NAME is the name of the container we're creating. In its simplest form, this file would contain something similar to the below:


This will tell systemd-nspawn to bind-mount the host directory of /usr/portage to the same path in the resulting container. More complex configurations can be used, however. For example, rather than binding my "regular" portage repository, I would prefer to bind my clone of the development repository. Additionally, want my overlay available, and I separate my DISTFILES and PKGDIR, which would need to be reflected in the container, resulting in a unit file more like:


The syntax is pretty straightforward, but in short the format is "action=source[:destination]"5.

Configuring networking

At this point you should be able to enter the chroot container and perform most of the things you might want, however by default it will only have a virtual network interface that connects it to the host - you won't be able to download any new distfiles from the isolated environment. This may be what you're looking for6, in which case skip along to the next section, however for the complete experience, one last small tweak is needed in the unit file to allow external networking.

Specifically, we want to add Private=no to the [Network] section of the unit file to instruct systemd to allow the container access to the host networking. This will result in our /etc/systemd/nspawn/testing.nspawn unit file looking like this:



This should provide the same network interfaces in your chroot container as on your real host, and allow the isolated environment normal networking access. It should be noted that this does work best with systemd-networkd as the host network management daemon - if you use something else, you may want to switch, or see my previous article on sharing network management between systemd-networkd and other utilities (specifically NetworkManager).

Permissions, starting and entering

The last few steps in the setup are to start the environment and, if you're like me and don't want to su just to get a shell, configure polkit to allow you to enter it from a user session.

Starting the container is as simple as machinectl start testing. You should then be able to see it by running machinectl with no arguments:

testing container systemd-nspawn gentoo -       -

1 machines listed.

From a root console, you can then enter the environment with machinectl shell testing, however we'll go one step further and add a polkit rule to allow users to enter the shell themselves. For this I will use the wheel group, but you can create and use your own group if you'd prefer. Create the file /usr/share/polkit-1/rules.d/90-org.freedesktop.machine1.rules7 with the content:

polkit.addRule(function(action, subject) {
    if ( == "" &&
        subject.isInGroup("wheel") && {
        return "yes";

This last bit of configuration tells polkit, the authentication policy manager, that users in the wheel group are allowed to perform the specified action - you could modify this to allow more control from a regular user account if preferred. Note that polkit may need to be restarted for the rule to take effect. You should now be able to get a shell in your new isolated chroot container using the command:

machinectl shell root@testing

To have the container start on-boot, you can enable it using either machinectl enable testing or systemctl enable systemd-nspawn@testing.service. Now go forth and enjoy breaking Linux in a risk-free8 way!

  • 1.
  • 2.
  • 3. Don't forget to verify checksums
  • 4. An nspawn-based container doesn't need a unit file to be found or started, but does if changing default behaviour
  • 5. See man systemd.nspawn for details
  • 6. You can also use FEATURES="network-sandbox" to prevent portage from using the network except during fetch
  • 7. The filename doesn't particularly matter, though for reference, rules get parsed in lexical order
  • 8. This statement is made with no warranty, express or implied

Add new comment