Manage multiple systemd service instances#

With systemd you can manage services like in post Environment variables set by systemd, but managing multiple instances of the same service is not so easy. For example, you have a service that runs a web server, and you want to run multiple instances of that service, each with different configuration. You can do that by creating multiple service files, but that is not very elegant. You can also use systemd templating, but that is not very flexible. The best way to do this is to use systemd instances.

The example service#

For the example, we will create a service that runs a Python script. The script will print the value of an environment variable. The name of the environment variable will be the name of the instance. The script will be called env_variables.py and will be located in /usr/local/bin/. The service will be called env_variables@.service and will be located in /etc/systemd/system/. So let’s copy the script from the previous post as it can print the value of an environment variable to the standard output.

Example code /usr/local/bin/env_variables.py#
#!/usr/bin/env python3

import os

if os.environ.get('VAR1'):
    var1 = os.environ['VAR1']
else:
    var1 = 'default'

print(var1)

The file needs to be executable and relies on the env command to find a version of Python 3.

Creating the template service#

With systemd templating, you can create a template service file that can be used to create multiple instances of the same service. The template service file is called env_variables@.service and is located in /etc/systemd/system/. The @ in the name of the file is important as it tells systemd that this is a template service file. The template service file looks like this:

Example unit file /etc/systemd/system/env_variables@.service#
[Unit]
Description=App for %i

[Service]
ExecStart=/usr/local/bin/env_variables.py
Environment="VAR1=$i"

[Install]
WantedBy=multi-user.target

Note

The %i or %I in the Description and Environment lines is the instance name. From systemd documentation, the only difference should be about escaping:

  • %i | Instance name | For instantiated units: this is the string between the “@” character and the suffix of the unit name.

  • %I | Unescaped instance name | Same as “%i”, but with escaping undone. This will resolve escape sequences like “x20” to their literal character value.

After creating the template service file, you need to reload the systemd daemon to make it aware of the new service file.

Reloading the systemd daemon#
$ sudo systemctl daemon-reload

Starting an instances#

Now that the service exists, you can start an instance of it. To do that, you need to use the @ in the name of the service. The name of the instance will be the string after the @. For example, to start an instance called customer01, you need to run:

Creating an instance and starting it for the first time#
$ sudo systemctl enable --now app@customer01
Created symlink /etc/systemd/system/multi-user.target.wants/[email protected] → /etc/systemd/system/[email protected].
Checking the status of the instance#
$ sudo systemctl status app@customer01
[email protected] - App for customer01
     Loaded: loaded (/etc/systemd/system/[email protected]; enabled; vendor preset: enabled)
     Active: inactive (dead) since Mon 2023-07-17 20:23:23 UTC; 6min ago
   Main PID: 3493888 (code=exited, status=0/SUCCESS)
        CPU: 101ms

Jul 17 20:23:23 ubuntu systemd[1]: Started App for customer01.
Jul 17 20:23:23 ubuntu env_variables.py[3493888]: customer01
Jul 17 20:23:23 ubuntu systemd[1]: [email protected]: Deactivated successfully.

If another instance is needed, you can create it and start it the same way as shown below where we create an second instance called customer02. It also shows the status of the instance.

Creating another instance and starting it for the first time#
$ sudo systemctl enable --now app@customer02
Created symlink /etc/systemd/system/multi-user.target.wants/[email protected] → /etc/systemd/system/[email protected].
$ sudo systemctl status app@customer02
[email protected] - App for customer02
    Loaded: loaded (/etc/systemd/system/[email protected]; enabled; vendor preset: enabled)
    Active: inactive (dead) since Mon 2023-07-17 20:23:28 UTC; 7min ago
    Process: 3493933 ExecStart=/usr/local/bin/env_variables.py (code=exited, status=0/SUCCESS)
  Main PID: 3493933 (code=exited, status=0/SUCCESS)
        CPU: 106ms

Jul 17 20:23:27 ubuntu systemd[1]: Started App for customer02.
Jul 17 20:23:28 ubuntu env_variables.py[3493933]: customer02
Jul 17 20:23:28 ubuntu systemd[1]: [email protected]: Deactivated successfully.

When checking the status of the instances, you can see that they are inactive. This is because the service is not a daemon. It is a one-shot service that runs the script and then exits. The instances are not running anymore. To see the output of the script, you can use the journalctl command.

Checking the output of the script in the system journal#
$ sudo journal -f
Jul 17 20:23:23 ubuntu systemd[1]: Started App for customer01.
Jul 17 20:23:23 ubuntu env_variables.py[3493888]: customer01
Jul 17 20:23:23 ubuntu systemd[1]: [email protected]: Deactivated successfully.
Jul 17 20:23:26 ubuntu systemd[1]: Reloading.
Jul 17 20:23:27 ubuntu systemd[1]: Started App for customer02.
Jul 17 20:23:28 ubuntu systemd[1]: Starting Daily apt download activities...
Jul 17 20:23:28 ubuntu env_variables.py[3493933]: customer02
Jul 17 20:23:28 ubuntu systemd[1]: [email protected]: Deactivated successfully.

Final thoughts about systemd instances#

With systemd instances, you can create multiple instances of the same service. This is useful when you need to run the same service multiple times with different parameters. It is also useful when you need to run the same service multiple times with the same parameters. Examples are different instances of an application server like Apache Tomcat, PHP, or a Django application, but the options are endless.