env.dev

.env File Syntax Rules: Quoting, Comments, Multiline

Compare measured .env parsing in Node, npm dotenv, Python, Docker Compose, and Docker CLI: whitespace, comments, quotes, multiline values, and expansion.

By env.dev Updated

An .env file has no universal parser. Whitespace, comments, quotes, and dollar expressions depend on the loader. Node's native parser, the npm dotenv package, Python dotenv, Compose interpolation, and Docker env-file loading are distinct mechanisms. A file accepted by one can change meaning in another.

Start with the .env guide for loading a file. Use the env validator to check our documented literal syntax, compare keys with .env.example, or generate a value-free example. It does not emulate every runtime.

What did the pinned parser comparison show?

We ran synthetic fixtures on Linux aarch64 on September 14, 2026: Node 24.21.0 using process.loadEnvFile(), npm dotenv 17.2.2, CPython 3.13.7 with python-dotenv 1.1.1, Docker Compose 2.39.4, and the Docker CLI 28.5.1 ParseEnvFile implementation. Python used its default interpolation setting. Each row was a separate file, with an isolated environment for the variables under observation.

Observed parser results, September 14, 2026
FixtureNode nativenpm dotenvPython dotenvCompose interpolationCompose env_fileCompose rawDocker CLI
Unterminated double quotekeep opening quotekeep opening quotewarn and skiperrorerrorkeep opening quotekeep opening quote
Double-quoted backslash-nnewlinenewlinenewlinenewlinenewlineliteral + quotesliteral + quotes
Surrounding whitespacetrimtrimtrimtrimtrimerrorerror
Unquoted alpha#betaalphaalphaalpha#betaalpha#betaalpha#betaalpha#betaalpha#beta
Unquoted alpha # betaalphaalphaalphaalphaalphaalpha # betaalpha # beta
Quoted valuesremove quotesremove quotesremove quotesremove quotesremove quoteskeep quoteskeep quotes
Quoted physical newlineacceptacceptacceptacceptaccepterrorerror
CRLF and empty valuesacceptacceptacceptacceptacceptacceptaccept
Duplicate keylast winslast winslast winslast winslast winslast winsboth entries¹
MALFORMED LINE then VALID=afterskip malformedskip malformedwarn and skiperrorerrorerrorerror
export KEY=valueacceptacceptacceptacceptaccepterrorerror
Dollar referenceliteralliteralexpandexpandexpandliteralliteral

¹ Docker CLI returns an ordered list at this parser stage; both duplicate entries remain. This is not a container-runtime precedence observation. Compose results came from config output, without a Docker daemon. Compose escapes literal dollars as $$ when serializing config.

These results describe the exact fixtures below, not every malformed key or quoting edge case. For complete inputs, raw outputs, pinned setup, and commands, see the reproducible compatibility record. Newer versions may behave differently.

dotenv
# These are separate fixtures, not one combined file.
 SPACE = spaced value 

HASH_JOINED=alpha#beta
HASH_SPACED=alpha # beta
HASH_QUOTED="alpha # beta"

DOUBLE="double value"
SINGLE='single value'

MULTI="line one
line two"

EMPTY=
DUP=first
DUP=second

MALFORMED LINE
VALID=after

export EXPORTED=exported

BASE=base
DOLLAR=${BASE}-suffix

How can I reproduce the comment difference?

In a disposable directory, create this synthetic file. It contains no credentials.

bash
printf 'HASH_JOINED=alpha#beta\nHASH_SPACED=alpha # beta\nHASH_QUOTED="alpha # beta"\n' > parser-example.env
node --env-file=parser-example.env -p 'JSON.stringify([process.env.HASH_JOINED, process.env.HASH_SPACED, process.env.HASH_QUOTED])'
# Node 24.21.0 expected: ["alpha","alpha","alpha # beta"]

uv run --python 3.13.7 --with python-dotenv==1.1.1 python -c 'import json; from dotenv import dotenv_values; d=dotenv_values("parser-example.env"); print(json.dumps(list(d.values())))'
# Expected: ["alpha#beta", "alpha", "alpha # beta"]

