Key idea
An environment variable is a name and a text value that a program is given when it starts. Your code reads it by name; whatever started the program (your shell, a .env loader, a hosting platform) decides the value.
Reading them in code
Every language can read them. The value is always text, or missing.
// Node.js
const port = Number(process.env.PORT ?? 3000);
const apiKey = process.env.API_KEY;
# Python
import os
port = int(os.environ.get("PORT", "3000"))
api_key = os.environ["API_KEY"] # raises KeyError if it's missing
Two habits help. Give optional settings a sensible default, like the port above. For required ones, such as a key, fail at start-up with a clear message instead of carrying on without it.
Because values are text, DEBUG=false is the string "false", which counts as true in both languages. Compare it to a string: process.env.DEBUG === "true".
Naming them
Names are case-sensitive on macOS and Linux, so api_key and API_KEY are different variables. The convention is upper case with underscores, and letters, digits and underscores only, not starting with a digit. Hosting platforms usually set a few for you, such as PORT, the port your app should listen on (lesson 1.4.3).
Pick one name per setting and use it everywhere: in your code, your .env file and the platform's settings.
Setting them in your shell
A variable set in a terminal applies to programs started from that terminal, until you close it.
export API_KEY="demo-not-a-real-key-0000"
node server.js
For one command only, put it in front: LOG_LEVEL=debug node server.js.
$env:API_KEY = "demo-not-a-real-key-0000"
node server.js
To check it, run $env:API_KEY.
.env files, for your own machine
Typing every variable each time gets old. A .env file keeps them in one place for local development:
# .env: local development only, never committed
DATABASE_URL=postgres://app:localpass@localhost:5432/shop
API_KEY=demo-not-a-real-key-0000
LOG_LEVEL=debug
Your app doesn't read this file by itself. Something has to load it: node --env-file=.env server.js (Node 20.6 or later), the python-dotenv package, or your framework, which often does it for you.
Two rules keep it safe:
- Add
.envto.gitignorebefore your first commit, so it never reaches the repository. - Commit a
.env.exampleinstead, with every name and a placeholder value, so a teammate knows what to set.
On a hosting platform, there's no .env file. You set the same names in its settings, and it passes them to your app when it starts.
Where did PATH and HOME come from?
Your shell already has dozens of variables, such as PATH (where to look for programs) and HOME (your user folder). Run env on macOS or Linux, or Get-ChildItem env: in PowerShell, to list them. A program inherits a copy of its parent's variables. That's why export in one terminal doesn't reach another, and why a program can't change the variables of the shell that started it.
Check yourself