
direnv 入門 - コミットせずに手元だけポート番号を変える
シェルや環境変数の土台を見直せる一冊。
シェル操作と環境変数の基本を押さえる。
ignoreやステータスの基礎を確認できる。
当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。
別のプロジェクトがすでに3000番ポートで動いていて、いま触っているリポジトリの開発サーバーを3001番で起動したい。そういう場面がよくあります。package.jsonのdevスクリプトを書き換えれば済みますが、それは自分の環境だけの都合なので、コミットに混ぜたくありません。差分に残ったままだと、別の作業のコミットにうっかり含めてしまう危険もあります。
この「コミットはしたくないが、手元だけ少し設定を変えたい」に、direnvがよく合いました。この記事では、ポート番号を手元だけ変える例を軸に、direnvの基本的な使い方をまとめます。
direnvとは
direnvは、カレントディレクトリに応じて環境変数を読み込んだり外したりするシェル拡張です。公式サイトの説明では、プロンプトを表示する直前に、現在のディレクトリと親ディレクトリに.envrcがあるかを確認します。見つかった.envrcはbashのコードとしてサブシェルで実行され、そこでexportされた変数だけが今のシェルに反映されます。
対応シェルはbash、zsh、fish、tcsh、elvish、PowerShell、murex、Nushellです。~/.zshrcに案件ごとの変数を書き足していく運用から抜け出せるのが、一番の利点です。
インストールとシェルへの組み込み
macOSならHomebrewで入ります。
brew install direnv
direnv version
# 2.37.1入れただけでは動きません。シェルに「プロンプトの前にdirenvを呼ぶ」フックを仕込む必要があります。zshの場合は、公式ドキュメントのとおり~/.zshrcの末尾に1行足します。
eval "$(direnv hook zsh)"シェルを開き直せば準備完了です。Oh My Zshを使っている場合は、direnv用のプラグインも用意されています。
3000番ポートを手元だけ3001番にする
開発サーバーを起動するリポジトリの直下に、.envrcを作ります。
export PORT=3001作った直後は、まだ読み込まれません。direnvは知らない.envrcを勝手に実行しないので、次のエラーが出ます。
direnv: error /path/to/project/.envrc is blocked. Run `direnv allow` to approve its content中身を確認したうえで承認します。
direnv allow .direnv: loading /path/to/project/.envrc
direnv: export +PORTexport +PORTは「PORTが追加された」という意味です。これで、このディレクトリにいる間はPORT=3001が効いた状態になり、npm run devをそのまま叩けば3001番で起動します。package.jsonには一切手を入れていません。
Next.jsでは.envにPORTを書いても効かない
「それなら.env.localにPORT=3001と書けばいいのでは」と思うかもしれませんが、Next.jsではうまくいきません。公式ドキュメントのCLIリファレンスに、次の注記があります。
PORTcannot be set in.envas booting up the HTTP server happens before any other code is initialized.
HTTPサーバーの起動が.envの読み込みより先に走るため、PORTだけは.envから設定できない、という説明です。next devとnext startのポートは、-pオプションか、起動時点で環境変数PORTに入っている値で決まります。シェルの環境変数そのものを差し替えるdirenvは、この制約にちょうど合います。
注意点が1つあります。package.jsonにnext dev -p 9001のようにポートが直書きされていると、そちらが使われます。手元のNext.js 16.1.6で、PORT=9555を渡しつつ-p 9666を指定して起動したところ、Local: http://localhost:9666となりました。スクリプトに-pが無いリポジトリなら、direnvのPORTがそのまま効きます。
.envrcをコミット対象から外す
.envrcも、放っておけばgit statusに未追跡ファイルとして出てきます。ここで.gitignoreに追記すると、今度は.gitignore自体の変更がコミット対象になってしまいます。
自分の手元だけで無視したい場合は、.git/info/excludeを使います。Gitのドキュメントでは、リポジトリ固有だが他の人と共有する必要のないパターンを書く場所として紹介されています。書式は.gitignoreと同じで、.gitの中にあるのでコミットされません。
echo '.envrc' >> .git/info/exclude
git status --short --ignored!! .envrc!!は「無視されているファイル」を表します。通常のgit statusにはもう出てこないので、git add -Aで巻き込む心配もなくなります。リポジトリをcloneし直すと.git/info/excludeも消える点だけ覚えておいてください。
書き換えると再承認が必要になる
.envrcを書き換えると、direnvは再びブロックします。試しにPORT=3002に変えると、承認前と同じis blockedのエラーに戻りました。内容が変わったファイルを黙って実行しない作りです。
毎回direnv allowを打つのが面倒なら、direnv edit .を使います。$EDITORで.envrcを開き、閉じたあとにそのまま承認まで済ませてくれます。
ディレクトリから出ると、読み込んだ変数は外れて元の状態に戻ります。別の案件のディレクトリに移ったらPORTが3001のまま残っていた、ということは起きません。
チームで.envrcを共有している場合
リポジトリによっては、.envrc自体がコミット済みのこともあります。その場合は共有の.envrcに次の1行を入れてもらい、個人用の上書きは.envrc.localに書く形が使えます。
export DATABASE_URL=postgres://localhost:5432/app_dev
source_env_if_exists .envrc.localexport PORT=3001source_env_if_existsは、指定したファイルがあれば読み込み、無ければ何もしないdirenvの標準関数です。.envrc.localは先ほどと同じく.gitignoreか.git/info/excludeで無視しておきます。
よく使う標準関数
.envrcの中では、direnvが用意している標準関数(stdlib)を呼べます。公式のマニュアルから、使い道の多いものを挙げます。
| 関数 | 役割 |
|---|---|
dotenv | .envを読み込む。dotenv_if_existsは無ければ何もしない |
source_env_if_exists | 別の.envrcを、存在する場合だけ読み込む |
source_up | 親ディレクトリをさかのぼって.envrcを探して読み込む |
PATH_add bin | 指定したディレクトリをPATHの先頭に足す |
layout node | ./node_modules/.binをPATHに足す |
watch_file | 指定したファイルが変わったら再読み込みする |
env_vars_required | 必要な変数が空ならエラーを出す |
たとえばlayout nodeを書いておくと、npxを付けずにnextやeslintをそのまま実行できます。
また、~/.config/direnv/direnv.tomlにload_dotenv = trueを設定すると、.envrcに加えて.envも自動で読み込むようになります。
使うときの注意
.envrcはbashのコードとして実行されます。cloneしたばかりの他人のリポジトリで、中身を見ずにdirenv allowしないでください。承認の仕組みはこのためにあります。- 環境変数は暗号化されません。APIキーなどを
.envrcに書く場合も、扱いは.envと同じです。詳しくは環境変数と.envによる設定と秘密情報の管理で書きました。 - 読み込みのきっかけはシェルのプロンプトです。ターミナルを経由しないプロセスには効かないので、そうした場面では
direnv exec <ディレクトリ> <コマンド>で明示的に読み込んで実行します。
手元だけの小さな設定変更は、これまでなら「変更したけど戻し忘れる」「コミットから外し忘れる」の二択になりがちでした。.envrcと.git/info/excludeの組み合わせなら、リポジトリには何も残さずに済みます。.gitignoreや除外の仕組みについてはGit 基本コマンドとエイリアスもあわせてどうぞ。


