.env と環境変数とは?Mac・Linux での確認と設定、秘密の扱い(コミットしてしまったら)

PR
.env と環境変数とは?Mac・Linux での確認と設定、秘密の扱い(コミットしてしまったら)
この記事は約32分で読めます。

.env は、環境変数を書いておくファイルです。環境変数とは、プログラムが始まるときに渡される「名前=値」の組のことで、API のキー(外部のサービスを使うための合言葉のような文字列)やデータベースのパスワードのように、コードに直接書きたくない値を渡すのに使われます。GitHub の文書も、秘密をコードに直接書かず、環境変数などを使うよう書いています。

先に、この記事の大事な点を3つ書いておきます。

  1. .env は、置いただけでは読まれません。読み込む道具(Node.js の --env-file、Vite、uv など)が、ファイルを読みに行きます
  2. Vite では、名前が VITE_ で始まる変数の値は、ビルドしたブラウザー向けのコードに入ります。.env.local に書いても同じです。秘密を入れてはいけません
  3. 秘密(API のキーなど)をコミットしてしまったときの最初の一手は、ファイルや履歴を消すことではなく、その秘密を失効させる・差し替えることです。これは GitHub の文書が書いている順番です

この記事は、Node.js・Vite・uv・GitHub・Git・Apple・zsh・Bash・POSIX・Microsoft の公式の文書を、2026年10月14日に開いて確かめたものです。コマンドは macOS 14.4.1(zsh 5.9・bash 3.2.57・Git 2.39.3)で流した結果を載せています。Node.js は v24.21.0、Vite は 8.3.4、uv は 0.12.10 です。Linux と Windows では何も流していないので、その部分は文書に書いてあることの範囲にとどめます。例に使う値は、すべて dummy のような偽の値です。本物のキーは使っていません。

この記事でわかること
  • 環境変数とは何か。PATH を例にした「パスを通す」の意味
  • Mac・Linux で環境変数を確認する方法と、その場だけ・ずっと設定する方法
  • .env とは何か。正式な仕様が無いこと、置いただけでは読まれないこと、Node.js と uv での読み込み方
  • Vite の VITE_ の値はブラウザーに出ること。.env.local でも同じこと
  • .env を Git に入れない方法と、秘密をコミットしてしまったときの順番(最初は失効・差し替え)
  1. 環境変数とは?(PATH を例に)
    1. 名前の決まりと、よく見る環境変数
    2. パスを通すとは?
    3. 子のプロセスに受け継がれる。でも、親には戻らない
  2. 環境変数を確認するには?(Mac・Linux)
    1. 1つの値を見る
    2. 一覧を見る。ただし、秘密も出ます
    3. 使っているシェルを確かめる
    4. Windows(PowerShell)では
  3. 環境変数を設定するには?その場だけ・ずっと・パスを通す
    1. その場だけ設定する(export)
    2. 1回のコマンドだけ設定する
    3. 消す(unset)
    4. ずっと設定する(設定ファイルに書く)
    5. パスを通す(PATH に足す)
    6. Windows で、ずっと設定するには
  4. .env とは?環境変数との関係
    1. .env は「環境変数を書いておくファイル」
    2. 置いただけでは読まれない
    3. Node.js で読む(--env-file)
    4. Python の uv で読む
    5. 道具によって違う例:${...} を広げるか
    6. 優先の決まりは、道具ごとに確かめる
    7. .env をシェルに読ませる(source)のは勧めません
  5. Vite の VITE_ の値は、ブラウザーに出る
    1. ビルドした JS に値が入る
    2. 読まれるファイルと優先
  6. .env を Git に入れないには?(.gitignore)
    1. .gitignore の書き方と確かめ方
    2. Vite のひな形は、.env を外していません
    3. すでにコミットしたファイルには、.gitignore は効きません
  7. 秘密をコミットしてしまったら?(最初は失効・差し替え)
    1. ① 最初に、その秘密を失効させる・差し替える
    2. ② これ以上入らないようにする
    3. ③ 過去のコミットには、まだ残っています
    4. ④ 履歴から消すかどうか:GitHub の文書に従う
    5. GitHub の secret scanning・push protection と、その範囲
    6. 次からのうっかりを減らすには
  8. よくある質問
    1. 環境変数を確認するコマンドは?(Mac)
    2. Vite で環境変数が undefined になるのはなぜ?
    3. Node.js で process.env が undefined になるのはなぜ?
    4. .gitignore に .env を書いたのに無視されないのはなぜ?
    5. .env(API キー)をコミットしてしまったらどうする?
  9. まとめ
  10. 参考資料

環境変数とは?(PATH を例に)

環境変数とは、プログラムが始まるときに渡される、名前=値 の形の文字列の並びです。Bash の文書は、プログラムが起動されるとき、環境と呼ばれる文字列の並びが渡され、それは name=value の形の名前と値の組の一覧だと書いています。POSIX(Unix 系の OS の共通の決まり)も、環境は name=value の形の文字列で、プロセスが始まるときに渡されると定めています。

