Day 25:遠端佈署 — 在筆電上寫好配置,一鍵推送到你的 NixOS Server
介紹 Colmena 與 deploy-rs 兩大遠端佈署工具,實現從本機定義配置並透過 SSH 一鍵推送到 NixOS Server 的 Infrastructure as Code 流程。
Day 25:遠端佈署 — 在筆電上寫好配置,一鍵推送到你的 NixOS Server
🗓 系列:NixOS 30 天學習之旅
📦 階段:第四階段 — 工程師進階實務 (Day 22 – Day 30)
🎯 階段核心目標:佈署、自動化、安全性與貢獻
前言:從本機管理到遠端佈署
到目前為止,我們大部分的操作都是直接在 NixOS 機器上編輯 configuration.nix,然後執行 nixos-rebuild switch。這在單機環境下運作得很好,但當你開始管理遠端的 server — 不管是一台 VPS、一台 homelab 主機、還是一整個 fleet — 你就會開始想:
「有沒有辦法在我的筆電上寫好配置,一鍵推送更新到遠端的 NixOS server?」
答案是:當然有,而且 NixOS 生態系提供了非常成熟的工具來實現這件事。
今天我們要認識兩個主流的遠端佈署工具:Colmena 和 deploy-rs。它們各有特色,但核心理念一致 — 讓你在本機定義整個基礎設施的配置,然後透過 SSH 推送到目標機器上,實現真正的 Infrastructure as Code。
遠端佈署的需求與挑戰
在深入工具之前,先釐清一下:為什麼不能直接 SSH 進去跑 nixos-rebuild switch 就好?
技術上當然可以,但這樣做有幾個明顯的問題:
1. 手動操作無法規模化
管 1 台 server,SSH 進去改設定還 OK。管 5 台呢?10 台呢?每台都要手動登入、編輯、rebuild,光想就覺得累。
2. 配置散落各處
每台 server 上各自維護一份 configuration.nix,版本不一致、沒有 version control,出事了很難追溯。
3. Build 資源浪費
在每台遠端 server 上各自 build,消耗大量 CPU 和記憶體。尤其小規格的 VPS,build 一次 NixOS 可能要跑半小時以上。
4. 缺乏 rollback 保護
直接在 production server 上操作,萬一新配置有問題,你必須手動介入修復。
遠端佈署工具解決的就是這些痛點。它們讓你可以:
- 在本機(或 CI/CD pipeline)統一管理所有 server 的配置
- 在本機 build 完成後,只把 build 結果推送到遠端
- 提供 atomic switch 和自動 rollback 機制
- 支援多機器平行佈署
Colmena 介紹與設定
Colmena 是一個專門為 NixOS fleet management 設計的佈署工具。名字來自西班牙語的「蜂巢」,暗示著它適合管理一群機器。
安裝 Colmena
在你的本機(佈署發起端)安裝 Colmena:
# 在 flake.nix 的 devShell 中加入
devShells.default = pkgs.mkShell {
packages = [ pkgs.colmena ];
};
或者直接用 nix run 臨時執行:
nix run nixpkgs#colmena -- --help
專案結構
Colmena 使用 hive.nix(或 flake.nix)作為進入點。以下是一個典型的專案結構:
my-infra/
├── flake.nix
├── flake.lock
└── hosts/
├── web-server.nix
├── db-server.nix
└── common.nix
使用 hive.nix 的配置方式
最簡單的入門方式是建立一個 hive.nix:
# hive.nix
{
meta = {
nixpkgs = import <nixpkgs> {
system = "x86_64-linux";
};
};
defaults = { pkgs, ... }: {
# 所有機器共用的配置
environment.systemPackages = with pkgs; [
vim
htop
git
];
# 啟用 SSH 以便遠端管理
services.openssh.enable = true;
# 自動 garbage collection
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
};
# ===== 定義你的 server =====
web-server = { name, nodes, pkgs, ... }: {
deployment = {
targetHost = "203.0.113.10";
targetUser = "root";
};
# Web server 專屬配置
services.nginx = {
enable = true;
virtualHosts."mysite.example.com" = {
root = "/var/www/mysite";
forceSSL = true;
enableACME = true;
};
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
};
db-server = { name, nodes, pkgs, ... }: {
deployment = {
targetHost = "203.0.113.11";
targetUser = "root";
};
# Database server 專屬配置
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
authentication = ''
host all all 203.0.113.10/32 md5
'';
};
networking.firewall.allowedTCPPorts = [ 5432 ];
};
}
使用 Flake 的配置方式
如果你的專案已經在使用 flake(在 Day 24 之後應該很熟悉了),Colmena 也能完美整合:
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
colmena.url = "github:zhaofengli/colmena";
};
outputs = { self, nixpkgs, colmena, ... }: {
colmena = {
meta = {
nixpkgs = import nixpkgs {
system = "x86_64-linux";
};
specialArgs = {
inherit self;
};
};
defaults = { pkgs, ... }: {
environment.systemPackages = with pkgs; [
vim
htop
];
};
web-server = { pkgs, ... }: {
deployment = {
targetHost = "203.0.113.10";
targetUser = "root";
};
imports = [ ./hosts/web-server.nix ];
};
};
};
}
Colmena 常用指令
# 顯示所有 node 的資訊
colmena eval -E '{ nodes, ... }: builtins.attrNames nodes'
# 佈署所有機器
colmena apply
# 只佈署特定機器
colmena apply --on web-server
# 先 build 但不佈署(dry run)
colmena build
# 使用 boot 模式(下次重開機才生效)
colmena apply --on db-server boot
# 平行佈署,限制同時佈署的機器數量
colmena apply --parallel 3
Colmena 的特色
Colmena 有幾個值得一提的設計亮點:
- Node 之間可以互相引用:透過
nodes參數,一台 server 的配置可以引用其他 server 的資訊(例如 IP 位址),這在設定 firewall rule 或 service discovery 時非常實用。 - Local build 與 remote build 可切換:預設在本機 build,但也可以指定在目標機器上 build,或是使用 remote builder。
- 內建 deployment tag:可以為機器打上 tag,方便批次操作特定群組。
# Node 之間互相引用的範例
db-server = { name, nodes, pkgs, ... }: {
deployment.targetHost = "203.0.113.11";
services.postgresql.authentication = ''
# 自動引用 web-server 的 target host
host all all ${nodes.web-server.config.deployment.targetHost}/32 md5
'';
};
deploy-rs 介紹與設定
deploy-rs 是由 Serokell 開發的佈署工具,設計理念更加聚焦在安全性與 atomic deployment 上。它最出名的特色就是內建的自動 rollback 機制。
安裝 deploy-rs
# 在 flake.nix 的 devShell 中加入
{
inputs = {
deploy-rs.url = "github:serokell/deploy-rs";
};
outputs = { self, nixpkgs, deploy-rs, ... }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.${system}.default = pkgs.mkShell {
packages = [ deploy-rs.packages.${system}.default ];
};
};
}
deploy-rs 的配置方式
deploy-rs 完全基於 flake,配置寫在 flake.nix 的 deploy output 中:
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
deploy-rs.url = "github:serokell/deploy-rs";
};
outputs = { self, nixpkgs, deploy-rs, ... }: {
# 先定義 NixOS configuration
nixosConfigurations = {
web-server = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/web-server.nix
{
networking.hostName = "web-server";
services.openssh.enable = true;
}
];
};
db-server = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/db-server.nix
{
networking.hostName = "db-server";
services.postgresql = {
enable = true;
package = nixpkgs.legacyPackages.x86_64-linux.postgresql_16;
};
}
];
};
};
# deploy-rs 的佈署設定
deploy = {
sshUser = "root";
nodes = {
web-server = {
hostname = "203.0.113.10";
profiles.system = {
user = "root";
path = deploy-rs.lib.x86_64-linux.activate.nixos
self.nixosConfigurations.web-server;
};
};
db-server = {
hostname = "203.0.113.11";
profiles.system = {
user = "root";
path = deploy-rs.lib.x86_64-linux.activate.nixos
self.nixosConfigurations.db-server;
};
};
};
};
# 佈署前的檢查(強烈建議加上)
checks = builtins.mapAttrs
(system: deployLib: deployLib.deployChecks self.deploy)
deploy-rs.lib;
};
}
deploy-rs 常用指令
# 佈署所有 node
deploy .
# 佈署特定 node
deploy .#web-server
# 佈署特定 node 的特定 profile
deploy .#web-server.system
# dry run(只 build,不實際佈署)
deploy . --dry-activate
# 跳過自動 rollback 確認(謹慎使用)
deploy . --auto-rollback false
# 顯示詳細 log
deploy . -- --show-trace
deploy-rs 的自動 rollback 機制
這是 deploy-rs 最強大的安全功能。它的運作邏輯如下:
- deploy-rs 將新的 system profile 推送到遠端 server
- 執行 activation(相當於
nixos-rebuild switch) - 在 activation 後,deploy-rs 會等待一個 magic rollback timer
- 如果在時間內本機能成功連線回遠端 server,就確認佈署成功
- 如果連不回去(例如你不小心把 SSH 或網路設定改壞了),server 會自動 rollback 到上一個 profile
# 可以在 node 層級自訂 rollback 行為
web-server = {
hostname = "203.0.113.10";
# 確認佈署成功的等待時間(秒)
magicRollback = true;
# 如果 240 秒內沒收到確認,就 rollback
confirmTimeout = 240;
profiles.system = {
user = "root";
path = deploy-rs.lib.x86_64-linux.activate.nixos
self.nixosConfigurations.web-server;
};
};
這個機制在管理遠端 server 時特別有價值。想像一下:你不小心推了一個把 SSH port 改錯的配置,如果沒有自動 rollback,你可能就要跑去機房或開 VNC console 手動修復了。有了 magic rollback,最多等幾分鐘,server 就會自己恢復到能正常連線的狀態。
Colmena vs deploy-rs 比較
兩個工具各有所長,以下是一個快速比較:
| 特性 | Colmena | deploy-rs |
|---|---|---|
| 配置格式 | hive.nix 或 flake.nix | 僅 flake.nix |
| 學習曲線 | 較平緩,語法直觀 | 稍陡,需要理解 profile 概念 |
| 自動 rollback | ❌ 無內建 | ✅ magic rollback |
| Node 互相引用 | ✅ 透過 nodes 參數 | ❌ 需自行處理 |
| 非 NixOS 目標 | ❌ 僅限 NixOS | ✅ 支援(透過 custom profile) |
| 平行佈署 | ✅ 內建 --parallel | ✅ 預設平行 |
| CI/CD 整合 | 良好 | 良好 |
| 社群活躍度 | 活躍,持續維護 | 活躍,Serokell 支持 |
| Tag/群組管理 | ✅ 內建 tag 系統 | ❌ 需自行分組 |
怎麼選?
- 如果你重視安全性,尤其是管理 production server,deploy-rs 的自動 rollback 是殺手級功能。
- 如果你管理大量機器,需要 node 之間互相引用或群組管理,Colmena 的 fleet management 功能更完善。
- 如果你還沒使用 flake,Colmena 的
hive.nix模式讓你不需要先遷移到 flake 就能開始用。 - 如果你已經在用 flake,兩者都能很好地整合,挑你喜歡的就好。
實戰:一鍵佈署到遠端 Server
讓我們用 Colmena 來做一個完整的實戰範例。假設你有一台 VPS,IP 是 203.0.113.10,已經裝好了 NixOS。
Step 1:準備 SSH 連線
確保你的筆電能用 SSH key 免密碼登入遠端 server:
# 如果還沒有 SSH key,先產生一把
ssh-keygen -t ed25519
# 把 public key 複製到遠端 server
ssh-copy-id root@203.0.113.10
# 確認可以免密碼登入
ssh root@203.0.113.10 "echo 'SSH connection OK'"
Step 2:建立專案
mkdir my-infra && cd my-infra
nix flake init
Step 3:撰寫 hive.nix
# hive.nix
{
meta = {
nixpkgs = import <nixpkgs> {
system = "x86_64-linux";
};
};
defaults = { pkgs, ... }: {
environment.systemPackages = with pkgs; [
vim
htop
curl
tmux
];
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "prohibit-password";
PasswordAuthentication = false;
};
};
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 14d";
};
};
my-vps = { pkgs, ... }: {
deployment = {
targetHost = "203.0.113.10";
targetUser = "root";
};
# 這台 server 的 hardware configuration
# 實際使用時,從遠端 server 複製過來
imports = [ ./hosts/my-vps/hardware-configuration.nix ];
boot.loader.grub = {
enable = true;
device = "/dev/vda";
};
networking = {
hostName = "my-vps";
firewall.allowedTCPPorts = [ 80 443 ];
};
# 跑一個簡單的 Nginx
services.nginx = {
enable = true;
virtualHosts.default = {
default = true;
root = pkgs.writeTextDir "index.html" ''
<!DOCTYPE html>
<html>
<body>
<h1>Hello from NixOS!</h1>
<p>Deployed with Colmena 🐝</p>
</body>
</html>
'';
};
};
system.stateVersion = "24.11";
};
}
Step 4:佈署!
# 先 build 看看有沒有語法錯誤
colmena build
# 確認沒問題後,正式佈署
colmena apply
# 你會看到類似這樣的輸出:
# [INFO ] Connected to my-vps (203.0.113.10)
# [INFO ] Building system profile for my-vps...
# [INFO ] Copying closure to my-vps...
# [INFO ] Activating profile on my-vps...
# [INFO ] Activation successful on my-vps
Step 5:驗證
# 確認 Nginx 有正確啟動
curl http://203.0.113.10
# 應該看到 "Hello from NixOS!"
就這麼簡單!以後每次想更新 server 配置,只要修改 hive.nix,然後再跑一次 colmena apply,就完成了。
多機器佈署策略
當你開始管理多台機器時,有些策略和模式值得注意:
1. 分層配置架構
my-infra/
├── hive.nix # 入口
├── modules/
│ ├── base.nix # 所有機器共用的基礎配置
│ ├── hardening.nix # 安全強化
│ ├── monitoring.nix # 監控(Prometheus、Grafana 等)
│ └── backup.nix # 備份策略
├── hosts/
│ ├── web-1/
│ │ ├── default.nix # web-1 的專屬配置
│ │ └── hardware-configuration.nix
│ ├── web-2/
│ │ ├── default.nix
│ │ └── hardware-configuration.nix
│ └── db-1/
│ ├── default.nix
│ └── hardware-configuration.nix
└── secrets/
└── ... # 使用 agenix 或 sops-nix 管理
2. 使用 Tag 分群佈署(Colmena)
# hive.nix
web-1 = { ... }: {
deployment = {
targetHost = "203.0.113.10";
tags = [ "web" "production" ];
};
imports = [ ./hosts/web-1 ];
};
web-2 = { ... }: {
deployment = {
targetHost = "203.0.113.11";
tags = [ "web" "production" ];
};
imports = [ ./hosts/web-2 ];
};
db-1 = { ... }: {
deployment = {
targetHost = "203.0.113.20";
tags = [ "database" "production" ];
};
imports = [ ./hosts/db-1 ];
};
# 只更新所有 web server
colmena apply --on @web
# 只更新 production 環境
colmena apply --on @production
# 排除 database server
colmena apply --on '!@database'
3. 漸進式佈署(Rolling Deployment)
在 production 環境中,一次更新所有機器是有風險的。比較安全的做法:
# 先佈署到一台 canary server
colmena apply --on web-1
# 確認服務正常後,再佈署其餘機器
colmena apply --on web-2
# 或者用 --parallel 限制同時佈署的數量
colmena apply --on @web --parallel 1
4. 搭配 CI/CD Pipeline
將佈署整合進 CI/CD 是最佳實踐。以 GitHub Actions 為例:
# .github/workflows/deploy.yml
name: Deploy NixOS
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-24.11
- name: Setup SSH Key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan 203.0.113.10 >> ~/.ssh/known_hosts
- name: Deploy with Colmena
run: |
nix run nixpkgs#colmena -- apply
這樣每次你 push 到 main branch,就會自動觸發佈署。搭配 PR review 流程,可以確保每次佈署都經過 code review。
小結
今天我們學會了如何把 NixOS 的管理範圍從單機擴展到遠端 server:
| 主題 | 重點 |
|---|---|
| 遠端佈署的價值 | 統一管理、本機 build、自動化推送、安全 rollback |
| Colmena | 直觀的 fleet management,支援 hive.nix 與 flake,內建 tag 系統與 node 互相引用 |
| deploy-rs | 專注安全性,magic rollback 防止配置失誤導致 server 失聯 |
| 實戰流程 | 寫好配置 → colmena build → colmena apply → 驗證 |
| 多機器策略 | 分層配置、tag 分群、漸進式佈署、CI/CD 整合 |
這就是 NixOS 在 Infrastructure as Code 領域的獨特優勢:你的整個 server 配置都是 Nix 程式碼,可以 version control、code review、自動化佈署,而且每次佈署都是 reproducible 的。
明日預告
Day 26:Secret 管理 — agenix 與 sops-nix
佈署到遠端 server 時,一個繞不開的問題是:密碼、API key、TLS 憑證這些 secret 怎麼管理?直接寫在 configuration.nix 裡然後 commit 到 Git?千萬不要。明天我們會介紹 NixOS 生態系中兩個主流的 secret management 工具,讓你安全地管理敏感資訊。
我們明天見! 🚀
📚 延伸閱讀