A .env file is a plain text file that stores configuration values as KEY=VALUE pairs, one per line. It is the standard way to keep secrets and environment-specific settings — database URLs, API keys, ports — out of your source code. Almost every modern application framework supports reading it.
Try it directly in your browser:
Validate your JSON →The syntax looks trivial, but the rules around quotes, comments, and special characters trip people up constantly. The same .env file can behave differently in Node.js, Python, and Docker if you assume parsing is universal. This guide walks through the actual format and where it diverges.
The basic syntax
Each non-empty, non-comment line is a key-value pair separated by a single =:
DATABASE_URL=postgres://localhost:5432/myapp PORT=3000 NODE_ENV=development API_KEY=sk_test_abc123
The key is a string of letters, digits, and underscores. Conventionally it is uppercase with words separated by underscores. The value runs from the first character after = to the end of the line.
Comments
Lines starting with # are ignored. Comments can also appear at the end of a line, but only when the value is unquoted:
# Database configuration DATABASE_URL=postgres://localhost:5432/myapp PORT=3000 # development port # Stripe (test mode only) STRIPE_KEY=sk_test_abc123
If your value contains a # character, you must quote it — otherwise the parser will truncate at the # and treat the rest as a comment.
When quotes are required
Quotes are optional for simple values. You need them in three cases:
1. The value contains spaces
# Wrong — parser may stop at the first space GREETING=Hello World # Right GREETING="Hello World"
2. The value contains a # character
# Wrong — value becomes "secret" PASSWORD=secret#1234 # Right — value is "secret#1234" PASSWORD="secret#1234"
3. The value spans multiple lines
Modern dotenv versions (Node.js dotenv v15+, python-dotenv) support multiline values inside double quotes:
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA1234567890abcdefghijklmnop... -----END RSA PRIVATE KEY-----"
Older parsers do not support this. The portable alternative is to use escape sequences and a single line:
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----"
Single quotes vs double quotes
The distinction matters when your value contains escape sequences or dollar signs:
| Type | Escape sequences | Variable expansion |
|---|---|---|
| Unquoted | No | Sometimes (parser-dependent) |
Single quotes '...' | No — taken literally | No |
Double quotes "..." | Yes — \n becomes a newline | Yes (parser-dependent) |
Example showing the difference:
LITERAL='line one\nline two' # value contains a literal backslash + n ESCAPED="line one\nline two" # value contains a real newline
Variable expansion
Many parsers let you reference other variables already defined in the file:
BASE_URL=https://api.example.com
USERS_ENDPOINT=${BASE_URL}/users
ORDERS_ENDPOINT=${BASE_URL}/orders
Behaviour varies — Node.js dotenv does not expand by default (you need the dotenv-expand package), while Docker Compose and python-dotenv do expand ${VAR} automatically. Read your parser's docs before relying on this.
Common mistakes
Spaces around the equals sign
# Wrong — most parsers fail or include the space in the key PORT = 3000 # Right PORT=3000
Forgetting to quote values with #
API keys and tokens often contain #. Without quotes, your secret silently becomes shorter than expected and authentication fails with no obvious cause.
Trailing whitespace
Most parsers strip trailing whitespace from unquoted values, but not all do. If you copy a value from a web form, check there is no invisible trailing space — it can be included in the value and cause comparison failures.
Wrong file name
The file must be named exactly .env (with the leading dot, no extension). Common mistakes: env, .env.txt, env.local. Some frameworks support variants like .env.local, .env.production, or .env.development — check your framework docs.
Loading from the wrong directory
Node.js dotenv reads from process.cwd(), not from the script's location. Running node ./src/server.js from the project root works; running it from inside ./src/ does not unless you pass an explicit path.
Loading .env in different environments
Node.js
Install the dotenv package and load it as early as possible:
// At the very top of your entry file
require('dotenv').config();
console.log(process.env.DATABASE_URL);
From Node.js 20.6+, you can skip the package entirely and pass the file to the runtime:
node --env-file=.env server.js
Python
Install python-dotenv and call load_dotenv() before reading any environment variables:
from dotenv import load_dotenv
import os
load_dotenv()
print(os.getenv("DATABASE_URL"))
Docker
Docker has two related but distinct mechanisms. The --env-file flag passes variables into the container at runtime:
docker run --env-file .env myimage
Docker Compose also reads .env automatically, but it uses it for variable substitution inside docker-compose.yml — not to inject variables into containers. To get variables into a container with Compose, use the service-level env_file directive:
services:
app:
image: myapp
env_file:
- .env
Docker's .env parser is stricter than Node.js dotenv. It does not support multiline values and treats quotes as part of the value rather than stripping them. Test your .env in the environment that will actually consume it.
Security: never commit your .env
A .env file is, by definition, where secrets live. Add it to .gitignore on day one:
# .gitignore .env .env.local .env.*.local
To help teammates know which variables they need, commit a .env.example file with the keys but no real values:
# .env.example DATABASE_URL= PORT=3000 STRIPE_KEY=sk_test_xxxxxxxxxxxxxxxx JWT_SECRET=
If you accidentally commit a .env with real credentials, rotate every secret in it immediately. Removing the file from a later commit does not erase it from git history — anyone who cloned the repo still has the secrets.
A complete, well-formed example
# Application NODE_ENV=production PORT=8080 LOG_LEVEL=info # Database DATABASE_URL="postgres://user:p@ss#word@db.internal:5432/myapp" DATABASE_POOL_SIZE=20 # Third-party services STRIPE_KEY=sk_live_abc123def456 SENDGRID_API_KEY=SG.realKeyHere SLACK_WEBHOOK="https://hooks.slack.com/services/T000/B000/abcdefg" # Multi-line secret (dotenv v15+) JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA... -----END RSA PRIVATE KEY-----"
Notice that the database URL is quoted because the password contains a #. The Slack webhook is quoted out of habit even though it doesn't strictly need to be. The private key uses multiline double-quoted format.
Working with config files?
If you also store configuration as JSON or YAML, use the formatter to clean up structure before committing.
JSON Formatter JSON to YAMLFrequently Asked Questions
Why is my .env file not being loaded?
Most often the file is in the wrong directory. Node.js dotenv looks for .env in the current working directory (process.cwd()), not next to the script. Run your app from the project root, or pass an explicit path: require('dotenv').config({ path: '/abs/path/.env' }). Also check the file is named exactly .env — not env or .env.txt.
Do I need quotes around values in a .env file?
No, quotes are optional for simple values. You need them when the value contains spaces, the # character (which would otherwise start a comment), or multiline content. Double quotes also enable escape sequences like \n. Single quotes preserve the value literally, with no escape processing.
Why do spaces around the equals sign break my .env file?
Most parsers treat the line as strict KEY=VALUE with no surrounding spaces. Writing KEY = value produces a key of "KEY " (with a trailing space) or fails to parse depending on the library. Always use KEY=value with no spaces around the equals sign.
Should I commit my .env file to git?
No. .env files contain secrets — API keys, database passwords, signing keys — that should never enter version control. Add .env to your .gitignore. Commit a .env.example file with the variable names but empty or placeholder values so teammates know which variables to set.
Why does Docker treat my .env differently from Node.js dotenv?
Docker's .env parser is stricter and supports fewer features. It does not handle multiline values, does not perform escape-sequence processing inside double quotes the same way, and treats quotes as part of the value rather than stripping them. If a .env works in your Node.js app locally but breaks inside a Docker container, the quoting and multiline rules are the most likely cause.
Ready to validate your json?
Open JSON Validator →