LinuxBeginner 12 min Lesson 11 of 24

Environment Variables

How configuration reaches your application, where variables come from, and why secrets belong here rather than in code.

Linux · Lesson 11 of 24
0/24 done(0%)

What is it? #

An environment variable is a named value that a process inherits from whatever started it.

They are the standard way to configure an application without changing its code: database URLs, API keys, log levels, feature flags.

The important property is inheritance. A shell passes its exported variables to the programs it starts, and those programs pass them on in turn.

They are also the standard place for secrets, because they keep credentials out of source control while still reaching the application.

Think of it like this #

Instructions handed to someone as they walk into a room, rather than painted on the wall.

The same worker can be given different instructions in different rooms, and nobody has to repaint anything to change what happens.

Simple example #

The same application code runs on your laptop against a local database and on the server against the production one. Only the environment differs.

Code #

BASH
# Viewing
printenv                    # everything
echo $HOME                  # one variable
echo "${DATABASE_URL:-not set}"   # with a fallback if unset

# Setting, for this shell and its children
export API_KEY="abc123"
export LOG_LEVEL=debug

# For one command only
DEBUG=true python app.py

# Unsetting
unset API_KEY
TEXT
Variables you will meet

PATH        directories searched for commands
HOME        the current user's home directory
USER        current username
PWD         current directory
LANG        language and character encoding
TZ          timezone
BASH
# PATH is how the shell finds commands
echo $PATH
# /usr/local/bin:/usr/bin:/bin:/srv/app/.venv/bin
which python            # which one will actually run
export PATH="/srv/app/.venv/bin:$PATH"   # prepend: searched first
BASH
# An env file, loaded by systemd or a library
# /srv/app/.env   — chmod 600, owned by the service user, NEVER committed
DATABASE_URL=postgresql://app:secret@localhost/shop
SECRET_KEY=change-me-in-production
LOG_LEVEL=info

# systemd loads it for the service
# [Service]
# EnvironmentFile=/srv/app/.env
PYTHON
import os

# Required: fail loudly at startup if it is missing
DATABASE_URL = os.environ["DATABASE_URL"]

# Optional: a sensible default
LOG_LEVEL = os.environ.get("LOG_LEVEL", "info")
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
PAGE_SIZE = int(os.environ.get("PAGE_SIZE", "20"))
# Note: every environment variable is a string. Convert deliberately.

How it works #

export marks a variable for inheritance. Without it, the variable exists in your shell but child processes never see it — which is a common and confusing cause of "the app cannot find the setting".

Prefixing a command with assignments, as in DEBUG=true python app.py, sets them for that one execution only. It is the cleanest way to try something without changing your shell.

PATH is a list of directories searched in order. Prepending a directory means its programs win, which is exactly how a virtual environment's python takes precedence over the system one.

Everything is a string. DEBUG=false is the non-empty string "false", which is truthy in most languages, so if os.environ.get("DEBUG") is true even when you set it to false. Converting explicitly is essential.

Using os.environ["DATABASE_URL"] for required values means the application refuses to start when it is missing, rather than failing mysteriously on the first query. Failing fast at startup is the behaviour you want.

The env file is loaded by systemd, a container runtime or a library. Its permissions matter: 600 and owned by the service user, because it contains credentials.

Variables set in a shell last only for that session. Persistent values go in the service definition, the container configuration or a shell profile file.

Real-world use #

Environment-based configuration is what makes the same build artefact deployable to development, staging and production. It is one of the twelve-factor principles and every deployment platform assumes it.

Committing a .env file is one of the most common security mistakes. The standard practice is to commit .env.example with placeholder values and add .env to .gitignore.

Secret managers are the next step up. AWS Secrets Manager, Vault and similar services inject values at runtime with rotation and audit logging, which environment variables alone do not provide.

The weakness worth knowing is visibility. A process's environment can be read by root and, on some systems, by the same user through /proc, so environment variables are convenient rather than perfectly secure.

In containers, variables come from the run command, the compose file or the orchestrator's secret mechanism, and the same rules apply.

Common mistakes #

  • Forgetting export, so child processes never see the variable.
  • Committing a .env file with real credentials.
  • Treating "false" as falsy — every environment variable is a string.
  • Using .get() with a default for a required secret, so the app starts misconfigured.
  • Leaving an env file world-readable instead of 600.

Practice #

Write a script that reads one required and two optional environment variables with correct type conversion. Run it with the required variable missing and confirm it fails clearly at startup. Then load the same values from an env file through a systemd unit.

Quick quiz

  1. 1. What does `export` do?

  2. 2. What type is every environment variable?

  3. 3. Why use `os.environ["KEY"]` for a required setting?

  4. 4. What does PATH control?

  5. 5. What permissions should an env file with secrets have?

Summary

  • Environment variables configure an application without changing its code.
  • `export` is what makes them visible to child processes.
  • Every value is a string — convert booleans and numbers deliberately.
  • Fail fast on missing required variables.
  • Keep secrets in a 600 env file or a secret manager, never in git.