名前の決まりと、よく見る環境変数

名前は、大文字と小文字が区別されます。POSIX によると、標準のコマンドが使う名前は、大文字・数字・_ だけでできていて、数字では始まりません。小文字を含む名前は、アプリが使うために空けてあります。

POSIX が説明しているものの中から、よく見るものを3つ挙げます。

名前意味(POSIX の説明)
HOMEログインしたときに設定される、利用者のホームディレクトリ(自分用のフォルダー)の場所
PATHコマンドの実行ファイルを探す場所の並び。:(コロン)で区切る
SHELL利用者が使うシェルの場所

シェルとは、ターミナルに打ったコマンドを受け取って実行するプログラムのことです。

パスを通すとは?

PATH は、前から順に探します。POSIX によると、コマンドの名前に / が入っていないときは、並びの先頭から末尾まで、各場所にその名前を当てはめて、実行できるファイルが見つかったところで止まります。名前に / が入っているときは、PATH を使った探索はしません。

「パスを通す」とは、コマンドの置き場を PATH に足して、名前だけで起動できるようにすることです。やり方は、あとの「環境変数を設定するには?」の節で書きます。

子のプロセスに受け継がれる。でも、親には戻らない

シェルから起動したコマンドは、環境を受け継ぎます。Bash の文書は、起動されたコマンドは環境を受け継ぎ、export と unset が環境にものを足したり消したりする、と書いています。

反対に、起動された側(子のプロセス)の中で環境変数を変えても、呼んだシェル(親)には戻りません。Node.js の文書には、process.env を書き換えても、その Node.js のプロセスの外には反映されないと書かれていて、「うまくいかない例」として次のコマンドが挙がっています。

node -e 'process.env.foo = "bar"' && echo "[$foo]"

Node.js v24.21.0 で流すと、[](空)が出ました。文書の例の echo $foo を、空かどうか見えるように echo "[$foo]" に変えています。

環境変数を確認するには?(Mac・Linux)

1つの値を見る

1つの値を見るには、echo か printenv を使います。

echo $HOME
printenv HOME

手元の zsh 5.9 と bash 3.2.57 では、どちらも同じ値が出ました。違いが出るのは、export していない変数です。次のスクリプトをファイルにして流した結果です。

EXAMPLE_KEY=dummy
echo "代入だけ: echo → [$EXAMPLE_KEY] / 子のプロセス(sh -c)→ [$(sh -c 'echo $EXAMPLE_KEY')] / printenv → [$(printenv EXAMPLE_KEY)]"
export EXAMPLE_KEY
echo "export のあと: 子のプロセス → [$(sh -c 'echo $EXAMPLE_KEY')] / printenv → [$(printenv EXAMPLE_KEY)]"
代入だけ: echo → [dummy] / 子のプロセス(sh -c)→ [] / printenv → []
export のあと: 子のプロセス → [dummy] / printenv → [dummy]

export する前は、シェルの中の変数として echo では見えても、子のプロセスと printenv からは見えませんでした。printenv が見せるのは、環境変数だけです。export の使い方は、次の節で書きます。なお、printenv の働きを説明した一次資料(公式の文書)は、この記事のために開いていません。ここは、手元の結果として書いています。

一覧を見る。ただし、秘密も出ます

引数なしの printenv(または env)を流すと、いまの環境変数が 名前=値 の形で全部並びます。

一覧には、自分の環境に入っている秘密の値(トークンなど)も、そのまま出ます。 画面を人に見せるとき、出力をチャットや掲示板にそのまま貼るときは、気をつけてください。

PATH の中身は、: で区切った場所の並びで出ます。手元で空の環境から始めて流したときは、/usr/bin:/bin と短く出ました。

使っているシェルを確かめる

echo $SHELL

SHELL は、先ほどの表のとおり、利用者が使うシェルの場所です。手元の Mac では /bin/bash と出ました。この Mac の利用者のログインシェルは、bash に設定されていたためです。

「Mac のシェルは zsh」と思い込まず、echo $SHELL で確かめてください。Apple の文書によると、macOS 10.15 以降の Mac は、zsh を既定のログインシェルと対話シェルとして使います。ただしこれは、10.15 以降に新しく作ったアカウントの既定の話です。10.14 以前の既定は bash です。

Windows(PowerShell)では

Microsoft Learn の文書(PowerShell 7.6 向けの頁)によると、PowerShell では $Env:名前 で環境変数を見たり変えたりできます。一覧は Get-ChildItem Env: です。この記事では Windows で何も流していないので、文書に書いてあることの紹介だけです。Windows に最初から入っている Windows PowerShell 5.1 で同じかは、この頁では確かめていません。

環境変数を設定するには?その場だけ・ずっと・パスを通す

その場だけ設定する(export)

