mise bootstrap で環境構築を完結するようにした

mise bootstrap で環境構築を完結するようにした

mise の native bootstrap で環境構築を完結させました。これまで `make all` とシェルスクリプトで管理していたシステムパッケージ、dotfiles、macOS 設定をすべて mise の設定ファイルに宣言的に書き直し、`mise bootstrap` 一本で実行できるようにした手順を紹介します。
2026.07.16

はじめに

こんにちは、クラスメソッド製造ビジネステクノロジー部の嶋田です。

環境構築を、mise の native な bootstrap へ寄せました。

mise はランタイムのバージョン管理ツールとして知られていますが、2026 年 7 月の v2026.7.4mise bootstrapmise dotfiles が experimental を卒業しました。これにより、システムパッケージの導入、dotfiles の symlink、macOS のシステム設定、ログインシェルの変更までを、mise の設定ファイルに宣言的に書けるようになりました。

本記事では、make all とシェルスクリプトで組んでいた環境構築を、mise の native bootstrap へ移した手順を紹介します。GUI アプリ(Homebrew の cask)を mise で入れられるかの検証と、同じく宣言的な環境管理で知られる Nix との比較もあわせて書きます。実際に自分の dotfiles へ入れた記録です。

なお、これらの機能は本記事の執筆時点(mise 2026.7.6)で GA になったばかりです。今後のアップデートで挙動が変わる可能性があります。

これまでの構成

移行前は make all を入口に、.bin/ のシェルスクリプト(Homebrew の導入、symlink、macOS 設定、brew bundle)を順に実行していました。パッケージは .Brewfile に、macOS 設定は defaults write を並べたスクリプトに書いていました。

mise はこの構成のなかで、~/.config/mise/config.toml にランタイムのバージョンを書くためだけに使っていました。環境構築の主役は Homebrew と自作スクリプトで、mise はその一部品でした。

mise の native bootstrap

mise bootstrap は、現在の設定に沿ってマシンを 1 コマンドで用意します。実行すると、次の手順を順に、宣言的かつ idempotent に適用します。

