Loading...

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 生態系提供了非常成熟的工具來實現這件事。

今天我們要認識兩個主流的遠端佈署工具:Colmenadeploy-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.nixdeploy 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 最強大的安全功能。它的運作邏輯如下:

  1. deploy-rs 將新的 system profile 推送到遠端 server
  2. 執行 activation(相當於 nixos-rebuild switch
  3. 在 activation 後,deploy-rs 會等待一個 magic rollback timer
  4. 如果在時間內本機能成功連線回遠端 server,就確認佈署成功
  5. 如果連不回去(例如你不小心把 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 比較

兩個工具各有所長,以下是一個快速比較:

特性Colmenadeploy-rs
配置格式hive.nixflake.nixflake.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 buildcolmena 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 工具,讓你安全地管理敏感資訊。

我們明天見! 🚀


📚 延伸閱讀