Day 29:系統維護與最佳化 — 讓你的 NixOS 跑得又快又乾淨
學習 NixOS 的日常保養,包含用 nix-tree 分析依賴關係、管理磁碟空間、最佳化編譯速度,以及建立系統健康檢查流程。
Day 29:系統維護與最佳化 — 讓你的 NixOS 跑得又快又乾淨
🗓 系列:NixOS 30 天學習之旅
📦 階段:第四階段 — 工程師進階實務(Day 22 – Day 30)
🎯 階段核心目標:佈署、自動化、安全性與貢獻
前言:NixOS 也需要保養
經過這 28 天的旅程,你的 NixOS 上大概已經累積了不少東西——各種 generation、開發環境的 derivation、測試用的套件、甚至好幾個版本的 kernel。這些東西全都乖乖地躺在 /nix/store 裡,一個都沒被刪掉。
這正是 NixOS immutable 設計的特性:舊版本不會被覆蓋,只會持續疊加。好處是你隨時可以 rollback,壞處是……磁碟空間會被慢慢吃光。
今天我們來聊聊 NixOS 的「日常保養」:如何用 nix-tree 分析空間占用與依賴關係、如何有效管理磁碟空間、如何最佳化編譯速度,以及建立一套可持續的系統健康檢查流程。
用 nix-tree 分析依賴關係與空間占用
安裝 nix-tree
nix-tree 是一個互動式的 terminal 工具,讓你用樹狀結構瀏覽 /nix/store 中的依賴關係與空間占用。先裝起來:
# configuration.nix
environment.systemPackages = with pkgs; [
nix-tree
];
或者臨時用 nix-shell 跑一下也行:
nix-shell -p nix-tree
基本用法
最直覺的用法是直接對當前系統 profile 進行分析:
# 分析目前系統的依賴樹
nix-tree /run/current-system
執行後會進入一個互動式介面,你可以用方向鍵瀏覽各個 dependency,畫面上會顯示每個套件的大小(包含自身大小與 closure 大小)。
幾個常用的操作:
| 按鍵 | 功能 |
|---|---|
Enter | 展開 / 收合子節點 |
w | 切換顯示模式(Why depends) |
s | 依 closure size 排序 |
q | 離開 |
找出空間大戶
想知道哪些套件佔用最多空間?用 --derivation flag 搭配排序:
# 列出目前系統所有 dependency 的大小,依 closure size 排序
nix path-info -rsSh /run/current-system | sort -k2 -hr | head -20
這條指令的意思是:
-r:遞迴列出所有 dependency-s:顯示 NAR size(套件自身大小)-S:顯示 closure size(含所有依賴的總大小)-h:人類可讀的大小格式
輸出大概會長這樣:
/nix/store/abc123...-linux-6.6.30 156.2M 156.2M
/nix/store/def456...-gcc-13.2.0 65.4M 198.7M
/nix/store/ghi789...-llvm-17.0.6 42.1M 310.5M
...
看到哪些套件的 closure size 特別大,就可以進一步分析它拉進了哪些依賴,思考是否有替代方案。
分析特定套件的依賴
如果想深入了解某個套件為什麼這麼「重」:
# 查看 firefox 的完整依賴樹
nix-tree $(which firefox)
# 或者直接指定 store path
nix-tree /nix/store/xxx...-firefox-125.0
這在你 debug「為什麼我的 container image 這麼大」的時候特別有用。
磁碟空間管理策略
了解目前的空間使用狀況
先來看看 /nix/store 到底吃掉了多少空間:
# 查看 /nix/store 的總大小
du -sh /nix/store
# 查看可以被回收的空間(dead store paths)
nix store gc --dry-run 2>&1 | tail -1
手動清理 garbage
NixOS 的 garbage collection(GC)會清除所有沒有被任何 root(如 system profile、user profile)參照的 store path。最基本的清理指令:
# 清除所有 GC roots 未參照的 store path
sudo nix-collect-garbage
不過,這只會清掉「已經沒有任何 generation 參照」的東西。如果你有 50 個 generation,每個 generation 各自參照了不同版本的套件,那這些套件都不會被清掉。
所以,更實際的做法是先刪除舊的 generation,再執行 GC:
# 刪除 30 天以前的所有 generation,然後執行 GC
sudo nix-collect-garbage --delete-older-than 30d
你也可以指定保留最近幾個 generation:
# 只保留最近 5 個 generation
sudo nix-env --delete-generations +5
sudo nix-collect-garbage
清理 user profile
別忘了 user-level 的 profile 也會佔空間:
# 清理當前使用者的舊 generation
nix-collect-garbage --delete-older-than 14d
# 搭配 home-manager 使用時,也要清理 home-manager 的 generation
home-manager expire-generations "-14 days"
清理開發用的 Flake 與 devShell
如果你大量使用 nix develop 或 nix build,Nix 預設會把 build result 的 GC root 存放在專案目錄下的 result symlink。清除這些 symlink 後再執行 GC:
# 在專案目錄中刪除 result symlink
rm -f result
# 清理 flake registry cache
rm -rf ~/.cache/nix/
自動化垃圾回收設定
手動清理終究不是長久之計。NixOS 內建了自動化 GC 的機制,直接在 configuration.nix 裡設定即可:
# 自動化 garbage collection
nix.gc = {
automatic = true;
dates = "weekly"; # 每週執行一次
options = "--delete-older-than 30d"; # 刪除 30 天前的 generation
};
這會建立一個 systemd timer,定期自動執行 nix-collect-garbage --delete-older-than 30d。
搭配 nix-optimise-store
Nix store 中有很多重複的檔案(例如同一份 source file 出現在不同 derivation 裡)。nix store optimise 會透過 hard link 來 deduplicate 這些重複檔案,節省額外的空間:
# 定期最佳化 store(deduplicate)
nix.optimise = {
automatic = true;
dates = [ "weekly" ];
};
或者手動執行:
# 手動執行 store 最佳化
sudo nix store optimise
第一次跑可能會花比較久的時間,但後續執行會快很多。根據經驗,在一個使用了一段時間的系統上,這通常可以省下 20-40% 的空間。
完整的自動維護配置
把 GC 和 optimise 寫在一起,這是建議的標準配置:
{
# 自動垃圾回收
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
# 自動 deduplicate
nix.optimise = {
automatic = true;
dates = [ "weekly" ];
};
# 也可以設定 store 大小上限(Nix 2.19+)
# 當 store 超過指定大小時,GC 會自動清理最舊的 path
nix.settings.min-free = 1073741824; # 至少保留 1 GB 可用空間
nix.settings.max-free = 5368709120; # 可用空間達 5 GB 時停止清理
}
編譯速度最佳化
NixOS 的「什麼都從 Nix expression 建置」哲學很美好,但也意味著編譯時間可能會讓你崩潰。以下幾個技巧可以顯著加速你的日常體驗。
善用 Binary Cache
最有效的加速方式是根本不要自己編譯。Nix 預設會從 cache.nixos.org 下載預編譯好的 binary(稱為 substitution)。確保你的 nix.conf 沒有關掉這個功能:
nix.settings = {
# 預設就是開啟的,但明確寫出來確保不會漏掉
substituters = [
"https://cache.nixos.org"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
];
};
如果你使用了第三方的 Flake(例如 nix-community 的套件),可以加入它們的 binary cache:
nix.settings = {
substituters = [
"https://cache.nixos.org"
"https://nix-community.cachix.org"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
設定 Cachix 作為私有 Binary Cache
如果你的團隊經常 build 自己的套件,強烈建議使用 Cachix 來建立團隊專屬的 binary cache:
# 安裝 cachix
nix-env -iA cachix -f https://cachix.org/api/v1/install
# 將 build result 推到你的 cache
cachix push your-team-cache ./result
這樣團隊裡只需要一個人 build 過,其他人就可以直接從 cache 下載,省下大量編譯時間。
平行編譯
Nix 預設會使用所有 CPU core 來編譯,但你可以更精細地控制:
nix.settings = {
# 同時可以進行多少個 build job
max-jobs = "auto"; # "auto" = CPU core 數量
# 每個 build job 可以使用多少 core
cores = 0; # 0 = 不限制,使用所有可用 core
};
💡 小提醒:
max-jobs和cores是相乘的關係。如果你有 8 個 core,設定max-jobs = 4和cores = 2,代表最多同時跑 4 個 build,每個 build 最多用 2 個 core(共 8 core)。
使用 Remote Builder
如果你的主力機器效能有限(例如筆電),可以把編譯工作 offload 到另一台更強的機器上:
nix.buildMachines = [{
hostName = "build-server";
systems = [ "x86_64-linux" "aarch64-linux" ];
maxJobs = 16;
speedFactor = 2;
supportedFeatures = [ "nixos-test" "big-parallel" "kvm" ];
mandatoryFeatures = [];
}];
nix.distributedBuilds = true;
# 建議搭配 SSH key 認證
programs.ssh.extraConfig = ''
Host build-server
HostName 192.168.1.100
User builder
IdentityFile /root/.ssh/builder_key
'';
這樣當你在筆電上執行 nixos-rebuild switch 時,Nix 會自動把 build job 分配到 build-server 上執行,完成後再把 result 拉回來。筆電的風扇終於可以安靜了。
nix.conf 調校
除了上面提到的設定之外,以下是一些實用的 nix.conf(透過 nix.settings 設定)調校建議:
nix.settings = {
# 啟用 Flakes 和 nix command(如果你還沒開的話)
experimental-features = [ "nix-command" "flakes" ];
# 允許以 root 執行時自動信任的使用者
trusted-users = [ "root" "@wheel" ];
# HTTP 連線數量(加速從 cache 下載)
http-connections = 50;
# 啟用自動 store 最佳化(每次 build 後自動 deduplicate)
auto-optimise-store = true;
# 記錄 build log,方便事後 debug
log-lines = 25;
# 如果 substitution 失敗,fallback 到本地 build
fallback = true;
# 連線 timeout(秒),避免卡在很慢的 cache server
connect-timeout = 10;
};
auto-optimise-store vs nix.optimise
你可能注意到這裡有兩種 optimise 機制:
| 機制 | 說明 |
|---|---|
auto-optimise-store = true | 每次有新的 path 寫入 store 時,即時 deduplicate |
nix.optimise.automatic | 透過 systemd timer 定期 對整個 store 做全量 deduplicate |
建議兩個都開。auto-optimise-store 可以避免空間持續膨脹,nix.optimise 則可以在初次啟用時清理歷史累積的重複檔案。
完整的效能調校範例
{ config, pkgs, ... }:
{
nix = {
settings = {
experimental-features = [ "nix-command" "flakes" ];
trusted-users = [ "root" "@wheel" ];
auto-optimise-store = true;
max-jobs = "auto";
cores = 0;
http-connections = 50;
substituters = [
"https://cache.nixos.org"
"https://nix-community.cachix.org"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
connect-timeout = 10;
fallback = true;
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
optimise = {
automatic = true;
dates = [ "weekly" ];
};
};
}
系統健康檢查流程
最後,來建立一套日常可以執行的系統健康檢查流程。你可以把以下指令整理成一個 shell script,定期跑一次:
1. 檢查磁碟空間
# 整體磁碟使用量
df -h /nix/store
# Nix store 的大小
du -sh /nix/store 2>/dev/null
# 目前有多少 store path
nix path-info --all | wc -l
2. 檢查 generation 數量
# 系統 generation 列表
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system
# 使用者 generation
nix-env --list-generations
過多的 generation 是磁碟空間的最大元兇之一。一般建議保留最近 5–10 個 generation,或 30 天以內的 generation。
3. 確認 channel 或 Flake input 是否需要更新
# 如果你使用 channel
sudo nix-channel --list
sudo nix-channel --update
# 如果你使用 Flakes
nix flake update
# 查看 flake.lock 的最後更新日期
git log -1 --format="%ai" -- flake.lock
4. 確認沒有 broken symlink
# 檢查 /run/current-system 的完整性
nix store verify --all --no-trust 2>&1 | head -20
5. 檢查 systemd 服務狀態
# 列出所有失敗的 service
systemctl --failed
# 檢查最近的系統日誌
journalctl -p err --since "7 days ago" --no-pager | tail -30
整合成一個健檢 script
以下是一個簡單的系統健檢 script,你可以放進 configuration.nix 的 environment.systemPackages 中,或是放在自己的 dotfiles 裡:
#!/usr/bin/env bash
set -euo pipefail
echo "========================================="
echo " NixOS 系統健康檢查報告"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================="
echo ""
echo "📦 NixOS 版本"
nixos-version
echo ""
echo "💾 磁碟使用量"
df -h / /nix/store 2>/dev/null || df -h /
echo ""
echo "📊 Nix Store 統計"
echo " Store paths 數量: $(nix path-info --all 2>/dev/null | wc -l | tr -d ' ')"
echo " Store 大小: $(du -sh /nix/store 2>/dev/null | cut -f1)"
echo ""
echo "🔄 System Generation"
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system 2>/dev/null | tail -5
echo ""
echo "⚠️ 失敗的 Service"
systemctl --failed --no-pager --no-legend || echo " 全部正常!"
echo ""
echo "🗑️ 可回收空間(預估)"
nix store gc --dry-run 2>&1 | tail -1
echo ""
echo "========================================="
echo " 檢查完成"
echo "========================================="
把它存成 nixos-health-check.sh,以後只要一個指令就能快速掌握系統狀態。
小結
今天涵蓋了 NixOS 系統維護的核心面向:
| 主題 | 重點 |
|---|---|
| 依賴分析 | 用 nix-tree 和 nix path-info 找出空間大戶 |
| 磁碟空間管理 | nix-collect-garbage --delete-older-than 30d 清理舊 generation |
| 自動化維護 | 設定 nix.gc 與 nix.optimise 讓系統自動保養 |
| 編譯加速 | 善用 binary cache、平行編譯、remote builder |
| nix.conf 調校 | auto-optimise-store、http-connections、fallback 等關鍵參數 |
| 健康檢查 | 建立一套可重複執行的系統檢查流程 |
NixOS 的 immutable 設計帶來了穩定性和可重現性,但也需要你主動管理磁碟空間和編譯效率。好消息是,這些維護工作大多可以自動化——設定好 nix.gc 和 nix.optimise,系統就會自己照顧自己。
養成定期跑健檢 script 的習慣,你的 NixOS 就能一直保持在最佳狀態。
明日預告
Day 30:回顧與展望 — 你的 NixOS 之旅才剛開始
30 天的旅程來到了最後一天。明天我們會回顧這趟學習之旅的核心觀念、盤點你已經掌握的技能,並展望接下來可以深入探索的方向——無論是貢獻 Nixpkgs、打造公司內部的 Nix infrastructure,還是參與社群活動。
最後一天,我們明天見! 🚀
📚 延伸閱讀