export EXAMPLE_KEY=dummy

export は、変数を、あとから起動するコマンドの環境に渡すよう印をつける命令です(zsh の文書では、"marked for automatic export to the environment of subsequently executed commands")。設定ファイル(次の項)に書かなければ、あとから新しく開くシェルには入りません。

= の前後には、空白を入れません。前後に空白を入れた行を zsh に読ませると、手元では bad assignment と出て止まりました。

本物のキーを、このようにコマンドとして打つのは避けてください。 zsh は、対話シェルを抜けるとき、打ったコマンドの履歴を HISTFILE のファイルに保存します(HISTFILE が無ければ保存しません)。手元の macOS 14.4.1 の /etc/zshrc は、HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history と SAVEHIST=1000 を設定していました。本物のキーを export API_KEY=… のように打つと、このファイルに残りうるということです。ほかの macOS の版や、Linux の既定は確かめていません。bash の履歴の保存についても、確かめていません。

1回のコマンドだけ設定する

EXAMPLE_KEY=dummy コマンド

コマンドの前に代入を書くと、その代入は、そのコマンドの環境にだけ入ります(Bash の文書)。手元の zsh と bash では、コマンドの中では値が見え、そのあとのシェルでは空でした。

消す(unset)

unset EXAMPLE_KEY

手元で unset のあと printenv EXAMPLE_KEY を流すと、何も出ず、終了コードは 1 でした。

ずっと設定する(設定ファイルに書く)

新しいターミナルを開いても残したいときは、シェルの設定ファイルに export の行を書きます。どのファイルが、いつ読まれるのかは、次のとおりです。

  • zsh(Apple の対応表): .zprofile は .bash_profile に当たり、ログインのとき(SSH を含む)に読まれます。.zshrc は .bashrc に当たり、ターミナルの新しいセッションごとに読まれます。Apple は、環境変数の設定は、bash のプロファイルから zsh のプロファイルへそのまま移せると書いていて、例は export MY_SETTING=1 です
  • zsh(zsh の文書): /etc/zshenv、~/.zshenv(いつも)の順に読み、ログインシェルなら /etc/zprofile と ~/.zprofile、対話シェルなら /etc/zshrc と ~/.zshrc、ログインシェルなら最後に /etc/zlogin と ~/.zlogin を読みます。~ は、ZDOTDIR が無ければ HOME です。手元でも、zsh -c(対話でない)は .zshrc を読まず、zsh -i(対話)は読みました
  • bash(Bash の文書): 対話のログインシェルは、~/.bash_profile・~/.bash_login・~/.profile のうち、最初に見つかった1つを読みます。ログインシェルでない対話シェルは、~/.bashrc を読みます。ふつうは ~/.bash_profile の中から ~/.bashrc を読み込みます

zsh で ~/.zshrc に書く場合の例です(Apple の文書の例です)。

export MY_SETTING=1

書いたあと、いまのシェルに読み込むには、次のコマンドを流すか、新しいターミナルを開きます。

source ~/.zshrc

手元では、source の前は空で、あとは値が入りました。

ここでは ~/.zshrc を例にしましたが、.zshrc と .zprofile のどちらに書くべきかを決めた公式の文は、見つかりませんでした。上に書いたのは、Apple の対応表と zsh の文書の読む順です。Linux の配布物(Ubuntu など)の既定のシェルや、ターミナルがログインシェルかどうかは、確かめていません。

パスを通す(PATH に足す)

コマンドの置き場を PATH に足すときは、元の $PATH を残して、前か後ろにつなぎます。

export PATH="$HOME/bin:$PATH"

手元で、~/bin に置いた hello-example というスクリプトは、足す前は command not found(終了コード 127)でした。足したあとは動きました。どの実行ファイルが使われるかは、command -v で確かめられます。

$ export PATH="$HOME/bin:$PATH"
$ echo $PATH
/Users/example/bin:/usr/bin:/bin
$ command -v hello-example
/Users/example/bin/hello-example

出力のうち、HOME の実際の場所は /Users/example に置き換えています。PATH が短いのは、空の環境から始めたためです。command -v の一次資料(POSIX の command)は、この記事のために開いていないので、手元の出力として載せています。

前に書いた場所が先に探されます(先ほどの PATH の探し方のとおり)。$PATH を書かずに export PATH="$HOME/bin" のように代入すると、元の場所が無くなるので、ほかのコマンドが見つからなくなります。これは PATH の探し方から言えることで、手元では流していません。

PATH が通っていないときの詳しい話は、Python の uv の記事の「PATH が通らないとき」にもあります。

Windows で、ずっと設定するには

PowerShell で環境変数を変えても、その変更は今のセッションにだけ効く、と Microsoft Learn に書かれています。ずっと残す方法は、PowerShell のプロファイルに書く、SetEnvironmentVariable() メソッドを使う、システムのコントロールパネルを使う、の3つが挙がっています。この記事では、Windows で何も流していないので、手順は書きません。詳しくは、about_Environment_Variables(Microsoft Learn)を読んでください。

