env.dev

The .env File: A Complete Guide to Environment Variables

Everything you need to know about .env files: syntax, usage across languages, best practices, and common pitfalls.

A .env file is a simple text file that stores environment variables as key-value pairs. It has become the de facto standard for local development configuration across every major framework — from Node.js and Python to Go, Ruby, and Docker. Instead of hardcoding secrets and config into your source code, you place them in a .env file that is loaded at startup and kept out of version control.

What is a .env file?

The concept was popularized by the dotenv library for Node.js, though implementations now exist for virtually every language. The idea is rooted in the 12-Factor App methodology, which states that configuration should be strictly separated from code and stored in the environment.

When your application starts, a dotenv parser reads the .env file and injects its key-value pairs into the process environment. In Node.js they become available via process.env, in Python via os.environ, and so on. The file itself is never deployed — it exists only on the developer's machine or in a CI pipeline.

Syntax Reference

The .env format is intentionally simple, but there are a few rules worth knowing:

Basic assignment

Keys are uppercase by convention. There must be no spaces around the = sign.

DATABASE_HOST=localhost
PORT=3000

Quoted values

Double quotes allow spaces and special characters. Single quotes treat the value as a literal string with no interpolation.

GREETING="Hello, World!"
REGEX='\d+\.\d+'

Comments and empty values

Lines starting with # are comments. KEY= sets an empty string, as does KEY="".

# Database config
DB_PASSWORD=
DB_NAME=""

Multiline and variable expansion

Some parsers support multiline values in double quotes and variable expansion with ${} syntax.

PRIVATE_KEY="-----BEGIN RSA KEY-----
MIIBogIBAAJBALRi...
-----END RSA KEY-----"

BASE_URL=https://api.example.com
FULL_URL=${BASE_URL}/v1/users

Here is a complete example:

# App
NODE_ENV=development
PORT=3000

# Database
DATABASE_URL="postgres://user:pass@localhost:5432/mydb"
DATABASE_POOL_SIZE=10

# External APIs
STRIPE_SECRET_KEY=sk_test_abc123
SENDGRID_API_KEY="SG.xxxx"

# Feature flags
ENABLE_CACHE=true

Language Examples

Every major language has a dotenv library. Here is how to get started in the most popular ones:

LanguageSetupAccess
Node.jsimport 'dotenv/config'process.env.KEY
Pythonfrom dotenv import load_dotenvos.getenv('KEY')
Gogodotenv.Load()os.Getenv("KEY")
Dockerenv_file: .env in composeInjected into container environment

Best Practices

  1. Never commit .env to git. Add it to your .gitignore immediately. Leaked secrets are the number one cause of security incidents.
  2. Provide a .env.example. Commit a template file with dummy values so new team members know which variables are required.
  3. Validate at startup. Fail fast if a required variable is missing or malformed. Libraries like envalid (Node.js) or pydantic-settings (Python) make this easy.
  4. Use typed configuration objects. Instead of sprinkling process.env.X throughout your codebase, parse variables into a typed config object at the entry point.
  5. Separate secrets from config. Non-sensitive values like PORT or LOG_LEVEL can live in .env, but production secrets belong in a secrets manager (Vault, AWS SSM, etc.).
  6. Use per-environment files. Many frameworks support .env.development, .env.production, and .env.test to keep environments cleanly separated.

Common Pitfalls

  • Whitespace around =KEY = value will break most parsers. Always use KEY=value with no spaces.
  • Unquoted values with #URL=https://x.com#anchor will be truncated at the # because it is treated as an inline comment. Wrap the value in quotes instead.
  • .env is NOT for production secrets — In production, use your platform's native secrets management (e.g. Kubernetes Secrets, AWS Parameter Store, Cloudflare Worker secrets). A .env file on a server is a plain-text liability.
  • Parser inconsistencies — The dotenv format has no formal spec. Behavior differs between dotenv, dotenvx, Docker, and shell sourcing. Always test with your specific parser.

Deep Dive