手順 設定セクション 内容
packages [bootstrap.packages] システムパッケージ(brew formula / brew-cask / mas / apt ほか)
repos [bootstrap.repos] git リポジトリの clone
dotfiles [dotfiles] $HOME への symlink / copy / render
defaults [bootstrap.macos.defaults] macOS のシステム設定
services [bootstrap.macos.launchd.agents] ほか 常駐サービス
user [bootstrap.user] ログインシェル
tools [tools] ランタイムの導入(mise install
task bootstrap タスク 宣言で表せない補助処理の逃がし先

各手順には前後に hook があり、宣言で表せない処理は最後の bootstrap という名前のタスクへ逃がせます。「宣言的な部分は各セクションに、手続き的な部分はタスクに」という切り分けです。

mise を使ったタスクランナーでシェルスクリプトを束ねる方法もありますが、symlink や macOS 設定のように mise 側が宣言的なセクションを持つものは、そちらへ寄せる方が設定が短く読みやすくなります。

移行の手順

設定をどこに置くか

bootstrap の設定は、dotfiles リポジトリ直下の mise.toml に置きました。

理由は、[dotfiles] の symlink がまだ張られていない初回時点でも設定を読めるようにするためです。~/.config/mise/config.toml はグローバル設定ですが、これ自体が dotfiles の symlink で作られるため、初回のブートストラップが自分の存在に依存してしまいます。リポジトリ直下の mise.toml なら、リポジトリを clone した直後から mise bootstrap を実行できます。mise.toml はドット始まりではないため、symlink の対象にもなりません。

システムパッケージを [bootstrap.packages] に

.Brewfile の内容を [bootstrap.packages] へ移しました。エントリは "マネージャ:パッケージ名" = "バージョン" の形式です。

[bootstrap.brew.taps]
"k1LoW/tap" = "https://github.com/k1LoW/homebrew-tap.git"

[bootstrap.packages]
"brew:ripgrep" = "latest"           # CLI formula
"brew:k1LoW/tap/git-wt" = "latest"  # tap つき formula
"brew-cask:google-chrome" = "latest" # GUI アプリ
"mas:539883307" = "latest"           # Mac App Store(数値は ADAM ID、LINE)

brew: が formula、brew-cask: が cask、mas: が Mac App Store のアプリです。tap は [bootstrap.brew.taps] に URL を書きます。

インストール済みの formula は mise bootstrap packages import で吸い出せるため、移行の下地はコマンドで作れます。

GUI アプリ(cask)は入れられるか

環境構築を mise で完結させるうえで、最大の焦点が GUI アプリでした。Chrome や Slack のような cask を mise で入れられるかです。

結論として、brew-cask: で宣言でき、インストールもできます。mise bootstrap -n(dry-run)で確認すると、次のように実際のバージョンを解決してインストール手順を組み立てます。

$ mise bootstrap -n
install cask google-chrome/150.0.7871.125
link app /Applications/Google Chrome.app
...
mas install 539883307 1429033973 497799835

仕組みとして、mise の brew-cask バックエンドは Homebrew の API(formulae.brew.sh)から cask のメタデータを直接取得します。Homebrew 本体のサブコマンドを介さずに cask を解決してインストールする形です。

移行時にはまり所がいくつかありました。

1 つは、パッケージ名が API のトークンと一致している必要があることです。旧 .Brewfilecask "aws-vault" としていたものは、API 上では cask ではなく formula でした(brew:aws-vault が正しい)。rustup-init も、現在の formula 名は rustup です。宣言を移すときは、API 上の正式なトークンへ直す必要があります。

次に cask と mas の状態管理です。mise の brew-caskmas のバックエンドは、mise 自身の状態でインストールを管理します。Homebrew や App Store 経由ですでに入っているアプリは「未インストール」と見なされ、apply すると入れ直そうとします。まっさらなマシンでは問題になりませんが、すでに使っているマシンへ後付けで導入する場合は、mise bootstrap -n で対象を確認してから適用するのが安全です。

最後に、バージョンの固定です。これは mise ではなく Homebrew 側の性質で、formula も cask も、任意の旧バージョンを後から入れる仕組みは基本ありません。固定できるのは、postgresql@16node@20 のようにメジャー版が別トークン(名前@版)として公開されている場合だけで、これは formula と cask に共通する唯一の正攻法です。ただし、こうしたバージョン付きトークンは formula の言語ランタイムやデータベースでは豊富な一方、GUI アプリの cask は最新版だけを配信するものが多いという差があります。私の環境では、CleanShot X の最新版が手持ちライセンスの対象外で、brew-cask:cleanshot だと未対応のバージョンが入ってしまう問題がありました。CleanShot にはバージョン付きのトークンがないため、このアプリだけは [bootstrap.packages] から外し、bootstrap タスク側で、公式 CDN が配信しているバージョン付きの DMG からライセンス済みのバージョンを固定して入れるようにしました。宣言で表せないものを最後のタスクへ逃がせる設計が、ここでも効きました。

dotfiles を [dotfiles] に

自作スクリプトで張っていた symlink を [dotfiles] へ移しました。ターゲットごとに source とモードを書きます。source はリポジトリルートからの相対パスです。

[dotfiles]
"~/.zshrc" = { source = ".zshrc", mode = "symlink" }
"~/.config" = { source = ".config", mode = "symlink" }
"~/.zsh" = { source = ".config/zsh", mode = "symlink" }

移行後に mise bootstrap dotfiles apply -n で確認すると、既存の symlink がすべて宣言と一致しました。

$ mise bootstrap dotfiles apply -n
mise files: all files are applied

なお、Claude Code の設定ディレクトリ(~/.claude)だけは、現状の symlink 構成が特殊で事故が怖かったため、宣言的な管理から外して手動のままにしました。宣言で表しにくいものを無理に押し込まず、外に置ける逃げ道がある点は扱いやすいところです。

macOS 設定を [bootstrap.macos.defaults] に

defaults write を並べていたスクリプトは、[bootstrap.macos.defaults] へ移しました。ドメインごとに、キーと値を書きます。値の型(整数、真偽値、文字列)で書き込みの型が決まります。

[bootstrap.macos.defaults]
"NSGlobalDomain" = { KeyRepeat = 2, InitialKeyRepeat = 15, AppleInterfaceStyle = "Dark" }
"com.apple.dock" = { autohide = true, tilesize = 59 }

こちらも dry-run で、既存の値とすべて一致することを確認できました。

$ mise bootstrap macos defaults apply -n
mise defaults: 16 value(s) already set

ログインシェルと逃がし先

ログインシェルの変更(旧構成の chsh)は [bootstrap.user] に書けます。

[bootstrap.user]
login_shell = "/bin/zsh"

宣言で表せない補助処理は、bootstrap という名前のタスクへ逃がします。私の場合は uv の導入と pnpm のグローバルパッケージがこれにあたります。このタスクは mise bootstrap の最後に自動で実行されます。

[tasks.bootstrap]
description = "uv と pnpm global パッケージを導入する"
run = [
  "curl -LsSf https://astral.sh/uv/install.sh | sh",
  "command -v pnpm >/dev/null 2>&1 && pnpm -g i || true",
]

初回だけのブートストラップ

mise bootstrap を実行するには、mise とリポジトリ本体が必要です。まっさらなマシンにはどちらもないため、この鶏卵を埋める最小の seed だけを 1 つのスクリプトに用意しました。

当初は「git を入れる、ghq を入れる、ghq でリポジトリを取得する、install.sh を実行する」という手順を想定していました。ただ、これは縮められます。ghq はリポジトリを <ルート>/github.com/owner/repo に置くディレクトリ規約なので、その場所へ普通に git clone すれば、あとから入る ghq がそのまま認識します。ghq を先に入れる必要はありません。git も、Homebrew のインストーラが Xcode Command Line Tools ごと用意します。

結果として、初回は次の 1 コマンドに収まりました。

$ curl -fsSL https://raw.githubusercontent.com/reona5/dotfiles/main/.bin/install.sh | bash

このスクリプトは、Homebrew を入れ(git もここで入る)、dotfiles を ghq レイアウトのパスへ clone し、mise を入れてから mise bootstrap を実行します。すでに mise が入っているマシンでの再セットアップは mise bootstrap の一本、まっさらなマシンでの初回はこの 1 コマンド、という切り分けです。

なお、dotfiles 以外の作業リポジトリも、[bootstrap.repos] に URL を書けば mise bootstrap がまとめて clone します。dotfiles リポジトリ自体だけは、設定がその中にある以上、この seed で先に取得する必要があります。

Nix との比較

宣言的な環境管理といえば、先行するのが Nix です。macOS では nix-darwinhome-manager を組み合わせ、パッケージ、dotfiles、macOS の system.defaults、さらには cask(Homebrew を経由)まで宣言的に管理できます。mise の native bootstrap がいま提供している範囲は、Nix がすでに実現してきたものです。

両者の性格の違いを整理します。

観点 mise bootstrap Nix (nix-darwin + home-manager)
位置づけ 既存ツールを束ねる宣言的レイヤ 独自ストアを持つ純粋関数型パッケージマネージャ
記述 TOML Nix 言語
再現性 latest は固定されず、Homebrew の現在の状態に依存 ハッシュ固定によるビルドの再現性
ロールバック なし(システムを直接変更する) 世代管理によるアトミックなロールバック
cask の扱い Homebrew API から取得 homebrew.casks で Homebrew に委譲
導入のしやすさ 既存の brew などをそのまま使う。可逆的 /nix ストアを導入する。学習コストが高い

要点は、mise が「いま使っているツール(Homebrew、App Store、シェル)を宣言的に束ねる薄い層」であるのに対し、Nix は「独自の仕組みで再現性とロールバックまで担う重い基盤」だということです。

Nix は、環境の再現性やアトミックなロールバックを求めるなら強力です。一方で、Nix 言語や flake の学習コストは高く(自分は一度 Nix ベースに移行しようと試みましたが、挫折した経験があります)、/nix ストアの導入は環境への踏み込みが大きくなります。mise の native bootstrap は、再現性やロールバックを Nix ほど厳密には保証しない代わりに、TOML と使い慣れたツールだけで環境構築を宣言的にまとめられます。すでに mise をランタイム管理で使っているなら、そのまま環境構築まで広げられる地続きさが利点です。

興味深いのは、cask の扱いがどちらも Homebrew に依存している点です。nix-darwin も homebrew.casks で Homebrew に委譲します。GUI アプリの導入は、宣言的な管理ツールにとって今なお Homebrew 頼みの領域だとわかります。

実行結果

最終的に、mise bootstrap -n が全手順を通して解決するようになりました。

$ mise bootstrap -n
mise bootstrap: system packages
mise brew: 42 package(s) already installed
install cask google-chrome/150.0.7871.125
link app /Applications/Google Chrome.app
mas install 539883307 1429033973 497799835
mise bootstrap: dotfiles
mise files: all files are applied
mise bootstrap: system defaults
mise defaults: 16 value(s) already set
mise bootstrap: login shell
mise login_shell: already set to /bin/zsh
mise bootstrap: tools
mise all tools are installed

dotfiles、macOS 設定、ログインシェル、ランタイムが現状と一致し、cask と mas はインストール手順として解決されました。

現状の到達点と限界

「環境構築が mise で完結するか」という問いに対しては、この GA でほぼ実現したというのが実感です。symlink、macOS 設定、システムパッケージ、cask、Mac App Store、ログインシェルまでを 1 つの mise.toml に宣言的に書き、mise bootstrap で適用できます。

残る限界は 3 つです。1 つは、cask と mas の状態管理が mise 独自のため、すでに導入済みのマシンでは再インストール扱いになること。1 つは、mise bootstrap の実行に mise 自身が必要で、初回の seed は外に残ること。もう 1 つは、これらが GA になったばかりで、常用にはまだ荒さが残ることです。宣言で表せない処理を bootstrap タスクへ逃がせるのは、この荒さを補ううえで実際に役立ちました。

おわりに

環境構築を mise の native bootstrap へ寄せた手順を紹介しました。

ランタイム管理で使っていた mise が、dotfiles とシステム設定まで宣言的に扱えるようになったことで、環境構築の入口が mise bootstrap の一本に整理できました。Nix ほどの再現性は持たない代わりに、TOML と使い慣れたツールだけで宣言的にまとめられる手軽さが、この方法の魅力だと感じています。

参考資料

この記事をシェアする

関連記事