env.dev

The Complete .env File Syntax Reference

.env file syntax reference: quoting, comments, multiline values, variable expansion, and the parser differences across Node.js, Python, Ruby, Go, and Docker.

Last updated:

The .env file format has no formal specification. The env file syntax that ships in motdotla/dotenv 17.x for Node.js diverges from theskumar/python-dotenv 1.0, Ruby bkeepers/dotenv 3.2, joho/godotenv 1.5, and the parser inside Docker Compose v2 in small ways that look like bugs but leak passwords or truncate URLs in practice. motdotla/dotenv v15.0.0 (Jan 31, 2022) made # the inline-comment marker on unquoted values by default — the upgrade caught teams pinned to v14 with a # in a password.

This reference documents the real .env file format rules — quoting, comments, multi-line values, variable expansion, and the export prefix — with explicit notes on where each implementation diverges. Use the env validator to check your files against these rules instantly.

What is the basic KEY=VALUE syntax?

Every .env file is a plain-text file where each line defines one environment variable as a KEY=VALUE pair. Keys are uppercase by convention and may contain letters, digits, and underscores. The value starts immediately after the first = character.

bash
DATABASE_HOST=localhost
PORT=3000
API_KEY=sk_live_abc123

Blank lines are ignored. A line that contains only whitespace is also ignored.

How do quoting rules work?

Values can be unquoted, single-quoted, or double-quoted. The quoting style determines how the parser handles special characters, whitespace, and escape sequences.

Unquoted values

The value is read as-is from after the = until end of line. Leading and trailing whitespace is trimmed by most parsers. Inline comments with # may or may not be stripped depending on the implementation.

bash
APP_NAME=my-app
DEBUG=true

Double-quoted values

Double quotes preserve inner whitespace and allow escape sequences. Most parsers interpret \n as a newline, \t as a tab, and \\ as a literal backslash. Variable expansion with ${VAR} is active inside double quotes.

bash
GREETING="Hello, World!"
MESSAGE="Line one\nLine two"
PASSWORD="p@ss#word with spaces"

Single-quoted values

Single quotes treat everything as a literal string. No escape sequences are processed and no variable expansion occurs. Use single quotes when your value contains backslashes, dollar signs, or other special characters that must remain verbatim.

bash
REGEX='\d+\.\d+'
TEMPLATE='Hello $USER, welcome!'
RAW_PATH='C:\Users\admin'

How do I add comments to a .env file?

Put a # at the start of a line — every major parser (Node.js motdotla/dotenv 17.x, python-dotenv 1.0, Ruby bkeepers/dotenv 3.2, Go joho/godotenv 1.5, Docker Compose v2) ignores the line. Leading whitespace before the # is fine. Blank lines are also ignored.

bash
# Database configuration
DB_HOST=localhost

  # Indented comments are also valid
DB_PORT=5432

The trickier question is inline comments — does KEY=value # comment set the value to value or to value # comment? Behavior diverges by library and version, and the divergence has shipped real bugs.

Node.js dotenv (motdotla/dotenv)

Up to v14.3.2, inline comments after unquoted values were preserved verbatim — the value value # comment ended up as "value # comment". v15.0.0 (released Jan 31, 2022) flipped the default and now strips the comment from unquoted values. Inside quoted values # is always literal. This change broke teams that had # characters in passwords pinned to v14.

javascript
// .env
TOKEN=abc#123 # API token
QUOTED_TOKEN="abc#123" # API token

// Node.js with dotenv@17 (or @15+)
require('dotenv').config();
process.env.TOKEN;        // "abc"
process.env.QUOTED_TOKEN; // "abc#123"

Python python-dotenv (theskumar/python-dotenv)

python-dotenv 1.0 strips inline comments from unquoted values when there is whitespace before the # — without that whitespace, # is treated as part of the value. Inside quoted values # is always literal. The whitespace requirement is the inverse of what most readers assume from the Node.js behavior, and it bites when migrating .env files between ecosystems.

python
# .env
TOKEN=abc#123 # API token
NO_SPACE=abc#123# API token
QUOTED_TOKEN="abc#123" # API token

# Python with python-dotenv@1.0
from dotenv import dotenv_values
dotenv_values('.env')
# {'TOKEN': 'abc#123', 'NO_SPACE': 'abc#123# API token',
#  'QUOTED_TOKEN': 'abc#123'}

Docker Compose v2

