.env & dotenv Cheatsheet - Load Order & Multi-Env
Almost every Node/Python/frontend project writes a .env, yet the most common traps are spaces around the equals sign, a # inside a value treated as a comment, and secrets accidentally committed to the repo. This reference covers 5 groups: variable definition, reading in Shell/Node/Python, dotenv loader usage, multi-environment file layout (.env/.env.local/.env.production), and a set of security-pitfall reminders. Sanity-check the format after writing and confirm no secrets were committed before deploying. After reading you can organize environment variables cleanly and avoid the most common leakage issues.
Typical Use Case
For developers separating configuration and secrets from code: a .env file for local work, real environment variables in production, and a committed .env.example so the team agrees on field names. This sheet covers variable definition and reading, access syntax per language and shell, loading with dotenv, multi-environment file conventions, and four classes of security pitfalls. The single decision rule: real secrets never enter version control.
Definition 6
KEY=valueKEY="value with space"KEY='literal $NOT_EXPANDED'KEY=${OTHER}_suffix# commentexport KEY=valueReading 5
echo $KEYecho %KEY%$env:KEYprocess.env.KEYos.getenv("KEY")dotenv Usage 4
require('dotenv').config()import 'dotenv/config'dotenv.config({ path: '.env.local' })python-dotenv load_dotenv()Multi-environment Files 5
.env.env.local.env.development.env.production.env.exampleSecurity FAQ 4
Don't commit secretsCase sensitiveType conversionEscape newlinesParameter matrix
| Parameter | Effect | Example |
|---|---|---|
KEY=value | Defines a variable; there must be no space on either side of the equals sign | KEY=value |
KEY="a b" | Quote the value when it contains spaces or special characters | KEY="value with space" |
KEY='$RAW' | Single quotes suppress variable expansion | KEY='literal $NOT_EXPANDED' |
export KEY=value | Sets a variable and exports it, so child processes inherit it | export DATABASE_URL=postgres://... |
$KEY / %KEY% / $env:KEY | The read syntax on the three platforms: shell, cmd and PowerShell | echo $KEY |
dotenv.config() | Loads the .env file into the process environment inside Node | require('dotenv').config() |
import 'dotenv/config' | The ESM form of loading a .env file | import 'dotenv/config' |
.env.local | A local override that takes precedence over .env | dotenv.config({ path: '.env.local' }) |
.env.<mode> | One file per environment: development and production | .env.production |
.env.example | A committable example template with the real secrets left out | cp .env .env.example |
os.getenv("KEY") | Read from Python, where an unset variable comes back as None | os.getenv("DATABASE_URL") |
process.env.KEY | Read from Node, where an unset variable comes back as undefined | process.env.PORT |
Common pitfalls
SymptomThe variable is right there in the file but reads back empty.
CauseThere are spaces around the equals sign, so KEY = value parses as running the command KEY, or the variable was never exported.
FixWrite KEY=value with no spaces at all and add export so child processes can see it.
SymptomA secret made it into Git history, and deleting the file now does not help.
Cause.env was never added to .gitignore, or the ignore rule was added only after the file had already been tracked.
FixRotate the key first, since an ignore rule cannot retract leaked content; then git rm --cached .env to drop it from the index and add the .gitignore entry.
SymptomA cross-platform script fails on Windows while working fine on Linux.
CauseThe shell reads $KEY, whereas cmd needs %KEY%.
FixKeep cross-platform scripts from reading variables directly, or handle the two syntaxes separately.
SymptomThe value comes back as the string "8080" rather than a number, and comparisons blow up on the type.
CauseEnvironment values are always strings and carry no type information whatsoever.
FixConvert explicitly: wrap the PORT lookup in Number(), or hand the string to parseInt with base 10.
SymptomA multi-line value such as a private key is read back as only its first line.
CauseThe newlines were never escaped, so parsing stops at the first line break.
FixWrap it in quotes and write the real newlines as \n, or store it base64-encoded or in a secrets manager.
SymptomI edited .env.local but the application still sees the old value.
Causedotenv does not overwrite a variable that already exists in the environment by default.
FixPass override: true to dotenv.config, or make sure the load order is what you intended.
Troubleshooting
1Confirm the variable really exists in the current shell
echo $KEYNo output means it was never exported or .env was never loaded; set | grep KEY works as well.
2Load a file into the current session temporarily
set -a && source .env && set +aset -a auto-exports everything source defines and +a turns that off; it only affects the current shell.
3Check whether Node really sees the value
node -e "console.log(process.env.KEY)"Bypassing the application and asking the runtime directly separates a load failure from code that is reading the wrong field.
4Produce a secret-free template for committing
sed -E "s/=.*/=/" .env > .env.exampleKeep the key names and blank out the values; confirm .env is listed in .gitignore.
5Check whether a secret has leaked into the repository
git log -S "SECRET_VALUE" --onelineAny hit means it exists in history and the key must be rotated; editing .gitignore only protects the future.
Command Examples
Define a variable and export it for child processes
export DATABASE_URL=postgres://localhost:5432/appNo spaces around the equals sign, and a value that is merely assigned without export stays invisible to child processes.
Load a .env file from an explicit path
dotenv.config({ path: '.env.local' })Without the path option it reads .env from the project root; supply it and only that file is loaded, with no automatic merge.
Reading syntax on each of the three platforms
echo $KEY
# Linux/macOS
echo %KEY%
# Windows cmd
$env:KEY
# PowerShellWhen the same configuration has to run everywhere, even the read syntax differs, so a script must not assume there is only one form.
Load and read a variable in Python
from dotenv import load_dotenv
load_dotenv()
os.getenv("API_HOST")A missing variable yields None, so any check that configuration loaded properly has to handle that None case first.
Common Pitfalls
- Keep .env out of version control and ship .env.example as the field template instead; real secrets never belong in a repository.
- Leave no spaces around the equals sign, since KEY = value assigns nothing, and an ALL_CAPS_WITH_UNDERSCORES naming style avoids most mistakes.
- Every value is a string, so numbers and booleans must be converted yourself; note that "false" is truthy in JavaScript and a naive check inverts your logic.
- Names are case-sensitive, and KEY and key are two different variables on Linux and macOS, so match the case exactly while hunting for an empty value.
- Multi-line content such as private keys or certificates must be wrapped in quotes or written with explicit escape sequences, otherwise you only ever read the first line.
Tips
- No spaces around = (KEY=value is correct, KEY = value is wrong).
- Add .env to .gitignore; commit secrets via .env.example template instead.
- All values are strings; convert numbers with Number()/parseInt().
- Multi-line values use quotes or \n for newlines.
FAQ
The variable is right there in .env but reads back empty. What gives?
Three causes account for most of these, roughly in order: a space on one side of the equals sign, which turns KEY = value into a single token rather than an assignment; the value was set but never exported, so child processes never see it; and a case mismatch, since KEY and key are two unrelated variables and the lookup is case-sensitive.
Why does my port come back as a string and blow up on comparison?
Every value in a .env file is text; there is no type annotation, so PORT=8080 yields the string "8080". Comparing or doing arithmetic on that fails at runtime unless you convert it yourself: Number(process.env.PORT) in Node, int(os.getenv("PORT")) in Python. The same trap applies to booleans, since the string "false" is truthy in JavaScript.
A multi-line value such as a private key only ever gives me the first line. Why?
A .env file is parsed line by line, so a raw newline terminates the value: the remainder becomes a new line that is either misread as another variable or dropped entirely. Wrap the value in quotes or express the line breaks with explicit escape sequences so the whole thing stays on one physical line.
If both .env and .env.local exist, which one wins?
.env.local takes precedence, acting as a local override for values only your machine should have, such as a connection string pointing at a local database. Shared baseline values belong in .env, per-environment splits go to .env.development and .env.production, and .env.example is the template the team agrees on, with no real secrets in it.
How do Node and Python actually read values from a .env file?
In Node, load the file with require('dotenv').config() first, after which values are read as process.env.KEY; in ESM projects the equivalent is the side-effect import of dotenv/config. In Python, python-dotenv's load_dotenv() populates the environment and you read through os.getenv("KEY"), which returns None when the variable is absent. In both languages the load has to happen before the read.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Aug 2, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us