「あれ、このMacにはfzf入ってなかったっけ...」
新しいMacをセットアップするたびに訪れる、あの微妙な不安。Homebrewでインストールしたはずのツールが見つからない。.zshrcの設定が古いバージョンのまま。そして気づけば半日が溶けている(昼休み返せ)。
開発者なら一度は経験するこの「dotfiles地獄」。新しいマシンをセットアップするたびに「前と同じにしたいだけなのに...」と思った経験、ありませんか?
Nix Flakesで脱出できるか実験してみました。
実験の動機
従来のdotfiles管理の限界
今まで使っていた管理方法:
# 昔ながらのやつ
git clone https://github.com/myname/dotfiles
cd dotfiles
./install.sh # ← 「これ、動くかな...」の祈り
この方法の問題点:
| 問題 | 具体例 | 心の叫び |
|---|---|---|
| 再現性なし | brew installの順番で結果が変わる | 「前のMacでは動いたのに...」 |
| バージョン固定困難 | Node.jsのバージョンがマシンごとに違う | 「なぜnpm installが落ちる」 |
| シェルスクリプト保守地獄 | if [ "$(uname)" = "Darwin" ]の嵐 | 「誰だこれ書いたの」(自分) |
| ロールバック不可 | 設定壊したら手動で直す | 「昨日の設定に戻りたい...」 |
Nixへの期待
Nixが約束してくれること:
- 宣言的設定: 「何をインストールしたいか」を書くだけ
- 再現性100%: 同じflake.lockなら同じ環境
- アトミック更新: 失敗したら自動ロールバック
...本当かな?(疑り深い性格なので、自分で試してみないと気が済まない)
実験環境
| 項目 | 内容 |
|---|---|
| 対象マシン | MacBook Pro (Apple Silicon) |
| OS | macOS Sequoia |
| 移行元 | Homebrew + シェルスクリプト |
| 移行先 | Nix Flakes + nix-darwin + home-manager |
構成設計
ディレクトリ構造
最終的にこうなりました:
dotfiles/
├── flake.nix # 全体の統括(ここが司令塔)
├── flake.lock # バージョン固定(これが再現性の要)
├── darwin/
│ └── default.nix # macOSシステム設定
├── home-manager/
│ ├── default.nix # ユーザー設定エントリポイント
│ ├── home/
│ │ └── default.nix # パッケージとdotfiles
│ └── programs/ # 各プログラムの設定
└── common/
└── packages.nix # 共通パッケージ
責務分離の考え方
- 🍎 nix-darwin … システムレベル(macOS全体の設定)
- 🏠 home-manager … ユーザーレベル(個人の開発環境)
実装のハイライト
flake.nixの心臓部
{
description = "Home Manager configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
nix-darwin = {
url = "github:LnL7/nix-darwin";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, home-manager, nix-darwin, ... }:
let
# 複数ユーザー対応(会社用と個人用で分けたい人向け)
mkHomeConfig = username: {
"${username}-darwin" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgsFor."aarch64-darwin";
modules = [ ./home-manager/default.nix ];
};
};
in {
# ... 設定が続く
};
}
ポイント:inputs.nixpkgs.followsで依存関係を揃えることで、バージョンの不整合を防ぐ。
nix-darwinでmacOSをコード化
# darwin/default.nix
{
# Dockの設定(UIで毎回ポチポチしなくていい)
system.defaults.dock = {
autohide = true;
orientation = "bottom";
tilesize = 50;
};
# Finderの設定
system.defaults.finder = {
FXPreferredViewStyle = "clmv"; # カラム表示
ShowPathbar = true;
ShowStatusBar = true;
};
# キーリピート爆速設定(コーディングに必須)
system.defaults.NSGlobalDomain = {
InitialKeyRepeat = 10; # 最速
KeyRepeat = 1; # 最速
};
# Touch IDでsudo(これがないと生きていけない)
security.pam.services.sudo_local.touchIdAuth = true;
}
home-managerでCLIツールを管理
# home-manager/home/default.nix
{
home.packages = with pkgs; [
# 検索系
ripgrep # grepより速い
fd # findより速い
fzf # ファジーファインダー
# Git系
gh # GitHub CLI
ghq # リポジトリ管理
lazygit # TUIのGitクライアント
# モダンCLI
bat # catより見やすい
eza # lsより見やすい
zoxide # cdより賢い
starship # プロンプトをかっこよく
# 開発ツール
mise # asdf後継
devcontainer # VSCode Dev Containers
];
}
マルチプラットフォーム対応
WSL環境とmacOSで同じdotfilesを使いたい場合にも対応できます:
# flake.nix
mkHomeConfig = username: {
# macOS (Apple Silicon)
"${username}-darwin" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgsFor."aarch64-darwin";
modules = [ ./home-manager/default.nix { _module.args.username = username; } ];
};
# Linux (x86_64) - WSL等
"${username}-linux-x86" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgsFor."x86_64-linux";
modules = [ ./home-manager/default.nix { _module.args.username = username; } ];
};
};
usernames = [ "naramotoyuuji" "yuji_naramoto" ];
これで1つのFlakeで複数ユーザー・複数プラットフォームに対応。会社PCと個人PC、macOSとWSLで微妙に違う設定も1つのリポジトリで管理できます。
移行でハマったところ
1. Homebrew Casksとの共存
問題: GUIアプリはNixで管理しにくい(署名の問題など)
解決: nix-darwinのhomebrew連携を使う
homebrew = {
enable = true;
onActivation = {
autoUpdate = true;
cleanup = "uninstall"; # casksにないものは消す
};
casks = [
"cursor"
"slack"
"1password"
# ... GUIアプリはこっちで管理
];
};
CLIはNix、GUIはHomebrew。役割分担が大事。
2. シークレット管理
問題: APIキーやトークンをGitにコミットしたくない
解決: テンプレートファイル方式
home.file = {
# テンプレートだけバージョン管理
".config/git/config.local.template".source = ./file/git/config.local.template;
# 実際の設定は.localファイル(.gitignore対象)
};
# セットアップ時に手動コピー
cp ~/.config/git/config.local.template ~/.config/git/config.local
# APIキーを編集
3. 更新スクリプトの統一
問題: nix flake updateとhome-manager switchとnix-darwin switchを毎回打つのが面倒(3回もコマンド打つの?という気持ち、わかりますよね?)
解決: flake.nixにappsを定義
apps = forAllSystems (system: {
update = {
type = "app";
program = toString (pkgs.writeShellScript "update" ''
nix flake update
nix run home-manager -- switch --flake .#$USER-darwin
sudo nix run nix-darwin -- switch --flake .#MyMBP
echo "✅ 更新完了!"
'');
};
});
これでnix run .#update一発。
実験結果
Before / After
| 指標 | Before(Homebrew) | After(Nix) | 感想 |
|---|---|---|---|
| 新Mac初期セットアップ | 3〜4時間 | 30分 | 「早く帰れる」 |
| 再現性 | 70%くらい | 100% | flake.lockの勝利 |
| ロールバック | 不可能 | 1コマンド | 「元に戻せる安心感」 |
| 学習コスト | - | 高め | Nix言語独特すぎる |
| デバッグ難易度 | - | 高め | エラーメッセージ解読班求む |
良かったこと
-
新しいMacの恐怖がなくなった
./setup.sh naramotoyuujiで30分後には完全な開発環境- 「あのツール何だっけ」問題が消滅
-
設定変更が怖くなくなった
- 実験的な設定変更 → 壊れた →
nix profile rollback - 気軽にいじれる
- 実験的な設定変更 → 壊れた →
-
複数ユーザー・複数プラットフォーム対応
- 会社用・個人用の設定を分離
- macOSとWSLで同じdotfilesを共有
nix run .#update yuji_naramotoで切り替え
正直しんどかったこと
-
Nix言語の独特さ
- 関数型言語なので最初は戸惑う
let ... inやwithの使い方に慣れるまで時間かかる
-
エラーメッセージの難解さ
- 「何が悪いかわからない」ことが多い
- Stack Overflowとにらめっこする時間が増えた
-
ディスク容量
/nix/storeが結構膨らむ- 定期的に
nix-collect-garbage必要
日常の運用
環境アップデート
nix run .#update
1コマンドで:
- flake.lockの依存関係更新
- home-managerの設定反映
- nix-darwin(システム設定)の反映
新しいMacへの移行
# 1. リポジトリをclone
git clone https://github.com/yourname/dotfiles
cd dotfiles
# 2. ローカル設定ファイルを作成
cp home-manager/home/file/git/config.local.template \
home-manager/home/file/git/config.local
# (名前とメールアドレスを設定)
# 3. セットアップ実行
./setup.sh naramotoyuuji
これで全部揃う。
まとめ
Nix Flakesでdotfiles管理、結論:やってよかった。
学習コストは確かに高い。最初の1週間は「Homebrew戻ろうかな...」と何度も思った。でも、一度設定が固まってしまえば:
- 新しいMacの恐怖から解放される
- 設定変更を気軽に試せる
- 「このMacどうなってたっけ」問題が消える
特にチームで同じ開発環境を揃えたい場合や、macOSとWSLを行き来する場合は、投資する価値がある。
「ちょっと興味あるけど、いきなり全部は...」という人は、まずhome-managerだけから始めるのがおすすめ。CLIツールの管理から試して、慣れてきたらnix-darwinに手を出す。
気づけば深夜2時までflake.nixをいじっている自分がいるかもしれない(経験談)。それでも「環境が壊れる恐怖」から解放される喜びには代えられない。