Compose's env_file parser (compose-spec/compose-go) splits on the literal two characters " #" — same whitespace requirement as python-dotenv. Without a space before the #, the hash stays in the value. Quoted values keep # literal. Same portability rule applies: quote anything with #.

bash
# .env consumed by env_file in docker-compose.yml
TOKEN=abc#123 # API token       # → TOKEN=abc#123
NO_SPACE=abc#123# API token     # → NO_SPACE=abc#123# API token
QUOTED_TOKEN="abc#123" # token  # → QUOTED_TOKEN=abc#123

The portable rule: never rely on inline comments for any value containing #, and quote the value whenever it might. URLs with fragment identifiers are the most common landmine — see the dotenv-not-loading guide for the full failure-mode list.

bash
# Dangerous — may be truncated to "https://example.com/path"
URL=https://example.com/path#anchor

# Safe — quotes preserve the full value
URL="https://example.com/path#anchor"

How do I write multi-line values in a .env file?

Two approaches. The first works in every parser; the second is parser- and version-dependent. The most common real-world need is shipping an RSA or EC private key (TLS certificates, JWT signing, GitHub App credentials) without flattening it onto one line.

Escape sequences inside double quotes (universal)

Embed the literal two characters \n inside double quotes. The parser converts them to a real newline at parse time. Works in Node.js motdotla/dotenv, python-dotenv, Ruby dotenv, and Go godotenv.

bash
MULTILINE="line one\nline two\nline three"

Real newlines spanning multiple lines

Open a double quote, drop in real newlines, close the quote on a later line. Behavior by parser:

  • Node.js motdotla/dotenv — supported since v15.0.0 (Feb 2022). Earlier versions silently truncated at the first newline.
  • python-dotenv — supported since 0.10.0 (2018).
  • Ruby bkeepers/dotenv — supported in all current 2.x and 3.x releases.
  • Go joho/godotenv — supported since v1.5.0 (Feb 4, 2023); v1.5.1 the next day fixed early parser regressions. v1.4.0 (Sep 2021) did not support real newlines.
  • Docker Compose v2not supported in env_file. Compose throws an unexpected character "..." in variable name error. Use the environment: block in your YAML, or base64-encode the value and decode at container start. See the Docker Compose env variables guide for the patterns.
bash
PRIVATE_KEY="-----BEGIN RSA KEY-----
MIIBogIBAAJBALRiMLAH...
-----END RSA KEY-----"

If you have to support Docker Compose and a Node.js or Python service from the same .env file, use the \n-escape form — it survives every parser. For storage and rotation considerations of multi-line secrets, see .env vars security best practices.

How does variable expansion work?

Variable interpolation lets you reference other variables defined earlier in the same file or already present in the environment. Both $VAR and ${VAR} syntax are supported.

bash
BASE_URL=https://api.example.com
# Both forms reference BASE_URL
API_V1=$BASE_URL/v1
API_V2=${BASE_URL}/v2

Variable expansion is disabled inside single quotes. This is consistent across all major implementations. It is enabled in double quotes and unquoted values in Node.js dotenv (with dotenv-expand), Python python-dotenv, and Go godotenv. Ruby dotenv enables it by default in all quoting styles except single quotes.

What about empty and missing values?

There is an important distinction between an empty value and an absent variable.

bash
# Empty string — the variable exists but has no content
DB_PASSWORD=
DB_NAME=""
DB_HOST=''

# These three all set the variable to an empty string ""

If a key is not present in the .env file at all, most dotenv libraries will not set it, and the variable will be undefined (Node.js) or None (Python) unless it was already defined in the system environment.

How are spaces and the = sign handled?

The treatment of whitespace around the = sign differs between implementations. Most dotenv libraries do not allow spaces around the equals sign.

bash
# Correct — no spaces around =
DATABASE_URL=postgres://localhost/mydb

# Risky — spaces around = may cause parse errors or unexpected keys
DATABASE_URL = postgres://localhost/mydb

For unquoted values, most parsers trim trailing whitespace. Leading whitespace after = is also typically trimmed. However, inside quotes, all whitespace is preserved exactly as written. The safest practice is to never rely on trimming — quote values that contain meaningful whitespace.

How do implementations differ across languages?

Because there is no formal specification, each dotenv library makes its own parsing decisions. The following table summarizes the key differences.