PATH の区切りは、Windows の PowerShell の例では ;(セミコロン)です。Linux・macOS では : を使います。また、setx コマンドには、1024文字の制限があります。超えると切られ、切られた値が既存の変数に入ると、前の値が失われうる、と Microsoft は書いています。このため、setx で PATH を書き換える手順は載せません。

.env とは?環境変数との関係

.env は「環境変数を書いておくファイル」

Node.js の文書は、.env ファイル(dotenv ファイルとも呼ばれる)を、環境変数を定義するファイルで、Node.js のアプリがそれを使える、と説明しています。広めたのは、npm の dotenv というパッケージだとされています。

.env には、正式な仕様がありません。 Node.js の文書は、この種のファイルは、さまざまな言語や道具で使われているが、正式な仕様は無いので、Node.js は自分で仕様を決めた、と書いています。つまり、道具によって、細かい読み方が違うことがありえます。違いの例は、この節のあとで書きます。

Node.js が決めている書き方は、次のとおりです。

  • 1行に1つ、名前=値 の形で書きます
  • 名前に使えるのは、英字・数字・_ で、数字では始まりません。大文字と _(必要なら数字)を使う書き方が勧められています
  • # から後ろはコメントです。ただし、引用符の中の # は、ふつうの文字として扱われます
  • 値は、' や " で囲めます。囲むと複数行も書けます。囲まない値は1行だけです
  • 行の前に export を付けても、無視されます
  • ファイル名は、ふつう .env か、.env で始まる名前(たとえば .env.dev)です。必須ではありません

値は、すべて文字列になります。 0 や true も、数や真偽値ではなく、文字列の '0' と 'true' になります。

手元で Node.js(v21.7.1 と v24.21.0)に読ませた .env は、次のものです。値は偽の値です。

# コメントの行
EXAMPLE_KEY=dummy
PORT=3000 # 行の後ろのコメント
QUOTED="dummy value"
export EXPORTED=dummy-export
NUM=0
FLAG=true
REF=${EXAMPLE_KEY}-ref

置いただけでは読まれない

.env は、ファイルを置いただけでは、環境変数になりません。読むのは、それを読む道具です。Node.js では、--env-file を付けずに process.env.EXAMPLE_KEY を見ると、undefined でした(手元の結果)。

Node.js で読む(--env-file)

node --env-file=.env app.js

Node.js の文書によると、今のフォルダーからの相対の場所にあるファイルから環境変数を読み込み、process.env で使えるようにします。上の .env を --env-file=.env で読ませると、手元では次のようになりました。

EXAMPLE_KEY = "dummy" string
PORT = "3000" string
QUOTED = "dummy value" string
EXPORTED = "dummy-export" string
NUM = "0" string
FLAG = "true" string
REF = "${EXAMPLE_KEY}-ref" string

# の後ろのコメントは落ち、export は無視され、値はすべて文字列でした。最後の REF は、${EXAMPLE_KEY} が広げられず、文字のまま入りました。

--env-file の決まりを、文書の記述にそって整理します。

  • 版: --env-file は v20.6.0 で追加されました。「実験的」の印が外れたのは v24.10.0 と v22.21.0 です。複数行の値は v21.7.0 と v20.12.0 から使えます。Node.js の文書では、.env の読み込みは、最新の版(v26.11.1 の表示)で「Stability: 2 - Stable」とされていますが、古い版では実験的でした。使う Node.js の版に合わせて、文書を確かめてください
  • 同じ名前が環境とファイルの両方にあるとき: 環境の値が勝ちます(Node.js の文書)。手元でも、EXAMPLE_KEY=from-shell node --env-file=.env … は "from-shell" でした。.env を書き換えたのに反映されないときは、シェルで先に export していないか見てください
  • 複数のファイル: --env-file は何度も書けて、後のファイルが前のファイルの値を上書きします。手元で .env のあとに、EXAMPLE_KEY=dummy-from-dev と書いた .env.development を読ませると、"dummy-from-dev" になりました
  • ファイルが無いとき: エラーになります。無くてもよいときは --env-file-if-exists を使います(v22.9.0 で追加)。手元の v24.21.0 では、無いファイルを --env-file に渡すと終了コード 9 で止まり、--env-file-if-exists では .env.missing not found. Continuing without it. と出て、続きを実行して終了コード 0 でした。手元の v21.7.1 には --env-file-if-exists が無く、エラーの文が出ました
  • node --run で起動するコマンド: --env-file で読んだ環境変数は渡されない、と文書にあります(手元では流していません)

コードの中から読むこともできます。process.loadEnvFile() は、既定では ./.env を読み込んで process.env に入れます(v21.7.0 と v20.12.0 で追加、v24.10.0 と v22.21.0 で実験的でなくなりました)。ファイルを process.env に入れずに、中身のオブジェクトだけを受け取る util.parseEnv() もあります。

