Environment variables set by systemd#

Applications sometimes need environment variables to be set for triggering certain behavior like giving debug output or routing traffic via a HTTP-proxy for example. A common way is to modify the start-stop script, but with systemd on most Linux systems, like Debian and Red Hat based distributions, this can also be directly set within the unit file and you don’t have to export the variables anymore.

Reading environment variables#

Let’s start with a Python script to read and print the environment variables set by the environment to see how this works. The Python script below that we run via systemd checks if environment variable VAR1 has been set and will generate different output based on that.

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

import os

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

print(var1)

Running the Python script also shows the output difference as the second command doesn’t print the string default anymore to the terminal, but the text test that we set via the environment variable.

Output of running env_variables.py#
$ ./env_variables.py
default
$ VAR1=test ./env_variables.py
test

Set variables in an unit file#

Setting the environment variables via systemd is done by adding the attribute Environment to the Service section of the unit file for the service. After a systemctl daemon-load the environment variable will be set when you start or restart the service.

Example unit file env_variables.service#
...
[Service]
ExecStart=/usr/local/bin/env_variables.py
Environment="VAR1=hello"
...

If more variables need to be set, then more Environment attributes can be added to the Service section.

Example unit file env_variables.service#
...
ExecStart=/usr/local/bin/env_variables.py
Environment="VAR1=hello"
Environment="VAR2=world"
...

When we reload the unit file, the environment variables are set when we restart the demo application and see the following output in the system journal.

Output of running env_variables.py#
$ sudo systemctl daemon-reload
$ sudo systemctl restart app.service
$ sudo journalctl -u app
Aug 12 09:42:35 server.example.org systemd[1]: Started App Service.
Aug 12 09:42:35 server.example.org app[4483]: hello

Set variables via a file#

In the previous section, environment variables were read directly from the unit file and set by systemd. This is fine for a small number of variables, but in some cases, these environment variables are provided by another package on the system or need to be the same for multiple services.

We have modified our Python application to print all environment variables that are set to make this example easier.

Updated example code env_variables.py#
#!/usr/bin/env python3

import os

for param in os.environ.keys():
    print("%20s %s" % (param, os.environ[param]))

We create the first environment file /usr/local/env/file1 with the content below to assign string values to variables. Just as in the systemd unit file no string interpolation is being done, only lines with an equal sign are processed and everything after a hash sign is ignored.

Example environment file /usr/local/env/file1#
FVAR1="test1"
FVAR2="test2"

We also create a second environment file /usr/local/env/file2 with the content below. Directly we see that variable FVAR1 is also declared in this environment file.

Example environment file /usr/local/env/file2#
FVAR1="TEST1"
FVAR3="Test3"

To use the environment files we need to declare them in the systemd unit file below. The line for file1 shows that we require the file to be present otherwise the service will fail, but for file2 the filename has been preceded by a hyphen to indicate to systemd that the file is optional and no error will be generated if it is absent.

Example code env_variables.py#
[Unit]
Description=App Service

[Service]
Type=simple
EnvironmentFile=/usr/local/env/file1
EnvironmentFile=-/usr/local/env/file2
Environment="VAR0=hello world"
ExecStart=/usr/local/bin/env_variables.py

After restarting the application with systemd all the environment variables that were set are shown in the system journal. The most interesting variable shown is FVAR1 as we declared it in two files earlier and we see that the value set in file1 was replaced by the value set in file2 that was processed later.

Output of running env_variables.py#
$ sudo systemctl daemon-reload
$ sudo systemctl restart app.service
$ sudo journalctl -u app
Aug 12 09:43:35 server.example.org systemd[1]: Started App Service.
Aug 12 09:43:35 server.example.org app[4483]: LANG en_US.UTF-8
Aug 12 09:43:35 server.example.org app[4483]: VAR0 hello world
Aug 12 09:43:35 server.example.org app[4483]: FVAR2 test2
Aug 12 09:43:35 server.example.org app[4483]: FVAR3 Test3
Aug 12 09:43:35 server.example.org app[4483]: FVAR1 TEST1
Aug 12 09:43:35 server.example.org app[4483]: PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

Overriding variables#

Unit files are installed /lib/systemd/system or /etc/systemd/system depending if unit files are installed as part of the system or not. A third option is to override the unit file with a local file. This is done by adding the attribute that needs to be overridden in /etc/systemd/system/<name>.service.d/override.conf.

Override the unit file for a service#
systemctl edit <service name>

Note

The unit file is not reloaded automatically, you need to restart the service for the changes to take effect.

Conclusion#

While it may break some human workflows in the beginning, in long term it is a good step for following the infrastructure as code path as Ansible could be used for managing these variables. Also storing these kinds of variables, in the same way, makes both troubleshooting and collecting settings for an audit easier as all configurations are stored in one place.

A common use case for this is to store environment variables for a Spring Boot application or to set the ORACLE_HOME environment variable for Oracle applications. Sourcing the environment variables from files can be used to let a platform team set all the default variables and then override them when needed with local values in a second environment file. It also separates the environment variables from the application code allowing Ops engineers to maintain a deployment and Dev engineers to maintain the application code.