環境変数は、OSがプロセスへ渡す name=value 形式の文字列です。PATH のようにOSが用意するものと、接続先URLやログレベルのようにアプリケーション側で決めるものが、同じ仕組みに同居しています。この記事では、POSIXの定義から見た実体、LinuxとWindowsの設定・永続化コマンド、PythonとNode.jsでの読み取りの注意点、Docker ComposeとGitHub Actionsでの優先順位を公式ドキュメントに沿って整理しました。機密情報の線引きと、反映されないときの切り分け手順も後半に置いています。
まとめ|環境変数の設定と受け渡しで最初に決める4点
環境変数は name=value の文字列の集まりで、プロセス起動時に親から子へコピーとして渡ります。値を書き換えても、すでに動いているプロセスには届きません。「設定したシェル」と「動かしているプロセス」のずれが、反映されない原因の大半です。
永続化の手段はOSで分かれます。LinuxとmacOSはシェルの初期化ファイルか /etc/environment、Windowsは SetEnvironmentVariable のUser/Machineスコープ。デーモンはシェルを経由しないため、systemdやコンテナ側で別途渡す設計が要ります。値は常に文字列で型を持たず、"false" もPythonでもJavaScriptでも真と評価されます。
機密情報は「全部だめ」でも「全部よい」でもありません。デプロイなしで失効と更新を回せるかで線を引きます。AWS Lambdaのように合計4KBの上限を持つ実行環境もあり、鍵の類はParameter StoreやSecrets Managerへ逃がすのが実務上の落としどころです。
環境変数の実体|name=value文字列とプロセス継承の仕組み
環境変数が「どこに存在するのか」を仕様で押さえます。ここが曖昧なまま設定コマンドだけ覚えると、反映されない場面で詰まります。
name=value の文字列配列として子プロセスへ渡る仕組み
POSIX(Open Group Base Specifications Issue 8)は、環境を「name=value の形式を持つ文字列の配列」と定義しています。プログラムからは environ という外部変数として見え、name の部分に = を含むバイトは置けません(Environment Variables / The Open Group Base Specifications Issue 8)。値の側の改行やスペースは可能で、禁止は名前側の等号だけです。
渡され方はコピーです。子が自分の環境を書き換えても親には戻らず、親を書き換えても起動済みの子には反映されません。プロセスが実際に持つ値は、Linuxなら次の3行で確認できます。
printenv PATH
env | sort | head -20
tr '\0' '\n' < /proc/self/environ
3行目はカーネル側の情報から直接読む方法で、PIDを差し替えれば別プロセスが起動時に受け取った値も見えます。切り分けでは、設定ファイルより実際に渡っている値を先に見ます。
POSIXが定める命名規則とWindows・Linuxで割れる大文字小文字の扱い
POSIXは、標準ユーティリティが使う環境変数名を「大文字・数字・アンダースコアのみで構成し、数字で始まらない」と規定しました。小文字を含む名前空間はアプリケーション用の予約です。実務では APP_DB_HOST のように全大文字とアンダースコアで統一し、プレフィックスで名前空間を切ります。
大文字小文字の区別はOSで割れます。PowerShellの公式ドキュメントは、macOSとLinuxでは名前が区別されるため $Env:Path と $Env:PATH が別の変数になると注意しています(環境変数について / Microsoft Learn)。Windowsでは区別されません。差はランタイムにも出て、Pythonの os.environ はWindowsでキーを大文字へ変換して保持します。Windows前提のコードをLinuxコンテナへ載せた途端に値が取れない事故は、ほぼこの仕様差が原因です。
LinuxとmacOSでの設定手順|exportと初期化ファイルでの永続化
Unix系ではシェル変数と環境変数が別物で、その境目を作るのが export です。
exportとenv・printenvで設定値を確認するコマンド操作
Bashのリファレンスマニュアルは、シェルが実行するコマンドへ渡される環境について、export でマークされた変数が子プロセスの環境へ入ると説明しています(Environment / Bash Reference Manual)。単に VAR=value と書いただけではシェル変数にとどまり、起動したコマンドからは見えません。効く範囲は3段階です。
APP_ENV=staging ./run.sh # このコマンド1回だけ
export APP_ENV=staging # 今のシェルと、以降そこから起動する子
printenv APP_ENV # 環境変数として入っているかの確認
set | grep APP_ENV # シェル変数も含めた一覧(export前でも出る)
printenv に出ず set にだけ出るなら、export 漏れです。この確認を挟むだけで「値は書いたのに読めない」の相当数が片付きます。env -i なら環境を空にした状態も再現できます。
.bashrcとsystemd・/etc/environmentでの永続化の使い分け
ログインのたびに効かせたい値は、シェルの初期化ファイル(bashは ~/.bashrc、zshは ~/.zshrc)へ書きます。システム全体に効かせるなら /etc/environment へ key=value を並べるか、/etc/profile.d にスクリプトを置く方法をMicrosoftの公式ドキュメントも案内しています。前者はシェルスクリプトではないため、変数展開やコマンド置換は書けません。
落ちやすいのがデーモンです。systemdが起動するサービスはログインシェルを経由せず、~/.bashrc の値は一切渡りません。ユニットファイルの Environment= で明示的に渡します。サービス管理の全体像はsystemdの役割と基本構成の解説、Linuxの土台はLinuxの仕組みとサーバー用途の解説で扱っています。
Windowsでの設定手順|PowerShellの3スコープと永続化の判断
WindowsにはMachine・User・Processの3スコープがあり、どこに書いたかで再起動後に残るかが決まります。
$env:による一時設定とSetEnvironmentVariableでの永続設定
PowerShellの $Env: ドライブ経由の変更は、現在のセッションにしか影響しません。公式ドキュメントは、これがコマンドシェルの set と同じ振る舞いであり、MachineやUserスコープを変えるには System.Environment クラスのメソッドが必要だと説明しています。
$Env:APP_ENV = 'staging'
[Environment]::SetEnvironmentVariable('APP_ENV', 'staging', 'User')
[Environment]::GetEnvironmentVariable('APP_ENV', 'Machine')
[Environment]::SetEnvironmentVariable('APP_ENV', '', 'User')
3番目の引数がスコープです。UserとMachineはレジストリへ書かれるため、プロセスを抜けても残ります。Machineスコープの変更には権限が必要で、足りなければコマンドは失敗。4行目の空文字設定は、そのスコープからの削除として働きます。なお SetEnvironmentVariable() へ $null を渡しても削除にならない点は、変数構文経由の挙動との差としてドキュメントに注記されています。
PATHへ追記した値がターミナルの再起動まで反映されない理由
Processスコープの内容は、プロセス起動時にMachineスコープとUserスコープから組み立てられます。コントロールパネルや SetEnvironmentVariable でUserスコープへ書いても、開いているターミナルやエディタには伝わりません。開発ツールのインストール直後に PATH が通らないのは、この構築タイミングの問題です。ターミナルを開き直すか、統合ターミナルを持つエディタごと再起動すれば解決します。
区切り文字にも差があり、Windowsのパス一覧はセミコロン、Windows以外はコロンです。クロスプラットフォームのスクリプトでは、区切り文字を直書きせず言語側の定数(Pythonなら os.pathsep)を使います。
PythonとNode.jsから環境変数を読む際の型と取得タイミング
アプリケーション側の読み取りには、言語ごとの落とし穴があります。いずれも公式ドキュメントに明記された挙動です。
os.environがインポート時のスナップショットである点の注意
Pythonの os.environ は、os モジュールが最初にインポートされた時点(通常は起動時)の環境をキャプチャしたマッピングです。その後にプロセス外部で環境が変わっても反映されません(os — 雑多なオペレーティングシステムインターフェース / Python 3 ドキュメント)。Python 3.14では、取り込みをやり直す os.reload_environ() が追加されました。
書き込み側は非対称です。os.environ への代入は内部で putenv() を呼ぶため、subprocess などで起動する子プロセスへ値が伝わります。一方 os.putenv() の直接呼び出しは os.environ へ反映されず、os.getenv() からも見えません。
import os
db_host = os.environ.get('APP_DB_HOST', 'localhost')
debug = os.environ.get('APP_DEBUG', '0') == '1'
os.environ['APP_ENV'] = 'staging' # 子プロセスへ伝わる
print(db_host, debug)
process.envの値が常に文字列になるNode.jsの取り扱い
Node.jsの process.env は、代入した値を暗黙に文字列へ変換します。null を代入すれば 'null' という4文字の文字列です。公式ドキュメントはこの暗黙変換を非推奨と位置づけ、将来のバージョンでは文字列・数値・真偽値以外でエラーを投げる可能性があると予告しました(process / Node.js ドキュメント)。削除は delete process.env.X です。
ワーカースレッドを使う構成では、各ワーカーが親スレッドの process.env のコピーを持ちます。ワーカー内の変更は他スレッドへ見えず、OSやネイティブアドオンから見える変更を行えるのはメインスレッドだけ。しかもWindowsでは、ワーカー側のコピーだけが大文字小文字を区別します。設定値の再読み込みをワーカーへ任せる設計は破綻します。
.envファイルとdotenvで開発環境の設定値をまとめる手順
ローカル開発では値をファイルへまとめると再現性が上がります。ただし .env はOSの機能ではありません。
dotenvでの読み込みと.envをGitに入れない共有の手順
.env ファイルを読むのは、ライブラリかランタイムの機能です。Node.jsは --env-file オプションと process.loadEnvFile() を備え、PythonやRuby、PHPではdotenv系のライブラリが同じ役割を担いました。言語別の書き方はdotenvと.envファイルの仕組みを解説した記事で扱っています。The Twelve-Factor Appの「設定をコードから分離し、環境に持たせる」原則(設定 / The Twelve-Factor App)に沿うなら、全員が同じ値を使えるものだけをファイルへ残します。
実際の形は、.env を .gitignore へ入れ、キー名だけを書いた .env.example を共有するものです。プロジェクトのディレクトリへ入ったときだけ .env の値をシェルへ流し込みたい場合は、direnvで.envrcを使ってディレクトリ単位に環境変数を切り替える方法が使えます。
# .gitignore
.env
.env.*.local
# .env.example
APP_DB_HOST=localhost
APP_DB_PASSWORD= # 値は各自のパスワードマネージャから
APP_LOG_LEVEL=debug
誤ってコミットした場合は、履歴から消す作業より先にその値を失効させるのが正しい順序です。履歴の書き換えではフォークやキャッシュまで追えません。
コンテナとCIでの受け渡し|Composeの優先順位とActionsの使い分け
同じ変数名が複数の場所に書かれると、どれが勝つかで挙動が変わります。ここは推測せず、公式の優先順位表に当たります。
Docker Composeで同名の値が競合したときの優先順位
Docker公式ドキュメントは、Compose環境での優先順位を高い順に次のように示しています(Environment variables precedence in Docker Compose / Docker Docs)。
| 順位 | 設定場所 | 使いどころ |
|---|---|---|
| 1 | docker compose run -e | 一時的な上書き |
| 2 | シェルや.envからの補間 | 環境ごとの差分 |
| 3 | compose.yamlのenvironment | 固定値の明示 |
| 4 | compose.yamlのenv_file | まとまった既定値 |
| 5 | DockerfileのENV | イメージ内の既定値 |
注意点がひとつあります。Compose側で environment・env_file・run --env のいずれかが設定されている場合、Dockerfile内の ARG や ENV は評価されません。イメージへ焼いた既定値が効かないときは、この条件を先に疑います。ENV 命令の書き方はDockerfileの主要命令と書き方の解説で整理しました。
GitHub Actionsのenv・vars・secretsの使い分けと上限
GitHub Actionsでは、機密でない構成値を vars、機密を secrets、ワークフロー内で参照する環境変数を env で扱います。公式リファレンスによれば、個々の変数は48KBまで、保存できる数は組織1,000件・リポジトリ500件・環境100件で、1実行あたりの組織とリポジトリの変数合計は256KBに制限されます(Variables reference / GitHub Docs)。同名なら下位のレベル(環境 → リポジトリ → 組織)が優先されます。
jobs:
deploy:
runs-on: ubuntu-latest
env:
APP_ENV: staging
APP_REGION: 変数参照(vars.APP_REGION)
steps:
- run: ./deploy.sh
env:
APP_TOKEN: 機密参照(secrets.DEPLOY_TOKEN)
置く階層で露出範囲が変わるため、トークンの類はステップ単位へ絞ります。受け渡しの詳細はGitHub Actionsのenv・vars・secretsの使い分けで扱っています。
KubernetesのConfigMapとSecretから環境変数を渡す方法
Kubernetesでは、コンテナの env に個別指定するか、envFrom でConfigMapのキーをまとめて環境変数として展開します(ConfigMap / Kubernetes ドキュメント)。
envFrom:
- configMapRef:
name: app-config
env:
- name: APP_DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret
key: db-password
ConfigMapは機密を保持する仕組みではありません。パスワードはSecretへ置きますが、Secretも既定ではetcdにBase64エンコードで格納されるだけなので、保存時暗号化とRBACでの参照制限を別途かけます。環境変数として渡した値は、ConfigMapを更新してもPodを作り直すまで変わりません。
機密情報を環境変数に置く判断基準|4KB上限とシークレット管理
「環境変数に秘密を置くな」と「12factorは環境変数を推奨している」は、どちらも実務では雑すぎます。条件を切って判断します。
環境変数に置いてよい値と外部の秘密管理へ逃がすべき値の線引き
判断軸はひとつです。その値を失効・更新するのに、デプロイし直す必要があるか。必要なら環境変数から出します。この軸で切ると次のように分かれます。
- 置いてよい:接続先ホスト名、リージョン、ログレベル、機能フラグ、タイムアウト値
- 置いてよい:CIジョブ内の短命なトークン(secretsのマスクが効き、ジョブ終了で消える)
- 逃がす:本番DBのパスワード、長期有効のAPIキー、秘密鍵、決済系の資格情報
逃がす理由は、環境変数が「読める場所が多い」ことにあります。子プロセスへ自動的に継承され、クラッシュダンプに載り、フレームワークによっては起動ログへ環境一式が出るためです。AWSであればParameter StoreとSecrets Managerの違いと使い分けで整理した参照方式へ切り替え、環境変数には「どこから取るか」のパス名だけを置きます。
Lambdaの4KB上限に当たったときの分割と参照方式の選択
AWS Lambdaは、すべての環境変数の合計サイズが4KBを超えないことを要件としています。_HANDLER、AWS_REGION、実行ロールから渡される AWS_ACCESS_KEY_ID などの予約済み変数も存在します(Lambda 環境変数の操作 / AWS ドキュメント)。値は保管時に暗号化され、別のKMSキー指定も選べます。
4KBに当たったときの打ち手は、設定をJSONへまとめてS3やParameter Storeへ置き、起動時に取得して実行環境のメモリへキャッシュする形です。取得の往復は初回だけで済み、上限も事実上外れます。どこから参照方式へ倒すかは起動頻度で変わるため、AWS・Google Cloud・Azureのインフラ構築支援で構成ごと相談する手も取れます。
設定しても反映されない場合の切り分け|継承・sudo・cronの落とし穴
実際に手が止まる場面の潰し方をまとめます。原因は毎回ほぼ同じ場所です。
設定した値が子プロセスへ継承されないときに確認する3つの起点
第一に、export したか。set には出るが printenv に出ないなら、シェル変数のままです。第二に、対象プロセスを再起動したか。環境は起動時にコピーされるため、稼働中のプロセスへは届きません。第三に、設定したユーザーと実行ユーザーが同じか。デプロイユーザーのシェル設定は、サービス実行ユーザーには無関係です。
推測で潰すより、動いているプロセスが受け取った値を直接見るほうが早く終わります。Linuxなら tr '\0' '\n' < /proc/<PID>/environ、Windowsなら [Environment]::GetEnvironmentVariable('APP_ENV','Machine') でスコープを指定して読みます。
sudo・cron・systemdで環境変数が消える仕様と回避の書き方
ユーザーのシェルでは動くのに、別の起動経路だと消える。これは仕様で、回避策も決まっています。
- sudo:既定の env_reset が効き、PATHも安全な既定値へ置き換わります。
sudo -Eか、sudoers側での変数指定で引き継ぎます - cron:初期化ファイルを読まず最小限の環境で実行されます。crontab内で変数を明示し、コマンドは絶対パスで書きます
- systemd:同じくシェルを経由しません。ユニットの
Environment=かEnvironmentFile=で渡します
コンテナも同じ系統の問題を抱えます。docker exec で入って確認した環境と ENTRYPOINT が受け取った環境は一致しないことがあるため、確認はアプリケーションを起動しているプロセスのPIDに対して行ってください。
よくある質問
環境変数の扱いで実際に検索されている質問を5つ、仕様の根拠とあわせて答えます。
環境変数とシェル変数はどう違いますか?
シェル変数はそのシェルの内部だけで有効な変数で、環境変数は起動する子プロセスへ引き継がれる変数です。Bashでは VAR=value の段階ではシェル変数で、export VAR を実行して初めて環境に入ります。確認方法も分かれ、set は両方を、printenv は環境変数だけを表示します。プログラムから読めない場合、この境目を越えていないケースがほとんどです。
環境変数に設定できる値の長さに上限はありますか?
POSIXの仕様として固定の上限値は定められておらず、実際の上限は実行環境ごとに決まります。AWS Lambdaはすべての環境変数の合計で4KB、GitHub Actionsは個々の変数が48KBまでです。Linuxではコマンドライン引数と環境の合計サイズに制限があり、長大な値は起動時のエラーになります。数KBを超える設定はファイルや外部ストアへ逃がしてください。
環境変数の名前は大文字と小文字のどちらで書くべきですか?
全大文字とアンダースコアで統一するのが無難です。POSIXは標準ユーティリティが使う名前を大文字・数字・アンダースコアのみと規定し、小文字を含む名前空間をアプリケーション側へ予約しました。ただしWindowsは大文字小文字を区別せず、LinuxとmacOSは区別するため、小文字名を混ぜると環境間で挙動が割れます。APP_DB_HOST のようにプレフィックスで名前空間を切れば、他ツールの変数との衝突も避けられます。
.envファイルは本番環境でも使ってよいですか?
本番では使わず、実行基盤側の仕組みで渡す形を推奨します。ファイルとして配置すると、誤ってイメージへ焼き込まれたりバックアップ経路に乗ったりするためです。コンテナならKubernetesのSecret、サーバーレスなら実行環境の設定とシークレットストアの参照を使います。読み込み経路を分けておけば、開発用の値が本番へ紛れ込む事故も防げます。
環境変数を変更したのにアプリに反映されないのはなぜですか?
環境はプロセス起動時にコピーされる仕様のため、稼働中のプロセスには新しい値が届きません。まずアプリケーションのプロセスを再起動してください。それでも変わらない場合は、設定を書いた場所と実行ユーザー・起動経路の一致を確認します。Pythonのように起動時のスナップショットを保持する言語では、外部から書き換えてもプロセス内のマッピングは変わりません(3.14以降は os.reload_environ() で取り直せます)。
関連記事
- dotenvとは?.envで環境変数を管理する仕組みと言語別の使い方:.envの読み込みを言語別に深掘りしています
- GitHub Actionsの環境変数|env・vars・secretsの使い分けと受け渡し:CI上での受け渡しとマスク機構の各論です
- パラメータストアとは?AWS Parameter Storeの料金・使い方・Secrets Managerとの違いを解説:環境変数から逃がした値の置き場所を比較しています
- Dockerfileとは|書き方・主要命令・ベストプラクティスを解説【2026年版】:ENV命令の書き方とイメージ内の既定値を扱っています
- systemdとは何か?Linuxにおける役割と概要解説:デーモンへ設定値を渡す前提知識です