import { loadEnvFile } from "node:process";
console.log("before:", process.env.EXAMPLE_KEY);
loadEnvFile();            // 既定は ./.env
console.log("after loadEnvFile():", process.env.EXAMPLE_KEY);
before: undefined
after loadEnvFile(): dummy

process.loadEnvFile() も、手元の v24.21.0 では、すでに環境にある値を上書きしませんでした(EXAMPLE_KEY=from-shell のとき from-shell)。ただし、この優先の決まりは、process.loadEnvFile() の文書の節には書かれていません。手元で確かめた結果です。

Python の uv で読む

uv では、uv run --env-file .env … で .env を読ませられます。何度も書け、後のファイルが前の値を上書きします。環境変数 UV_ENV_FILE でも指定できます(uv の文書)。手元の uv 0.12.10 では、付けないと None、付けると 'dummy' でした。すでに環境に EXAMPLE_KEY=from-shell があると、'from-shell' になりました。この「環境が勝つ」も、uv の文書のその節には書かれていません。手元の結果だけです。

uv の使い方の全体は、Python の uv の記事で説明しています。

道具によって違う例:${...} を広げるか

.env に正式な仕様が無い例を挙げます。上の REF=${EXAMPLE_KEY}-ref は、Node.js の --env-file では、手元の v21.7.1 と v24.21.0 のどちらでも広げられず、文字のまま入りました。一方、Vite は、.env の中の変数を dotenv-expand で広げる、と文書に書いています。$ を値の文字として使うときは、前に \ を付けます。Vite のほうは、手元では試していません。

同じ .env を、Node.js と Vite の両方で読ませる場合は、この違いに気をつけてください。

優先の決まりは、道具ごとに確かめる

環境にすでにある値と .env の値が食い違ったときの優先は、次のとおりでした。

道具優先根拠
Node.js の --env-file環境の値が勝つNode.js の文書
Vite起動したときにすでにある環境変数が、いちばん強いVite の文書
Node.js の process.loadEnvFile()環境の値が勝った手元の v24.21.0 の結果だけ(文書に書かれていない)
uv の --env-file環境の値が勝った手元の uv 0.12.10 の結果だけ(文書の節に書かれていない)

「どの道具でも環境が勝つ」と決めつけず、使う道具の文書を確かめてください。

.env をシェルに読ませる(source)のは勧めません

.env の書き方は道具ごとに違うので(上のとおり)、シェルの source で .env を読ませるのは勧めません。Node.js の文書には、export を付ければシェルでそのまま読める、という趣旨の説明がありますが、その文書の例の行は、= の前後に空白が入った形で、zsh で読むと bad assignment で止まりました(手元の zsh 5.9)。= の前後に空白が無ければ、読めました。

Vite の VITE_ の値は、ブラウザーに出る

Vite(React などで使うビルド道具)は、環境変数を import.meta.env というオブジェクトで読めるようにします。VITE_ で始まる変数は、Vite がまとめたあと(バンドル後)に、ブラウザーで動くコードの中に公開されます。 Vite の文書は、うっかり漏らさないために、秘密の変数にはこの接頭辞(名前の頭に付ける文字)を付けないよう書いています。

ビルドした JS に値が入る

手元の Vite 8.3.4(Node.js v24.21.0)で、次のファイルを置いて vite build を流しました。

# .env
VITE_SOME_KEY=dummy-public-123
DB_PASSWORD=dummy-secret-456

# .env.local
VITE_LOCAL_ONLY=dummy-local-789

読み出す側のコードです。

console.log("VITE_SOME_KEY:", import.meta.env.VITE_SOME_KEY);
console.log("DB_PASSWORD:", import.meta.env.DB_PASSWORD);
console.log("VITE_LOCAL_ONLY:", import.meta.env.VITE_LOCAL_ONLY);
console.log("typeof:", typeof import.meta.env.VITE_SOME_KEY);

ビルドしてできた dist フォルダーの中を、値で検索した結果です。

$ grep -ro dummy-public-123 dist | wc -l
1
$ grep -ro dummy-secret-456 dist | wc -l
0
$ grep -ro dummy-local-789 dist | wc -l
1

ビルドした JS の該当の部分は、次のとおりです(そのまま)。