Node and npm dotenv treat any unquoted # as a comment. Python dotenv and Compose preserve a joined # but strip a whitespace-prefixed comment. Quoting a hash preserves it in these loaders; raw Docker mechanisms also preserve the quote characters themselves.

Are spaces, lowercase keys, and empty values valid?

Node, npm dotenv, Python dotenv, and Compose's default parser trimmed the surrounding whitespace in SPACE = spaced value . Shell assignment syntax is different: sourcing that line attempts to run a command. Use SPACE='spaced value' for a shell assignment.

Uppercase names are a convention. Our validator accepts names matching [A-Za-z_][A-Za-z0-9_]* , including lowercase. An assignment such as EMPTY=explicitly sets an empty string; it is not a syntax error. A missing key is a different condition.

CRLF line endings were accepted in every tested mechanism. Converting Windows line endings is not a universal fix for a variable that failed to load. Check the working directory, selected file, and loader first.

Do quotes enable expansion or escape sequences?

Quotes alone do not decide whether expansion happens. Native Node and npm dotenv left the dollar reference literal in our fixture; Python dotenv expanded it by default, and Compose expanded it. Python's interpolate=False option kept it literal. Node's native environment-file documentation does not define shell evaluation, and npm dotenv needs a separate expansion mechanism.

Do not assume a single-quoted value disables interpolation in every library. Escape handling also varies by loader. In the measured escaped fixture, double-quoted backslash-n became a newline in Node, npm dotenv, Python, and Compose default parsing. Single-quoted and unquoted backslash-n stayed literal. Raw Compose and Docker CLI kept both the backslashes and surrounding quotes. A backslash followed by n is two source characters, whereas a physical newline is a line break in the file. Use the versioned evidence and your actual loader before converting secrets between representations.

The validator deliberately performs no expansion, escape decoding, command substitution, or shell execution. It keeps dollars and backslashes literal. It rejects unsupported embedded matching quotes, malformed closing quotes, NUL characters, and backtick quoting. A successful check validates that syntax scope; it does not certify equivalent values in Node, Python, Docker, or a shell.

Can a value span multiple physical lines?

Yes in the quoted multiline fixture for Node, npm dotenv, Python dotenv, Compose interpolation, and default Compose service env_file. It failed in Compose raw mode and the Docker CLI parser. Do not transfer the default Compose result to docker run --env-file.

dotenv
MESSAGE="line one
line two"

The accepting parsers produced a value represented in JSON as "line one\nline two". For binary data or secret material, use your platform's supported secret-file or binding mechanism when that avoids encoding ambiguity.

How do Docker's three env-file paths differ?

  • docker compose --env-file file.env config supplies values for interpolation of the Compose model.
  • A service's env_file: supplies container variables using Compose's default parsing rules.
  • env_file: [{ path: file.env, format: raw }] preserves literal values, including quotes and dollars; it is not multiline quote parsing.

The separate docker run --env-file path uses the Docker CLI parser. In our pinned observation it preserved quotes and comment suffixes. See the Compose environment guide for interpolation versus injection precedence.

What should happen when input is malformed or duplicated?

Some libraries skip malformed lines and return the remaining entries. That can make an incomplete file look plausible. Our converter blocks output on parse errors or duplicate keys. Resolve those findings before copying or downloading a result.

The env converter preserves the literal values within its supported formats and reports unsupported target cases. The validator reports duplicate assignments separately and can compare keys against .env.example. Generated examples contain keys and empty assignments only, with no copied values or comments.

Which references define the supported loaders?

Was this helpful?

Frequently Asked Questions

Does .env syntax work the same in every runtime?

No. Pinned Node, Python, and Docker observations differ for comments, dollar references, malformed lines, and raw env-file quoting. Check the loader and version that actually reads the file.

Do spaces around the equals sign break .env files?

Node, npm dotenv, Python dotenv, and Compose default parsing accepted surrounding whitespace in the measured fixture. Shell assignment syntax and raw Docker env-file parsing have different rules.

Can .env values contain physical newlines?

The quoted multiline fixture was accepted by Node, npm dotenv, Python dotenv, Compose interpolation, and default service env_file parsing. Compose raw and the Docker CLI parser rejected that fixture.

Stay up to date

Get notified about new guides, tools, and cheatsheets.