env.dev

The .env File: A Complete Guide to Environment Variables

Everything about .env files: syntax rules, parser quirks, language examples (Node, Python, Go, Docker), best practices, and the gotchas that bite.

By env.dev Updated

What is a .env file? It's a plain-text configuration file that stores environment variables as KEY=value pairs, one per line, loaded into your process at startup so secrets and config never get hardcoded into source. The pattern started as a Ruby gem from Brandon Keepers in 2012, crossed to Node.js the next year via Scott Motte's motdotla/dotenv, and now ships with parsers for every major language. As of Node.js 20.6 (August 2023), you don't even need a library — node --env-file=.env app.js reads the file natively. Bun auto-loads .env with no flag at all; Deno requires both --env-file and --allow-env for sandbox reasons (see the Bun vs Deno vs Node comparison).

TL;DR

  • A .env file stores config as KEY=value pairs loaded into the process at startup. It belongs in .gitignore — committing one is the fastest way to leak production credentials.
  • Since Node.js 24.10 and 22.21 (late 2025), node --env-file=.env app.js loads .env natively and is no longer experimental — the dotenv library is optional for simple setups.
  • Native --env-file does not expand ${VAR} references; if one variable builds on another, keep dotenv-expand or restructure the file.
  • .gitignore stops commits, not disk reads — AI coding agents (Claude Code, Cursor, Copilot) read plaintext .env straight off disk. dotenvx (6.5M weekly npm installs) encrypts the file so it is safe to commit and opaque to those tools. To actually hand secrets to a teammate, don't commit or paste them — share through a single-use link like send.env.dev (covered below).

How do .env files work?

At process startup, a dotenv parser reads the file line by line and injects each KEY=value pair into the OS environment. In Node.js the values appear on process.env, in Python on os.environ, and so on. The file itself sits next to your code during local development, never gets deployed, and belongs in .gitignore — committing one is the fastest way to leak production credentials.

The pattern descends from the 12-Factor App methodology, published by Heroku co-founder Adam Wiggins in 2011, which says configuration should be strictly separated from code and stored in the environment. There is no formal RFC for the format itself, which is why parser quirks bite in production: a stray space in KEY = value silently breaks the Node.js parser but works fine when sourced from a shell.

What is the .env file syntax?

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.

ini
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.

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

Comments and empty values

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

ini
# Database config
DB_PASSWORD=
DB_NAME=""

Multiline and variable expansion

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

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

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

For an exhaustive parser-by-parser reference — every quoting rule, escape behaviour, and interpolation quirk across Node.js, Python, Ruby, Go, and Docker — see the .env file syntax reference. Here is a complete example:

ini
# 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

Two of those keys are worth knowing beyond this file: NODE_ENV changes how Express, React, and most build tools behave, and DATABASE_URL has a URI format with its own escaping rules for special characters in passwords.

How do I load .env files in different languages?

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

Do you still need the dotenv library in 2026?

Not always. The runtime now loads .env for simple cases, but the library still wins when you need variable interpolation or encrypted, commit-safe secrets. Here is how the four common approaches compare.

ApproachHow to loadExpands ${VAR}Safe to commitBest for
dotenv libraryimport 'dotenv/config'With dotenv-expandNoThe mature default; any Node version
Native --env-filenode --env-file=.envNoNoZero-dependency loading on Node 24.10 / 22.21+
dotenvxdotenvx run -- node app.jsYesYes (encrypted)Committed secrets; keeping AI tools out
Bun / Deno autoloadBun: automatic; Deno: --env-file --allow-envRuntime-dependentNoApps already on Bun or Deno

Opinionated take: for a fresh Node.js 24 service with no cross-variable references, prefer native --env-file — one fewer dependency and nothing to import. Keep dotenv when a value is built from another, like FULL_URL=${BASE_URL}/v1, which the native loader leaves untouched. Reach for dotenvx when secrets must live safely in git or stay out of an AI agent's reach — its public-key encryption (ECIES, AES-256) keeps the committed file opaque, even though .gitignore never stops a tool from reading the plaintext off disk. For the full Node.js loading story — precedence, process.loadEnvFile(), and CI patterns — see the Node.js environment variables guide.