FeatureNode.js dotenvPython python-dotenvRuby dotenvGo godotenvDocker Compose
Inline commentsYes (unquoted)Yes (unquoted)Yes (unquoted)Yes (unquoted)Yes (unquoted)
Variable expansionVia dotenv-expandBuilt-inBuilt-inBuilt-inBuilt-in
Multiline (real newlines)Yes (v15+)YesYesYesNo
export prefixIgnoredIgnoredIgnoredIgnoredNot supported
Spaces around =TrimmedTrimmedTrimmedTrimmedNot allowed
Overwrite existing envNo (default)No (default)No (default)No (default)Yes

What does the export prefix do?

Some .env files use export KEY=VALUE so the file can be sourced directly in a shell session. Most dotenv parsers silently strip the export prefix and treat the line normally. Docker Compose is the notable exception — it does not recognize the prefix and will either error or treat the entire string as the key.

bash
# Works in shell (source .env) and most dotenv parsers
export DATABASE_URL=postgres://localhost/mydb
export NODE_ENV=production

# Docker Compose: remove the export prefix
DATABASE_URL=postgres://localhost/mydb

What are the most common gotchas?

  • BOM characters — Some editors (particularly on Windows) insert a Unicode byte order mark (U+FEFF) at the start of the file. This invisible character becomes part of the first key, causing it to silently fail to match. Save your .env as UTF-8 without BOM.
  • Windows line endings (CRLF) — If your file uses \r\n line endings, the \r may become part of the value. Most modern parsers handle this, but older versions may not. Configure your editor to use LF line endings for .env files.
  • Trailing whitespace — Unquoted values may silently include trailing spaces. This is especially dangerous for API keys and connection strings. Double-quote values when precision matters.
  • Inline comments on unquoted values — The line KEY=value # comment sets the value to value in Node.js dotenv, but to value # comment in parsers that do not support inline comments. Always quote values if they might appear alongside a #.
  • Dollar signs in passwords — An unquoted value like PASS=my$ecret will trigger variable expansion in parsers that support it, replacing $ecret with an empty string. Use single quotes: PASS='my$ecret'.
  • Duplicate keys — When a key appears more than once, most parsers use the last occurrence. However, this behavior is not guaranteed. Avoid duplicate keys entirely.

What does a complete .env file look like?

This example demonstrates every syntax feature described above in a single file.

bash
# Basic key-value pairs
NODE_ENV=development
PORT=3000

# Double-quoted value with spaces and escape sequences
GREETING="Hello, World!"
MULTILINE="first line\nsecond line"

# Single-quoted literal value — no interpolation
REGEX='\d{3}-\d{4}'

# Variable expansion (requires dotenv-expand in Node.js)
BASE_URL=https://api.example.com
FULL_URL=${BASE_URL}/v2/users

# Empty values
OPTIONAL_FLAG=
ALSO_EMPTY=""

# Export prefix (stripped by most parsers, not Docker Compose)
export LOG_LEVEL=debug

# Multiline value with real newlines
PRIVATE_KEY="-----BEGIN EC KEY-----
MHQCAQEEIBkg...
-----END EC KEY-----"

References

Related on env.dev

Was this helpful?

Read next

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.

Continue →

Frequently Asked Questions

Can .env files have comments?

Yes. Lines starting with # are treated as comments in all major dotenv implementations. Inline comments (KEY=value # comment) are supported by some libraries (Python, Ruby) but not all (Node.js dotenv). Avoid inline comments for maximum portability.

Do I need to quote values in .env files?

Unquoted values work for simple strings without spaces or special characters. Use double quotes for values with spaces, newlines (\n), or variable expansion ($VAR). Use single quotes for literal strings where no expansion should occur.

Does .env support multiline values?

Yes, in double quotes. Use \n for newline characters or span multiple lines by keeping the opening and closing quotes. Exact support varies by implementation — Node.js dotenv supports both approaches.

Are .env files case-sensitive?

Yes. Keys are case-sensitive — DB_HOST and db_host are two distinct variables. By convention, environment variable names are uppercase with underscores (SCREAMING_SNAKE_CASE), but parsers do not enforce this. Mixed case works but invites bugs when one machine reads DB_HOST and another reads Db_Host.

What is the difference between export KEY=VALUE and plain KEY=VALUE?

In a shell script, export marks the variable for inheritance by child processes; plain KEY=VALUE only sets it in the current shell. Inside a .env file, most dotenv parsers (Node.js, Python, Go, Ruby) silently strip the export prefix and treat both forms identically. Docker Compose is the exception — it does not recognize export and will fail or treat the entire string as the key. Keep export for files you also source directly with `source .env`.

Stay up to date

Get notified about new guides, tools, and cheatsheets.