動画で読む
まず結論 — mise は「ランタイム・env・タスク」を 1 ファイルに畳む道具
最初に結論を書きます。mise(旧 rtx、Rust 製のツール)を入れると、Node や Python のバージョン管理・環境変数の出し入れ・make のようなタスク実行を、プロジェクト直下の mise.toml 1 ファイルに寄せられます。これまで asdf で言語、direnv で env、Makefile でタスク、と 3 つの道具に分けていたものが 1 つの設定に畳まれる、というのが導入してみての一番の手応えでした。元ネタは Zenn のmise 開発環境ガイドで、私はこれを個人開発と小さなチームの運用目線で試しました。
mise は単なる「asdf の速い版」ではありません。確かに asdf 互換のプラグインで言語ランタイムを管理できますが、本質は [tools] / [env] / [tasks] という 3 つのセクションを 1 ファイルに同居させて、ローカルと CI で同じ手順を再現できる点にあります。何を一元化できて、何が落とし穴かを、移行の判断から順に棚卸しします。
なぜ asdf + direnv + Makefile の三重管理をやめたか
結論から言うと、設定ファイルが 3 つに散ると「どこに何を書いたか」を毎回思い出す税金が地味に高い、というのが移行の動機です。.tool-versions(asdf)と .envrc(direnv)と Makefile が同じリポジトリ直下に並んでいて、新しいマシンでセットアップするたびに 3 つを順番に確認していました。
私が痛い目を見たのは、ある金曜の夜のことです。新しい MacBook に環境を移したとき、asdf のプラグインは入れたのに direnv の allow を忘れていて、env だけが読まれず、Node は動くのに DB に繋がらない、という中途半端な状態で 30 分ほど首をかしげました。原因は単純で、道具が 3 つあれば「allow し忘れる道具」も 3 つに増えるというだけの話です。この手の事故は作業ログを測ってから自動化する習慣で減らせると分かっていたのに、肝心のセットアップ手順そのものが分散していては測りようがありませんでした。
mise はこの三役を 1 ファイルに統合します。要件を「思想」と「仕様」に分けて考える話を別記事で書きましたが、mise でいう思想は「環境の真実を 1 ファイルに集約する」、仕様は「[tools] / [env] / [tasks] の 3 セクション」です。
tools — ランタイムを mise.toml に宣言する
まず [tools] セクションです。ここに書いた言語やツールのバージョンを、mise install 一発で揃えられます。.tool-versions の代わりだと思えば移行は怖くありません。
# mise.toml — プロジェクト直下に置く
[tools]
node = "22"
python = "3.12"
"npm:@anthropic-ai/claude-code" = "latest"
書いたら mise install で取得、mise use node@22 でそのディレクトリの既定に固定できます。グローバルに入れたいときは mise use --global node@22 で ~/.config/mise/config.toml に書かれます。一時的に試すだけなら mise exec python@3.12 -- python で、インストールを汚さずに実行だけできます。npm や pipx、GitHub release から直接ツールを引けるので、「言語ランタイムは asdf、CLI は brew」という二重管理もここに寄せられました。
地味に効くのが、ディレクトリに入った瞬間に必要なツールへ自動で切り替わることです。Sidekiq の cron と同じで、ちゃんと切り替わっている間は誰も気付かず、切り替わらなかった日にだけ存在を思い出す。それが正しい道具だと思います。
env — direnv を置き換える環境変数管理
次が [env] です。結論として、direnv でやっていた「ディレクトリに入ったら env を読む」は mise の [env] でほぼ置き換えられます。.env ファイルの読み込みも、PATH への追加も、設定ファイルの中で完結します。
[env]
NODE_ENV = "development"
DATABASE_URL = "mysql2://localhost/app_dev"
# .env ファイルを読み込む(direnv の dotenv 相当)
_.file = ".env"
# ./bin を PATH 先頭に足す
_.path = ["./bin"]
_.file で dotenv を読み、_.path でローカルの実行ファイルを PATH に通す。direnv の .envrc に書いていた内容が、ほぼそのまま [env] に収まります。私の場合、.envrc で dotenv と PATH_add bin を呼んでいただけだったので、移行は 5 行のコピペで終わりました。
注意は 1 点。秘密情報を直書きせず、.env 側に逃がして _.file で読む構成にしておくこと。mise.toml はコミットする前提のファイルなので、ここに API キーを書くと git 履歴に残ります。私がGit の「しまった」を仕組みで吸収する運用を気にしているのも、こういう「コミットしてはいけないものをコミットする」事故を一度やらかしているからです。
tasks — Makefile をやめてタスクを mise に寄せる
[tasks] は make の置き換えです。結論を先に言うと、タスクを mise に寄せる最大の利点は「タスク実行前に必要なツールが自動で入る」ことと、依存タスクを並列で走らせてくれることです。
[tasks.build]
description = "フロントをビルドする"
run = "npm run build"
[tasks.test]
description = "テストを流す"
depends = ["build"]
run = "npm test"
mise run test を叩くと、depends に書いた build が先に走り、しかも依存関係が独立していれば並列で実行されます。make でやっていた .PHONY の管理や、タブとスペースの取り違えで動かない悲劇から解放されます。長いシェルスクリプトは mise-tasks/ ディレクトリにファイルとして置き、先頭に #MISE description="..." コメントを書けば、同じく mise run で呼べます。タスク内では MISE_PROJECT_ROOT などの環境変数が自動で渡るので、モノレポでもパスがぶれません。
ローカルと CI を同じ手順に揃える
最後が運用の本丸です。mise を入れて一番効いたのは、ローカルと CI(GitHub Actions など継続的インテグレーション)で「まったく同じ mise.toml を読ませる」だけで手順が揃うことでした。CI 用に別のセットアップを書かなくて済みます。
# .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v2
- run: mise run test
jdx/mise-action が mise.toml を読んでツールを揃え、あとは mise run test を呼ぶだけ。ローカルで mise run test が通れば CI でも同じことが起きる、という対称性が手に入ります。24/7 の自動化で踏んだ罠を別記事に書きましたが、その教訓の多くは「ローカルと本番で手順が違う」ことに起因していました。mise はその差を縮めます。
ちなみにバージョンの完全固定が要るときは、設定で lockfile = true を有効にすると mise.lock が生成され、ツールの解決バージョンを固定できます。package-lock.json の言語ランタイム版だと思えば腑に落ちます。
運用者としての落とし穴と判断
棚卸しすると、導入判断は「散らばった道具を 1 ファイルに集約する価値があるか」で決まります。便利な反面、気をつける点もあります。
最大の落とし穴は mise trust です。mise は信頼していない mise.toml のタスクや env を勝手に実行しません。これはセキュリティ上正しい挙動ですが、clone 直後に mise install しても env が読まれず「なぜ動かない」と詰まることがあります。答えは mise trust を一度叩くこと。私はこれを知らずに、CI ではないローカルで env が空のまま 10 分悩みました。チームに展開するときは、README に「最初に mise trust を実行」と 1 行書いておくだけで、新メンバーの初日が静かになります。
| やりたいこと | これまで | mise |
|---|---|---|
| ランタイム管理 | asdf (.tool-versions) | [tools] |
| 環境変数 | direnv (.envrc) | [env] (_.file / _.path) |
| タスク実行 | Makefile | [tasks] (mise run) |
| CI セットアップ | 個別に記述 | jdx/mise-action で同一設定 |
| バージョン固定 | 各ツール任せ | mise.lock |
私はソロ開発でも、この一元化を入れてからセットアップ手順書が短くなりました。道具を 1 つにまとめる価値は、賢い機能そのものより「思い出すことが減る」点にあります。それだけ持ち帰ってもらえれば十分です。
よくある質問
- mise は asdf や direnv の完全な置き換えになりますか?
- ランタイム管理は asdf 互換のプラグインで、環境変数は [env] の _.file / _.path で direnv 相当を、タスクは [tasks] で make 相当を 1 ファイルに集約できます。多くの個人開発・小規模チームの用途では三つを mise.toml に寄せられます。
- clone 直後に env やタスクが動かないのはなぜですか?
- mise は信頼していない mise.toml を勝手に実行しないためです。mise trust を一度実行すると、その設定の env とタスクが有効になります。チームでは README に「最初に mise trust」と書いておくと初日の事故が減ります。
- mise.toml に秘密情報を書いてよいですか?
- 書かない方が安全です。mise.toml はコミット前提のファイルなので、API キーなどは .env に逃がし、[env] の _.file = ".env" で読み込む構成にします。これで秘密情報が git 履歴に残るのを避けられます。
- ローカルと CI でバージョンを完全に固定するには?
- 設定で lockfile = true を有効にすると mise.lock が生成され、解決されたツールのバージョンが固定されます。package-lock.json の言語ランタイム版だと考えると分かりやすいです。