DEV Community

pickuma
pickuma

Posted on • Originally published at pickuma.com

Environment Variables and PATH, Explained

Type node and the right program runs. Set DATABASE_URL and your app connects without a single credential in the source. Both of those are environment variables doing quiet work, and understanding them removes a whole category of confusing errors.

What an environment variable is

An environment variable is a named string the operating system attaches to a running process. Each process gets its own set, stored as simple KEY=value pairs — there are no numbers, booleans, or nested objects at this level, just text. Your program reads them through whatever its language exposes: process.env.HOME in Node, os.environ["HOME"] in Python, $HOME in a shell.

The detail that makes them useful is inheritance. When a process starts a child process, the child receives a copy of the parent's environment. Your shell has an environment; every command you launch from it inherits that environment; anything those commands launch inherits it in turn. This is why exporting a variable in your terminal makes it visible to the programs you then run there — and why it vanishes when you close the terminal, since that process and its environment are gone.

A few names show up almost everywhere. HOME points at your home directory. USER (or USERNAME on Windows) holds your login name. SHELL names your default shell, PWD your current directory, and LANG your locale. You rarely set these yourself; the system populates them at login.

You set your own variables in a few standard ways. In a shell, export NAME=value makes the variable available to child processes for that session (plain NAME=value without export keeps it local to the shell itself). To persist it, add the export line to a startup file like ~/.bashrc or ~/.zshrc. For applications, a .env file holds project-specific values that a loader reads at startup. In CI and production, the same values are injected as pipeline secrets or platform-managed environment settings.

Why config and secrets live here

The reason this pattern is everywhere is separation. Your code describes behavior; the environment describes the situation it runs in — which database, which API key, which log level. Keeping those apart means the exact same build runs in development, staging, and production with nothing changed but the environment around it.

This is the idea behind the "config in the environment" rule popularized by the Twelve-Factor App methodology: anything that varies between deploys should live in the environment, not in the code. The practical payoff is concrete. Secrets stay out of version control, so a leaked repository does not leak your production keys. Rotating a credential is an environment change and a restart, not a code change and a redeploy. And there is no risk of a developer's local database URL accidentally shipping to production, because the URL was never in the code to begin with.

Storing a secret in an environment variable keeps it out of your source code, which is the main goal — but the value itself is plain text. Any process that can read the environment (or run ps, printenv, or a crash reporter that dumps the environment) can read the secret. They are a deployment convention, not a vault. For high-value secrets, layer a real secrets manager on top.

PATH: how the shell finds commands

PATH is the single most consequential environment variable, and it works exactly like the others — it is just a string. Its value is a list of directories joined by colons on macOS and Linux (semicolons on Windows):

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin
Enter fullscreen mode Exit fullscreen mode

When you type a bare command like git, the shell does not search your whole disk. It walks the directories in PATH from left to right and runs the first executable named git it finds. If it reaches the end of the list without a match, you get the familiar command not found. That error is rarely about a missing program — usually the binary exists but its directory is not on PATH. (You can ask the shell which file it would actually run with which git or, in many shells, type git.)

To make a new tool runnable, you add its directory to PATH. Prepending puts it ahead of everything else:

export PATH="$HOME/.local/bin:$PATH"
Enter fullscreen mode Exit fullscreen mode

The order matters more than it looks.

Because PATH is searched left to right and the search stops at the first match, a directory near the front shadows the same-named binary further back. This is exactly how version managers (nvm, pyenv, asdf) work — they prepend a shims directory so their managed python or node is found before the system one. It is also why a stray executable early in your PATH can silently hijack a command. When which shows a surprising location, your PATH order is usually the reason.

FAQ


Originally published at pickuma.com. Subscribe to the RSS or follow @pickuma.bsky.social for new reviews.

Top comments (0)