console.log(`VITE_SOME_KEY:`,`dummy-public-123`),console.log(`DB_PASSWORD:`,void 0),console.log(`VITE_LOCAL_ONLY:`,`dummy-local-789`),console.log(`typeof:`,`string`)
  • VITE_SOME_KEY の値は、そのままコードに入っていました
  • VITE_ の付かない DB_PASSWORD の値は、コードの中に0件で、void 0(undefined)になっていました。秘密でない値が undefined のときは、名前の頭に VITE_ が付いているかを見ます。読むときは process.env ではなく import.meta.env を使います。ただし、VITE_ を付けた値は、ビルドしたコードに入り、ブラウザーから見えます。API のキーやパスワードのような秘密は、VITE_ を付けて読もうとせず、サーバー側で扱うことを検討します(Vite の文書)
  • .env.local に書いた VITE_LOCAL_ONLY も、コードに入っていました。「Git に入らない」ことと、「ブラウザーに出ない」ことは、別のことです
  • 値は文字列で、typeof は string でした。Vite の文書にも、VITE_SOME_KEY=123 は文字列の "123" になる、とあります

開発サーバー(vite)では試していません。確かめたのは vite build の出力だけです。

Vite の文書は、VITE_* の変数には、API のキーのような秘密を含めてはいけない、値はビルドのときにコードに埋め込まれる、と書いています。本番では、バックエンドのサーバーや、サーバーレス・エッジの関数で、秘密を守ることを検討するよう書かれています。

読まれるファイルと優先

Vite の文書が挙げるファイルは、次の4つです。

ファイル読まれるとき
.envいつも
.env.localいつも。文書は「git には入れない」と書く
.env.[mode]そのモードのときだけ
.env.[mode].localそのモードのときだけ。文書は「git には入れない」と書く

モードのファイル(たとえば .env.production)は、.env より優先されます。Vite を起動したときにすでにある環境変数は、いちばん強く、.env で上書きされません。.env は Vite の起動のときに読まれるので、変えたらサーバーを起動し直します。

表の「git には入れない」は、Vite が Git を設定してくれるという意味ではありません。そう働くのは、後で書くように、ひな形の .gitignore に *.local があるからです。

Vite の .env の話は、Vite とは?Create React App の代わりに React を始める方法【2026年版】の「環境変数は VITE_ で始める」でも説明しています。ここで書いたビルドの結果(値が dist に入ること)は、その先の話です。

.env を Git に入れないには?(.gitignore)

秘密が入る .env は、Git に入れないのが基本です。そのために使うのが .gitignore です。.gitignore の基本は、【完全入門】Git .gitkeep と .gitignore の違いと使い分けの話でも説明しています。

.gitignore の書き方と確かめ方

git-scm.com の文書によると、.gitignore は「わざと追跡しないファイル」を Git に無視させるためのファイルです。

.env
*.local

この2行を書くと、.gitignore のあるフォルダーと、その下のどの階層の .env にも当たります。パターンの先頭や途中に / が無いと、そのフォルダーより下のどの階層にも当たるためです。* は、/ 以外の何にでも当たるので、*.local は .env.local などに当たります。手元で .env と .env.local を置いて git status --short を流すと、この2つは出ませんでした。

無視されているか、どの行で無視されたかは、git check-ignore -v で確かめられます。

$ git check-ignore -v .env .env.local
.gitignore:1:.env	.env
.gitignore:2:*.local	.env.local

Vite の文書も、.env.*.local は手元だけのファイルで、秘密が入りうるので、.gitignore に *.local を足すよう書いています。

Vite のひな形は、.env を外していません

Vite のひな形を作る道具 create-vite の、.gitignore を調べました。create-vite 9.2.1 の16個のひな形すべてで、*.local の行はありましたが、.env の行はありませんでした。.env と .env.production は、git status で「追跡していないファイル(??)」として出て、.env.local と .env.production.local は外れました。

つまり、ひな形のままでは、git add . で .env までコミットされうるということです。.env を使うなら、自分で .gitignore に足してください。

すでにコミットしたファイルには、.gitignore は効きません

.gitignore は、すでに Git が追跡しているファイルには効きません(git-scm.com の文書)。.env をコミットしたあとで .gitignore に書いても、.env は追跡されたままです。手元の流れは次のとおりです。

$ git add .
$ git commit -q -m first
$ git ls-files
.env
app.js
(あとから .gitignore に .env を書き、.env の値を書き換えた)
$ git status --short
 M .env
?? .gitignore
$ git check-ignore -v .env
(何も出ない。終了コード 1)
$ git check-ignore -v --no-index .env
.gitignore:1:.env	.env

追跡中のファイルは、規則に当たっていても git check-ignore -v には出ません(--no-index を付けると出ます)。「.gitignore に書いたのに無視されない」ときは、先に git ls-files で、追跡されていないかを見てください。

追跡をやめるには、git rm --cached を使います。索引(次のコミットの候補)からだけ外し、手元のファイルは消えません。そのあと .gitignore に書けば、また入るのを防げます。

$ git rm --cached .env
rm '.env'
$ git status --short
D  .env
?? .gitignore

手元では、このあと ls -a で .env が残っていることも確かめました。GitHub の文書も、追跡すべきでないファイルは .gitignore に書き、その .gitignore の変更もコミットして push するよう書いています。ほかの開発者も守られるためです。

