dotenvとは?.envで環境変数を管理する仕組みと言語別の使い方
dotenvは、プロジェクト直下に置いた.envファイルのKEY=VALUEを読み取り、実行中のプロセスの環境変数へ流し込むだけのライブラリです。Node.js・Python・Rubyそれぞれに同名の実装があり、APIキーやデータベース接続情報をソースコードから切り離す用途で使われます。一方でNode.js本体もv20.6.0から同等の機能を持っており、v22.21.0/v24.10.0で実験的フラグの扱いではなくなりました。ここでは.envの書式、言語別の導入手順、ライブラリを入れずに済ませる条件、そして.envをGitへ上げてしまった場合の復旧手順までを整理します。
まとめ:dotenvの要点と判断の目安
dotenvがやることは「ファイルを読んで環境変数に入れる」の一点です。3実装に共通する既定として、すでに設定済みの環境変数は上書きしません。本番のサーバーやCIで環境変数を渡していれば、.envが残っていてもそちらが優先されます。
採用の目安は次のとおりです。単純なKEY=VALUEだけで足りるNode.jsプロジェクトなら、Node.js 22.21.0/24.10.0以降の--env-fileで依存を1つ減らせます。変数展開や複数ファイルのマージが必要ならライブラリ側が有利です。Python・Rubyにはランタイム内蔵の相当機能がないため、python-dotenv 1.2.2やdotenv 3.2.0を使います。
本番環境については、.envを置くこと自体を前提にしない設計を勧めます。平文のファイルがサーバー上に残るため、シークレット管理サービスや暗号化を挟む構成のほうが事故時の被害を抑えられます。以降で書式・言語別の手順・運用上の分岐を順に見ていきます。
dotenvの役割:.envの値をプロセスの環境変数へ流し込む仕組み
設定値をコードに直書きすると、開発用と本番用でソースが分岐し、APIキーがリポジトリへ入り込みます。環境変数へ逃がせばこの問題は解けますが、開発マシンごとに手作業でexportするのは現実的ではありません。dotenvはこの隙間を埋めるために、ファイルからの読み込みという1機能だけを担います。読み込まれた値は、Node.jsならprocess.env、Pythonならos.environ、RubyならENVから、通常の環境変数と同じように参照できます。
設計の中心にあるのは、The Twelve-Factor Appが説く「設定を環境に持たせる」という考え方です。ここから、実装をまたいで共通する重要な挙動が導かれます。既存の環境変数は上書きしないという既定です。python-dotenvのload_dotenv()はoverride=Falseが既定で、Rubyのdotenvもoverwrite: trueを渡さない限り上書きしません。Node.jsの--env-fileも、公式ドキュメントが「同じ変数が環境とファイルの両方で定義されている場合は環境の値が優先される」と明記しています。開発機の.envをそのままサーバーへ置いても、正しく環境変数を設定していれば本番の値が勝つ、という前提で作られています。
逆に言えば、dotenvは暗号化も権限管理もしません。.envは平文であり、読めるユーザーはすべての値を読めます。この性質が、後述する本番運用での分岐点になります。
.envファイルの書式:共通の記法と実装ごとに割れる仕様
すべての実装で共通する基本記法
.envは1行1変数のテキストファイルで、拡張子ではなくファイル名そのものが.envです(先頭がドットのため、多くのOSでは隠しファイルとして扱われます)。置き場所はプロジェクトのルートが基本で、Pythonのpython-dotenvはスクリプトと同じディレクトリから上位へさかのぼって探索します。
# コメント行
DB_HOST=localhost
DB_PORT=5432
API_KEY="sk-xxxxxxxx" # 行末コメント
export NODE_ENV=development
値のクォートは省略でき、囲んだ場合の引用符は値に含まれません。行頭のexportは読み飛ばされるため、シェルスクリプトからのコピーがそのまま通ります。ここまでは3実装とNode.js組み込みパーサのすべてで共通です。
複数行・変数展開・コマンド置換の対応差
共通なのは基本記法までで、そこから先は実装ごとに割れます。同じ.envを言語間で使い回すときに事故が起きるのはこの部分です。
| 記法 | Node.js組み込み | dotenv (npm) 17.4.2 | python-dotenv 1.2.2 | dotenv (Ruby) 3.2.0 |
|---|---|---|---|---|
| 引用符付きの複数行 | 対応 | 対応(v15.0.0以降) | 対応 | 対応 |
| ${VAR} の展開 | 非対応 | 非対応(dotenv-expand等が必要) | 対応 | 対応 |
| $VAR の展開 | 非対応 | 非対応 | 非対応 | 対応 |
| $(command) の置換 | 非対応 | 非対応 | 非対応 | 対応 |
| 行頭 export の無視 | 対応 | 対応 | 対応 | 対応 |
Node.js組み込みパーサの非対応は実測でも確認できます。ROOT_URL=${DOMAIN}/appと書いた.envをNode.js v26.5.0に--env-fileで渡すと、値は展開されず${DOMAIN}/appという文字列のまま入ります。npmのdotenvも本体では展開せず、READMEはdotenv-expandまたはdotenvxを案内しています。
注意が必要なのはRubyのdotenvで、$(command)によるコマンド置換に対応しています。DATABASE_URL="postgres://$(whoami)@localhost/my_database"のような記述が便利な一方、出所の不明な.envを読み込むとその場でコマンドが実行されます。他人から受け取った.envは中身を確認してから読み込んでください。
言語・ランタイム別の導入手順とバージョン要件
Node.js:dotenv 17.4.2と起動ログの抑止
npm install dotenvで導入し、アプリの最初期に読み込みます。ESMならimport 'dotenv/config'、CommonJSならrequire('dotenv').config()です。
import 'dotenv/config'
console.log(process.env.DB_HOST)
17系で挙動が変わった点が1つあります。読み込んだファイル名と変数の数を知らせるログが、既定で標準出力に出ます。このログはv16.6.0で導入され、v16.6.1でいったんquietの既定がtrueになって非表示へ戻り、v17.0.0で既定がfalseとなって再び出るようになりました。JSON出力を前提にしたCLIやログ収集で邪魔になる場合は、config({ quiet: true })または環境変数DOTENV_CONFIG_QUIET=trueで抑止します。
なお、GitHubのmasterブランチのCHANGELOGには、-r dotenv/config形式のプリロード廃止、CLIのdotenv run --への一本化、.env.vaultサポートの削除がUnreleasedとして予告されています。2026年8月2日時点では未リリースですが、プリロード方式を採用しているプロジェクトは次のメジャー更新時に書き換えが必要になります。
Node.js組み込み:実験的でなくなった–env-file
Node.jsは--env-fileフラグを本体に持っています。v20.6.0で追加され、v24.10.0およびv22.21.0で実験的機能ではなくなりました。現行のLTSはv24系(Krypton)とv22系(Jod)ですが、非実験として扱えるのはv24.10.0以降・v22.21.0以降です。同じLTS系列でもそれ以前のパッチ版では実験的機能の警告が出ます。
node --env-file=.env --env-file=.development.env index.js
複数指定でき、あとに書いたファイルが先のファイルの値を上書きします。ファイルが存在しないとエラーで停止するため、任意扱いにしたい場合はv22.9.0で追加された--env-file-if-existsを使います。コード側から読み込むならprocess.loadEnvFile(path)があり、こちらもv24.10.0/v22.21.0で非実験になりました。引数を省略するとカレントディレクトリの.envを読みます。
Python:python-dotenv 1.2.2のload_dotenvとdotenv_values
pip install python-dotenvで導入します。1.2.2はPython 3.10以上が必要です。
from dotenv import load_dotenv
import os
load_dotenv() # 既存の環境変数は上書きしない
load_dotenv(override=True) # ファイル側を優先する場合
print(os.getenv("DB_HOST"))
環境を汚さずに値だけ取り出したい場合はdotenv_values()を使います。戻り値は辞書なので、共有設定と機密情報のファイルを分けて辞書展開でマージし、最後にos.environを重ねる、といった優先順位の組み立てができます。変数展開は${VAR}形式のみで、$DOMAINのような裸の記述は展開されません。$VARも展開されるRubyのdotenvから.envを持ち込むと、この違いで値が壊れます。
Ruby / Rails:dotenv-rails 3.2.0の読み込み順とテストでの自動復元
Railsではdotenv-railsをGemfileの先頭付近へ追加します。本番で.envを読ませない構成にするなら、グループを開発・テストに限定します。
gem 'dotenv-rails', groups: [:development, :test]
Railsアプリではconfig/application.rbでApplication定数が定義されるタイミング(before_configurationコールバック)に自動で読み込まれ、RAILS_ENVに応じて次の優先順位が適用されます。上にあるファイルほど優先度が高く、先に設定された値が勝ちます。
| 優先度 | development | test | production | .gitignore |
|---|---|---|---|---|
| 最高 | .env.development.local | .env.test.local | .env.production.local | する |
| 2番目 | .env.local | 読み込まない | .env.local | する |
| 3番目 | .env.development | .env.test | .env.production | しない |
| 最低 | .env | .env | .env | 内容次第 |
テスト環境だけ.env.localが対象外になっているのは、開発者ごとのローカル設定でテスト結果が変わるのを避けるためです。必要ならconfig/application.rbでDotenv::Rails.filesへ明示的に追加できます。
3.0以降のもう1つの変更が、テストごとのENV自動復元です。テスト内でENVを書き換えても各テストの終了時に元へ戻るため、状態が他のテストへ漏れません。ActiveSupport::TestCaseとRSpecの双方で動作し、config.dotenv.autorestore = falseで無効化できます。素のRubyやSinatraで使う場合はdotenv単体のgemを入れ、起動処理の早い段階でrequire 'dotenv/load'を実行します。VS Codeのデバッガや統合ターミナルから起動するときに変数が渡らない場合は、ruby on rails を vscode で開発する環境構築で扱っているデバッグ設定側の指定を確認してください。
dotenvを入れない判断:ランタイム内蔵で足りる条件
Node.jsに限れば、dotenvを追加しない選択が現実的になりました。判断基準ははっきりしています。使っているNode.jsがv22.21.0/v24.10.0以降で、.envが単純なKEY=VALUEの羅列に収まっているなら、--env-fileで十分です。依存が1つ減り、アプリコードから読み込み処理そのものが消えます。
ライブラリ側を選ぶべきなのは次の条件に当たるときです。${VAR}で他の変数を参照している、読み込むファイルをコード側の条件で切り替えたい、あるいはCI・Lambdaなどランタイムバージョンを固定できない環境が混ざっている場合です。ファイルの切り替えはconfig({ path: '.env.production' })のようにオプションで指定でき、config()の戻り値のerrorとparsedを見れば読み込みの成否も判定できます。組み込みの--env-fileには、この分岐に相当する手段がありません。
加えて、そもそも.envを読む主体がアプリでないケースもあります。Docker Composeはenv_fileやenvironmentでコンテナに環境変数を渡せるため、コンテナ前提の構成ならアプリ側の読み込みは不要になります(docker-composeとは?複数コンテナをymlで定義し一括管理する仕組みで構成例を解説しています)。NestJSのように設定モジュールを備えたフレームワークでは、ConfigModuleでNestJSの環境変数を型安全に管理する実装のほうがバリデーションまで含めて扱えます。二重に読み込む構成は、値がどこ由来か追えなくなるだけなので避けてください。
.envに書くもの・書かないもの:本番でのシークレット管理
.envに入れるのは、環境ごとに変わる値と、コードへ直書きしたくない資格情報です。データベースの接続先、外部APIのキーやトークン、S3のバケット名などが該当します。逆に、全環境で同じ定数やアプリの内部設定は、通常の設定ファイルに置いたほうが変更履歴を追えます。
本番でどこまで.envに頼るかは、切り分けが必要です。前述のとおり.envは平文で、サーバーに侵入されれば全件が読まれます。ローテーション(定期的な鍵の入れ替え)も手作業になり、複数台へ配布していれば取りこぼしが起きます。この2点が許容できない本番系では、AWS Secrets Managerの料金・ローテーションと採用判断で整理しているようなシークレット管理サービスへ寄せる構成が現実的です。Railsであれば、標準のconfig/credentials.yml.encとマスターキーの組み合わせも選択肢になります。
.envのファイル形式は維持したまま安全性を上げたい場合は、値を暗号化してリポジトリに入れられるdotenvxで.envを暗号化してGitで安全に管理する仕組みがあります。2026年7月29日時点の最新は2.19.1で、公開鍵で暗号化した値を.envに置き、復号鍵だけを別管理する方式です。npmのdotenvも--secureフラグからdotenvxへ復号を委譲する設計を予告していますが、こちらも前述のUnreleased枠で、2026年8月2日時点では未リリースです。
.envをGitに載せない運用と、上げてしまった場合の復旧手順
基本の防御は.gitignoreに.envを書くことと、キーの一覧だけを持つ.env.exampleをコミットしておくことです。前掲のRailsの表のとおり、.env.developmentのような共有設定は意図的に追跡対象へ残す運用もあります。この場合、そのファイルに機密を書かないという規律とセットでなければ意味がありません。
実際に機密を含む.envをpushしてしまった場合、着手する順番で結果が変わります。GitHubの公式ドキュメントは、履歴の書き換えより先に該当の鍵やトークンを失効・再発行することを求めています。理由は単純で、公開された時点でその値は漏洩済みと考えるべきだからです。
- 漏れた鍵をただちに失効・再発行する(最優先。これだけで実害を止められる場合が多い)
- 履歴からの削除が必要なら
git filter-repoを使う(GitHubが公式に案内しているツール) - force push後も、書き換え前のクローンを持つメンバーが
git pullとgit pushを実行すると機密が復活する点をチームへ周知する - フォーク、プルリクエストの参照、キャッシュされた表示に残る可能性を前提に、失効を省略しない
予防面では、パブリックリポジトリで自動実行されるGitHubのシークレットスキャン(secret scanning)が、既知の形式のトークンを検出してアラートを出します。ただし検出は事後であり、自社が発行した独自形式のキーは有料のカスタムパターンを設定しない限り検出されません。.gitignoreとレビューが依然として一次防御になります。
よくある質問
dotenvとdotenv-railsのgemはどう違いますか?
dotenvが本体で、dotenv-railsはRails向けの薄いラッパーです。dotenv-railsを入れると、Railsの起動時にRAILS_ENVに応じた.env.developmentや.env.testを自動で読み込み、テストでのENV自動復元も有効になります。Rails以外(素のRuby、Sinatra、Rakeタスク単体など)では本体のdotenvを入れ、require 'dotenv/load'で明示的に読み込みます。バージョンは両方とも3.2.0で共通で、dotenv-railsはdotenvに依存します。
.envファイルはどこに置けばよいですか?
原則としてプロジェクトのルート、つまりアプリを起動するディレクトリの直下です。Node.jsの--env-fileはカレントディレクトリからの相対パスで解決し、python-dotenvのload_dotenv()はスクリプトのあるディレクトリから上位へさかのぼって探します。Railsではアプリのルートが対象です。サブディレクトリから起動するスクリプトでは、探索の起点がずれて読み込まれないことがあるため、パスを明示的に渡すほうが確実です。
VS Codeで「環境ファイルは構成されていますが、ターミナル環境への取り込みは無効になっています」と表示されます
.envのパス自体は設定されているものの、統合ターミナルへ値を注入する設定がオフになっている状態を指すメッセージです。この設定python.terminal.useEnvFileはPython Environments拡張が提供しており、既定値がfalseのため、初期状態ではこの表示になります。有効にするとターミナルの作成時に.envの変数が取り込まれます。読み込むファイルのパスはpython.envFileで指定します。注入されるのはターミナル生成時のみなので、設定変更後は既存のターミナルを閉じて開き直してください。
.envを置いているのに値が読み込まれません。どこから確認すればよいですか?
確認の起点は、読み込み処理がアプリの最初期に走っているかどうかです。設定値を参照するモジュールより後にdotenvを読み込むと、参照時点では未設定になります。次に探索パスを疑ってください。プロジェクトのルート以外から起動している場合、Node.jsはconfig({ path: '.env.production' })のようにパスを明示できます。npmのdotenvはconfig()の戻り値にerrorとparsedを返すので、ファイルを開けなかったのか、開けたが目的のキーが無いのかを切り分けられます。値は入っているのに空文字になる場合は、引用符や行末コメントの書き方を見直してください。
dotenvはpnpmやyarnでも同じように使えますか?
使えます。dotenvは実行時にファイルを読むライブラリなので、パッケージマネージャーの違いによる差はありません。pnpm add dotenvやyarn add dotenvで導入し、読み込み方はnpmの場合と同一です。ただし、パッケージマネージャー自身の設定ファイル(.npmrcなど)は.envとは別系統で、認証トークンをそちらに書く場合は別途.gitignoreの対象にしてください。