State of the ecosystem, June 2026

  • Node.js 24 ("Krypton") is the Active LTS line — 24.16.0 shipped in May 2026 — and --env-file has been stable on it since 24.10. For production, where no .env usually exists, use --env-file-if-exists=.env (added in Node 22.9): it skips a missing file instead of refusing to start.
  • process.loadEnvFile() still throws when the file is missing — wrap it in try/catch; a non-throwing option is an open request in nodejs/node#61086 (December 2025).
  • The dotenv library sits at 17.4.2 with roughly 121M weekly npm downloads. The v17.0.0 release (2025) turned on runtime log output by default — pass config({ quiet: true }) or set DOTENV_CONFIG_QUIET=true to silence it.

What are .env file 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.). For local team workflows, a dev container can mount secrets from the host instead of committing a checked-in .env.
  6. Use per-environment files. Many frameworks support .env.development, .env.production, and .env.test to keep environments cleanly separated — Vite's mode system is the canonical example.

What are common .env file 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 — see the .env file syntax reference for the full per-parser comparison. Always test with your specific parser.

If your variables are unexpectedly undefined at runtime, the most common causes — load order, working directory, production overrides — are walked through in the dotenv not loading debugging guide.

How do you share .env files securely?

Never paste a .env into Slack, email, Discord, or a ticket comment — those channels keep the message indexed forever and sync it to every device the recipient ever signs into. The zero-friction option is send.env.dev: paste the file, get a single-use link, share it, and the link burns on first read. Encryption happens in your browser (the URL fragment carries the key, which browsers never send to servers), hosting is in the EU, and there is no account or CLI to install.

For continuous secret distribution to running apps and CI — not one-off hand-offs — you want a secrets manager instead: 1Password / Bitwarden CLI under 20 people, Doppler or Infisical past that, HashiCorp Vault when compliance demands dynamic credentials. The full sharing guide compares all of them with cost, security, and setup-effort columns.

References

Where can I learn more about environment variables?

Was this helpful?

Frequently Asked Questions

Can Node.js load .env files without the dotenv library?

Yes. Since Node.js 24.10 and 22.21 (late 2025), the --env-file flag is stable: node --env-file=.env app.js populates process.env natively, and process.loadEnvFile() does the same from code. The native loader does not perform variable interpolation, so keep dotenv with dotenv-expand if one variable is built from another.

What is a .env file?

A .env file is a plain text configuration file that stores environment variables as KEY=VALUE pairs. It is used to keep sensitive configuration like API keys, database URLs, and feature flags out of source code.

Should .env files be committed to Git?

No. The .env file should be listed in .gitignore to avoid exposing secrets. Instead, commit a .env.example file with placeholder values so team members know which variables are required.

Can .env files have comments?

Yes. Lines starting with # are treated as comments in most dotenv implementations. Inline comments (KEY=value # comment) are supported by some libraries but not all — avoid them for portability.

What is the dotenv library?

dotenv is a library (available for Node.js, Python, Ruby, and other languages) that reads a .env file and loads its variables into the process environment. It is typically called at application startup before accessing any environment variables.

How do I stop node --env-file from failing when .env is missing?

Use --env-file-if-exists=.env (Node 22.9+, stable since 24.10 / 22.21): it loads the file when present and silently skips it otherwise, which is what you want in production where no .env exists. The programmatic process.loadEnvFile() still throws on a missing file, so wrap it in try/catch — a non-throwing option is tracked in nodejs/node#61086.

Why does dotenv print log output to the console since v17?

dotenv 17.0.0 (2025) turned on runtime logging by default — it announces how many variables were injected, plus occasional tips. Silence it with require("dotenv").config({ quiet: true }) or by setting DOTENV_CONFIG_QUIET=true. The current release is 17.4.2.

Stay up to date

Get notified about new guides, tools, and cheatsheets.