.envの#以降が消える Node・dotenv・Composeの差を実測
.env に DB_PASSWORD=abc#123 と書いたら、アプリには abc しか渡っていなかった。原因は # 以降がコメントとして捨てられたことだが、同じファイルを Docker Compose に読ませると abc#123 のまま通る。どちらも「.env を読む」道具なのに、# の扱いが違う。
この記事では、同じ .env を Node.js 組み込みのパーサー、npm の dotenv、python-dotenv、Docker Compose の 4 つで読み、値がどう変わるかを実測する。
同じ .env を4つのパーサーで読む
次の sample.env を用意した。パスワードや URL、色コードなど、# が入りがちな値を並べている。
1 | |
実行環境は Windows 11、Node.js v24.11.1(util.parseEnv と --env-file)、dotenv 18.0.9、Python 3.11.9 + python-dotenv 1.2.4、Docker Compose v5.5.1(docker compose config の出力)。結果を並べる。
| キー | Node.js v24.11.1 | dotenv 18.0.9 | python-dotenv 1.2.4 | Docker Compose v5.5.1 |
|---|---|---|---|---|
| PASS1 | "abc" |
"abc" |
"abc#123" |
"abc#123" |
| PASS2 | "abc" |
"abc" |
"abc" |
"abc" |
| PASS3 | "abc#123" |
"abc#123" |
"abc#123" |
"abc#123" |
| PASS4 | "abc #123" |
"abc #123" |
"abc #123" |
"abc #123" |
| URL | "https://example.com/" |
"https://example.com/" |
"https://example.com/#top" |
"https://example.com/#top" |
| COLOR | "" |
"" |
"#ff0000" |
"#ff0000" |
| SPACED | "hello world" |
"hello world" |
"hello world" |
"hello world" |
| MULTI | 改行入り | 改行入り | 改行入り | 改行入り |
| SINGLE | \n が文字のまま |
\n が文字のまま |
\n が文字のまま |
\n が文字のまま |
| EXPORTED | "yes" |
"yes" |
"yes" |
"yes" |
食い違うのは PASS1、URL、COLOR の 3 行で、どれも「引用符なしの値の途中に、空白を挟まずに # がある」行だ。Node.js と dotenv は # の位置で値を切り、python-dotenv と Compose は切らない。COLOR=#ff0000 は Node.js と dotenv では空文字列になる。
引用符で囲んだ PASS3 / PASS4 と、空白の後に # がある PASS2 は 4 つとも同じ結果だった。二重引用符の中では \n が改行に、一重引用符の中では文字のまま残る点も揃っている。
Node.js は node --env-file=sample.env と process.loadEnvFile('sample.env') でも試し、util.parseEnv と同じく PASS1 が "abc"、URL が "https://example.com/"、COLOR が "" になった。
なぜ食い違うのか: 「# の前に空白が要るか」
各実装のドキュメントとコードを見ると、コメントの始まりの決め方が 2 系統に分かれている。
Node.js と dotenv: # があればそこからコメント
Node.js のドキュメント(Environment Variables の DotEnv 節)は、# がコメントの始まりで行の残りは無視され、引用符の中の # は普通の文字として扱う、と説明している。--env-file の説明でも「# の後のテキストはコメント」とある。
npm の dotenv の README も、# があるところからコメントが始まるので、値に # を含めるなら引用符で囲むよう書いている。これは v15.0.0 からの破壊的変更とされている。インストールした 18.0.9 のパーサーでも、引用符なしの値は [^#\r\n]+ という正規表現で取り出しており、# の手前で止まる。
python-dotenv と Docker Compose: 空白の後の # だけがコメント
Docker Compose のドキュメント(Interpolation の .env 構文の節)は、引用符なしの値の行内コメントは空白の後に置く必要があると明記し、VAR=VAL# not a comment は VAL# not a comment になる例を載せている。
python-dotenv の README は「値の後にコメントを書ける」とだけ書いていて # の規則は明記していない。パーサー(dotenv/parser.py)を読むと、引用符なしの値に re.sub(r"\s+#.*", "", part) を当てており、空白に続く # 以降だけを落としている。
境界のケース: 空白とタブ
「空白の後」の解釈にも差がある。次の 3 行で試した。
1 | |
TAB の abc と #123 の間はタブ 1 文字である。
| キー | Node.js / dotenv | python-dotenv 1.2.4 | Docker Compose v5.5.1 |
|---|---|---|---|
| NOVAL | "" |
"#novalue" |
"#novalue" |
| SPCOMMENT | "" |
"" |
"# comment" |
| TAB | "abc" |
"abc" |
"abc\t#123" |
SPCOMMENT= # comment は python-dotenv では空、Compose では # comment という値になった。python-dotenv のパーサーには「= の後に空白があって値が # で始まるなら空の値とコメント」という分岐があり、それが効いている。Compose では = 直後の空白が落ち、# から始まる文字列が値として残った。タブの後の # は Compose ではコメントにならなかった。ドキュメントの「space」の表記どおりの挙動である。
つまり、空白の後に # を書いても 4 つで揃うとは限らない。コメントは独立した行に書くのがいちばん確実だ。
実際に試す
1. 4つのパーサーで読み比べる
Node.js(記事では v24.11.1 で確認。util.parseEnv が無い古いバージョンでは動かない)と Python 3、Docker が入った環境で試せる。作業用ディレクトリに上の sample.env を置き、dotenv と python-dotenv を入れる。
1 | |
Node.js 側の比較スクリプト compare.js。
1 | |
1 | |
Python 側の compare.py。
1 | |
1 | |
Docker Compose は、env_file で読ませた値を docker compose config で確認できる。コンテナを起動しなくてよい。
1 | |
1 | |
2. 引用符なしの # を含む行を検出する
どのパーサーでも同じ値にするには、# を含む値を引用符で囲めばよい。囲み忘れを探す小さなスクリプトを置いておく。
1 | |
1 | |
見つかると終了コード 1 を返すので、CI や pre-commit に入れられる。PASS2=abc #123 のように意図した行内コメントも拾うが、前節のとおり行内コメント自体がパーサー間で揃わないので、独立した行に移すきっかけとして扱えばよい。
ハシトシステムの .env⇄JSON 変換では
ブラウザで使える .env と JSON の相互変換(ハシトシステム) は、2026-10-29 時点のコードで、引用符なしの値は最初の # の位置で切る(indexOf('#') で探してそこから先を落とす)。上の sample.env をツールと同じ関数で変換すると PASS1 は "abc"、URL は "https://example.com/"、COLOR は "" になり、Node.js / dotenv と同じ側の読み方だった。
Compose や python-dotenv で読む前提の .env をこのツールで JSON にすると、値が短く見えることがある。そのときは原文で引用符の有無を確かめてほしい。逆方向の JSON→.env では、#・空白・引用符などを含む値を自動で二重引用符で囲む。{"PASS":"abc#123","COLOR":"#ff0000"} は次のように出力され、4 つのパーサーのどれで読んでも元の値に戻る。
1 | |
設定ファイルの値が読み手によって変わる別の例として、YAML の NO や on が false になる記事 も書いた。
まとめ
- 引用符なしの
abc#123は、Node.js(--env-file/util.parseEnv/process.loadEnvFile)と dotenv ではabc、python-dotenv と Docker Compose ではabc#123になる。COLOR=#ff0000は前者で空文字列になる。 - 差の原因は「
#の前に空白が要るか」。Node.js と dotenv はどこでも、python-dotenv と Compose は空白の後だけをコメントとして扱う。 KEY= # commentや、タブの後の#は python-dotenv と Compose の間でも結果が違った。行内コメントは避け、コメントは独立した行に書く。#を含む値は二重引用符か一重引用符で囲む。これで 4 つとも同じ値になる。
参考
- Node.js ドキュメント: Environment Variables(DotEnv)、Command-line API(
--env-file) - motdotla/dotenv README(Comments)
- theskumar/python-dotenv README(File format)と
dotenv/parser.py - Docker Docs: Set, use, and manage variables in a Compose file with interpolation(.env file syntax)
このバージョンはその範囲に入るのか
依存の話でいちばん間違えやすいのは ^1.2.3 や ~1.2.3 がどこまでを許すかです(^0.2.3 のようにメジャーが 0 のときは規則が変わります)。範囲とバージョンを貼ると、下限・上限に展開したうえで一致・不一致とその理由を表示するsemver 範囲判定ツールを置いています。ブラウザの中だけで動き、入力はどこにも送信しません。