systemd Dynamic Users

The Linux init system systemd has its controversies, but it also has some pretty neat features, my favorite of which is Dynamic Users. Lennart Poettering's blog has a full rundown of the ins and outs, but I'll summarize the key points below, and document a few tricks.


Users and groups and Name Service Switch, oh my!

On Linux, when you create a user it is usually added to /etc/passwd, and groups it is in are added to /etc/groups. However, these databases and others can actually come from multiple sources, controlled by the Name Service Switch, and on modern Linux one of those sources is systemd.

When configured to, systemd will create a user for the lifetime of a Unit to run that Unit. The user is never added to /etc/passwd, but exists only at runtime, and when the Unit finishes the user goes away. Although they are very locked down by default, this user is a full Linux user, and can be added to groups, write to disk, or be given capabilities. In particular, systemd provides a system for State Directories, which will be writable by the created user, persist after the Unit finishes, and will be fixed-up the next time the Unit runs to be writeable by the next dynamically created user, which can be used to store service data, or to simulate a home directory.

Simple services

A very useful result of Dynamic Users is to simplify the packaging and running of services. Instead of having to create a user to run a service when it is installed and remove it later when the service is uninstalled, the service package only needs to install a service file as the user is created at runtime. For example, below is the configuration for a web service of mine:

$ cat /lib/systemd/system/
Description=Backend server for

# Create a user at runtime.
# It will have a random name.

# Set the user's primary group to www-data.

# Create a temporary runtime directory at /run/log-eth-moe/.

# Create a persistent state directory at /var/lib/log-eth-moe/.

ExecStart=/usr/bin/ -socket /run/log-eth-moe/listen.sock -dir /var/lib/log-eth-moe/


It's also generally useful any time you want a non-human user to run something. For example, below is my configuration for running the Kodi media center on a Raspberry Pi:

$ cat /lib/systemd/system/kodi.service
Description=Kodi Media Center

# Create a user at runtime.

# Call that user "kodi".

# Add it to useful groups for a media center.
# Note that these are all supplementary groups,
# and the dynamic user has no primary group.

# Create a persistent state directory at /var/lib/kodi.

# Set the home directory of the dynamic user to /var/lib/kodi.



Sandboxed Steam?

Steam is great, but it also requires running other people's code, so it's best to sandbox it a little. While some people might go to the lengths of running Steam in Docker, I use systemd to achieve similar ends.

The steps of this approach are:


After creating your group, grant that group permission to use your display, substituting eth-x11 for your group:

$ xhost +si:localgroup:eth-x11

This will need to be done every X11 session, and should probably be added to your .xinitrc or .xsession.

Then create a script to run Steam under systemd-run:

$ cat /usr/local/bin/steam

set -eux

systemd-run \
    --pty \
    --property=DynamicUser=yes \
    --property=Group=eth-x11 \
    --property=SupplementaryGroups=audio \
    --property=SupplementaryGroups=input \
    --property=SupplementaryGroups=video \
    --property=StateDirectory=steam \
    --property=Environment=DISPLAY=${DISPLAY} \
    --property=Environment=HOME=/var/lib/steam \

Unfortunately, to use systemd-run you must either be root or authenticate yourself to systemd, but the experience can be improved with sudoers or setuid on the script.