.gitignore に書いた .env は、Git の外にあります。そのため、git worktree で作る新しい作業フォルダーには入りません。git worktree で、この話がどう現れるかは、git worktree とは?使い方と削除(remove・prune)、注意点までで説明しています。

秘密をコミットしてしまったら?(最初は失効・差し替え)

ここでの「コミット」は、変更を Git に記録すること、「push」は、記録を GitHub などに送ることです。秘密(パスワード・トークン・資格情報)を含むファイルをコミットしてしまったときの順番を、GitHub の文書にそって書きます。

① 最初に、その秘密を失効させる・差し替える

GitHub の文書は、消したいデータが秘密(パスワード・トークン・資格情報)であることが多いとして、次のように書いています。

as a first step you need to revoke and/or rotate that secret.

「失効(revoke)」は、その秘密を無効にすること、「差し替え(rotate)」は、新しい秘密に取り替えることです。続けて GitHub の文書は、失効または差し替えをした秘密は、もうアクセスに使えなくなり、それで問題が片づくこともある、と書いています。履歴を書き換えて秘密を消す追加の手間は、要らないこともある、という意味の文が続きます(日本語版の文書は、この最後の文の訳が原文と意味がずれているので、英語の原文で確かめてください)。

API のキーのように、差し替えられる秘密なら、発行元のサービスで、そのキーを無効にして、新しいキーを作ります。具体的な手順はサービスごとに違い、この記事では扱いません。

ファイルを消す前に、まず失効・差し替え。順番を逆にしないでください。 理由は、GitHub の文書にあります。

  • 履歴を書き換えて force push(強制的な push)しても、秘密を含むコミットは、ほかの場所から見られうる。リポジトリのクローン(手元への複製)やフォーク(ほかの人が GitHub 上で自分のアカウントに作った複製)、GitHub 上の SHA-1(コミットの識別子)での直接の表示、それを参照する pull request などです
  • ほかの人のクローンからは、秘密を消せない。フォークにあるものは、フォークの持ち主に頼むしかない。GitHub は、その持ち主の連絡先を教えられない
  • GitHub Support は、機密でないデータは消さない。機密データの削除も、資格情報の差し替えでは危険を減らせない場合にだけ手伝う

② これ以上入らないようにする

失効・差し替えのあと、同じファイルが入り続けないように、追跡をやめて .gitignore に書きます。前の節の git rm --cached と .gitignore です。.gitignore の変更も、コミットして push します。

③ 過去のコミットには、まだ残っています

追跡をやめても、過去のコミットには値が残っています。 手元では、.env をコミットしたあと追跡をやめても、前のコミットから値が読めました。

$ git show HEAD~1:.env
EXAMPLE_KEY=dummy

git log -p -S EXAMPLE_KEY -- .env でも、足した差分と消した差分の両方が出ました。ファイルを消すだけでは、秘密は履歴から消えません。(push したあと、GitHub 側でどう見えるかは、手元では試していません。GitHub の文書の記述だけです。)

④ 履歴から消すかどうか:GitHub の文書に従う

履歴を書き換えて秘密を消す手順は、GitHub の文書の Removing sensitive data from a repository に載っています。専用の道具を使う手順と、リポジトリ全体を強制的に上書きする push を含み、そのあとにも、GitHub から完全に消すための追加の手順(GitHub Support への依頼を含む)が要ります。この記事は、手元で流していないので、そのコマンドは写しません。文書に従ってください。

その前に、GitHub の文書が書いている副作用を知っておいてください。

  • 書き換えの前のクローンを持つ人が、git pull のあとに git push すると、秘密が戻る
  • 秘密を含むコミットと、それ以降のすべてのコミットの、ハッシュ(識別子)が変わる
  • force push を止めるブランチの保護があれば、(少なくとも一時的に)外す必要がある
  • 閉じた pull request の差分が見られなくなる
  • コミットの署名が消える
  • 既存のクローンを持つ人には、履歴が食い違ったことで、秘密の場所を知られてしまう

だから、まず失効・差し替えです。差し替えた秘密なら、履歴に残っていても、もう使えないからです。

GitHub の secret scanning・push protection と、その範囲

GitHub には、秘密を見つける secret scanning(シークレットスキャン)と、push を止める push protection(プッシュプロテクション)があります。ただし、「GitHub が見つけてくれるから大丈夫」とは言えません。

  • secret scanning が探すのは、API キー・パスワード・トークンなどの、既知の種類の秘密です。リポジトリの全ブランチの Git の履歴全体を調べます
  • 使える範囲は、公開リポジトリでは無料で自動で動きます。組織が持つ非公開リポジトリは、GitHub Secret Protection が要ります(GitHub Team・Enterprise Cloud)。個人が持つ(User-owned)リポジトリで使えるのは、GitHub Enterprise Cloud の Enterprise Managed Users の場合と書かれています。個人の非公開リポジトリでは、ふつうは動かないと考えてください(2026年10月14日に取得した、Free・Pro・Team の版の文書)
  • GitHub.com の個人のアカウントに付く push protection は、既定で有効です。ただし、止めるのは、公開リポジトリへの秘密の push です。止められても、通す(bypass)ことができます
  • リポジトリ向けの push protection は、既定で無効で、GitHub Secret Protection が要ります

警告(アラート)が来たら、すぐに資格情報を差し替えるよう書かれています。履歴から消すこともできますが、手間がかかり、すでに失効させていれば要らないことが多い、というのも同じ方針です。これらの仕組みは、手元では試していません(本物の形の秘密を push しないと試せないため)。

次からのうっかりを減らすには

GitHub の文書は、うっかりコミットを避ける手として、次を挙げています。

  • 秘密をコードに直接書かず、環境変数などを使う
  • git add . や git commit -a を避け、ファイルを名前で git add する
  • git diff --cached で、これからコミットされる差分を見る
  • リポジトリで push protection を有効にする

GitHub Actions での秘密の値(secrets)の扱いは、GitHub Actions とは?使い方と、安全に使うための設定(権限・SHA 固定・secrets)で説明しています。また、手元の環境にある認証情報を探して持ち出す、npm のサプライチェーン攻撃の説明が、npm・yarn・pnpm の違いとは?どれを使う?(2026年版・サプライチェーン攻撃への備えまで)にあります。

よくある質問

環境変数を確認するコマンドは?(Mac)

1つの値は echo $HOME か printenv HOME、一覧は引数なしの printenv(または env)です。一覧には、手元の秘密の値もそのまま出るので、画面や出力を人に見せるときは気をつけてください。使っているシェルは echo $SHELL で確かめられます。くわしくは、本文の「環境変数を確認するには?」で説明しています。

Vite で環境変数が undefined になるのはなぜ?

Vite でブラウザー向けのコードに出るのは、名前が VITE_ で始まる変数だけです。VITE_ が付いていないと、undefined になります。読むときは process.env ではなく import.meta.env を使います。ただし、VITE_ を付けた値は、ビルドしたコードに入り、ブラウザーから見えます。API のキーやパスワードのような秘密は、VITE_ を付けて読もうとせず、サーバー側で扱うことを検討します(Vite の文書)。.env は Vite の起動のときに読まれるので、変えたらサーバーを起動し直します。

Node.js で process.env が undefined になるのはなぜ?

.env は置いただけでは読まれません。node --env-file=.env app.js のように読ませる必要があります。--env-file は v20.6.0 より前の Node.js にはありません。ファイルが無いとエラーになり、すでに環境に同じ名前があるときは、環境の値が使われます。

.gitignore に .env を書いたのに無視されないのはなぜ?

すでにコミットして、Git が追跡しているファイルには、.gitignore は効きません。git ls-files で追跡されていないかを確かめ、追跡されていたら git rm --cached .env で追跡をやめます(手元のファイルは消えません)。ただし、過去のコミットには値が残ります。

.env(API キー)をコミットしてしまったらどうする?

最初にすることは、その API キーを発行元のサービスで失効させて(無効にして)、新しいキーに差し替えることです。追跡をやめて .gitignore に書くのは、その後です。履歴を書き換えて消すのは、要らないこともあります。これは GitHub の文書が書いている順番です。くわしくは、本文の「秘密をコミットしてしまったら?」で説明しています。

まとめ

  • 環境変数は、プログラムが始まるときに渡される 名前=値 の組。子のプロセスに受け継がれるが、子で変えても親には戻らない
  • Mac・Linux では、echo・printenv で見る。export で設定する。ずっと残すには設定ファイルに書く。どのファイルが読まれるかは、使うシェルで違う
  • 「パスを通す」は、置き場を PATH に足すこと。元の $PATH を残して前か後ろにつなぐ
  • .env には正式な仕様が無く、置いただけでは読まれない。Node.js は --env-file、uv は --env-file で読む。道具ごとに文書を確かめる
  • Vite の VITE_ で始まる変数の値は、ビルドしたコードに入る。.env.local に書いても同じで、秘密は入れない
  • .gitignore は、すでに追跡しているファイルには効かない。Vite のひな形は .env を外していない
  • 秘密をコミットしてしまったら、最初に失効・差し替え。履歴から消すかどうかは、その後に GitHub の文書で決める
  • 本物のキーを、コマンドとして打たない(履歴に残りうる)。printenv の出力を人に貼らない。GitHub の secret scanning は、あてにしない

参考資料

この記事の内容は、2026年10月14日に開いた文書をもとにしています。GitHub Docs の頁には更新日が出ていません。

※この記事は、公式の資料をもとに AI(Claude)で下書きし、2026年10月14日時点の公式の資料と照らして確かめてから公開しています。誤りに気づいたらお問い合わせからお知らせください。

PR
PR
タイトルとURLをコピーしました