Build a service

Pass configuration and secrets

Give a service its runtime configuration without embedding secrets in the unit or source repository.

8 minute lesson

~~~

A service does not inherit the full environment of your interactive shell. The PATH tweaks and exported variables in your .bashrc do not exist when systemd starts the process. Define every required value deliberately, in the unit.

Environment and EnvironmentFile

Environment= suits small non-secret values, written directly in the unit:

[Service]
Environment=NODE_ENV=production
EnvironmentFile=/etc/demo-api/app.env
ExecStart=/usr/bin/node /opt/demo-api/server.js

EnvironmentFile= loads KEY=VALUE lines from a separate file. That keeps configuration out of the unit and out of your repository:

# /etc/demo-api/app.env
PORT=3000
DATABASE_URL=postgres://[email protected]/app

Make that file root-owned and unreadable to others:

sudo chown root:root /etc/demo-api/app.env
sudo chmod 600 /etc/demo-api/app.env

If the file is missing, the service fails to start. Prefix the path with a dash (EnvironmentFile=-/etc/demo-api/local.env) when the file is optional.

Why environment variables are weak for secrets

Even with a root-owned file, environment variables may still be exposed through process inspection or debugging. systemctl show demo-api -p Environment prints them. Child processes inherit them. Crash reports and debug tooling can dump them.

For sensitive data, prefer application-supported credential files. systemd has a mechanism built for this:

[Service]
LoadCredential=db-password:/etc/demo-api/db-password

The service reads the secret from $CREDENTIALS_DIRECTORY/db-password, a private per-service path. The value never appears in the environment or in systemctl show. The application needs to support reading a secret from a file, but most database drivers and libraries do.

Do the inventory

List every value your application needs to run. Classify each one as ordinary configuration or secret. Ordinary configuration goes in Environment= or an EnvironmentFile=. Secrets go in a credential file with tight ownership and mode, loaded through LoadCredential= or the application’s own secret-file option.

Doing this classification once, on paper, is faster than discovering a leaked DATABASE_URL in a process listing later.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →