nixos-anywhere + disko で VPS に NixOS を宣言的にセットアップした

tech March 08, 2026 siraken 14 min read
nixos-anywhere + disko で VPS に NixOS を宣言的にセットアップした

どうも白澤です。以前の記事で Kagoya VPS に NixOS を手動インストールした話を書きましたが、あのときの最後に「nixos-anywhere とか disko も試してみたい」と書いていました。今回はその続きです。

手動セットアップだとディスクのパーティション切りやフォーマットが毎回手作業で、再構築のたびに同じことをしないといけません。せっかく NixOS 使ってるんだからディスク構成も宣言的にやりたい。

ということで、nixos-anywheredisko を導入してみました。まず別の VPS(fuga)でテストして、正常に動くことを確認できたのでその記録です。(記事内に記載されているホスト名や IP アドレス、ポート番号などはすべて仮のものです)

ちなみに Nix の設定ファイル群は、ほぼ Claude Code(Opus 4.6)に書いてもらってます。disko のディスク定義やホストの設定ファイル構成など、「こういう環境でこういう構成にしたい」と伝えると、既存の Flake 構成を読み取った上でいい感じに書いてくれます。Nix の設定は手で書くと地味に時間がかかるので、かなり助かりました。今回は特に、すでに出来上がっていた fuga ホストの設定をもとに指示ができたのでよりスムーズに書いてもらえた感がありますね。

nixos-anywhere とは

ざっくりいうと、nixos-anywhere はリモートのマシンに NixOS をインストールするためのツールです。ローカルで NixOS のシステムクロージャをビルドして、それをリモートマシンに転送してインストールしてくれます。

VPS 側で用意している OS イメージはほとんどの場合 NixOS がありません。代わりに Ubuntu はほぼ確実にあると思うので、まずは Ubuntu などの Linux ディストリビューションで起動して、そこから nixos-anywhere を使って NixOS に切り替える、という流れになります。

「切り替える」 というのは、nixos-anywhere はリモートマシンのディスクを丸ごと上書きして NixOS をインストールするので、既存の OS は完全に消えて NixOS に置き換わるということです。そう、つまり既存 OS のデータは消えます。新規で立ち上げたインスタンスに入っている Ubuntu は SSH してインスタンスに接続するため「だけ」に使います、ちょっと儚いですね、、、

前提環境

今回使った VPS の環境はこんな感じです。

  • VPS: Kagoya VPS(QEMU/KVM)
  • アーキテクチャ: x86_64-linux
  • ブート: BIOS/Legacy(UEFI ではない)
  • ディスク: /dev/vda、GPT パーティションテーブル
  • 既存の構成管理: Nix Flakes

ブートが BIOS/Legacy なのは前回と同じです。

既存のディスク構成を確認する

まず、既存サーバー(hoge)のディスク構成を確認するところから。

sudo fdisk -l /dev/vda
lsblk -f
findmnt -t ext4,vfat,xfs,btrfs
ls /sys/firmware/efi 2>/dev/null && echo "UEFI" || echo "BIOS/Legacy"

ここで確認すべきなのは、パーティションテーブルが GPT であること、BIOS/Legacy ブートであること、あとネットワーク設定(IP、ゲートウェイ)ですね。

サーバーパネルからインスタンスを作成すると画面にネットワークの情報が出てきますが、この画面のスクショを Claude Code に貼っつけるだけで必要な情報を読み取ってくれました。便利。

flake.nix に disko を追加

まず flake.nix の inputs に disko を追加します。

# flake.nix
inputs = {
  # ...既存の inputs...

  disko = {
    url = "github:nix-community/disko";
    inputs.nixpkgs.follows = "nixpkgs";
  };
};

outputs = { self, nixpkgs, disko, ... }@inputs: {
  nixosConfigurations = {
    fuga = import ./hosts/fuga { inherit inputs; };
  };
};

inputs.nixpkgs.follows = "nixpkgs" で nixpkgs を共有するのはいつも通りですね。

disko のディスク定義を作成

ここが disko の肝です。GPT + BIOS boot の構成を Nix で宣言的に定義します。

# hosts/fuga/disk-config.nix
{
  disko.devices = {
    disk = {
      vda = {
        type = "disk";
        device = "/dev/vda";
        content = {
          type = "gpt";
          partitions = {
            boot = {
              size = "1M";
              type = "EF02"; # BIOS boot partition
            };
            root = {
              size = "100%";
              content = {
                type = "filesystem";
                format = "ext4";
                mountpoint = "/";
              };
            };
          };
        };
      };
    };
  };
}

BIOS boot の場合、type = "EF02" で 1M のブートパーティションを切るのがポイントです。

結構シンプルに書けますよね。これだけでディスクのパーティション構成が宣言的に管理できるのは嬉しい。

ホストの設定ファイル構成

ホストのディレクトリ構成はこんな感じにしました。Nix のディレクトリ構成はまだ試行錯誤中なので多分今後また変わるんでしょうけど。

hosts/fuga/
├── default.nix                 # モジュール構成
├── disk-config.nix             # disko ディスク定義
├── hardware-configuration.nix  # カーネルモジュール等
└── configuration.nix           # ホスト固有設定

default.nix

disko モジュールと disk-config.nix を読み込みます。

# hosts/fuga/default.nix
{ inputs, ... }:
let
  inherit (inputs) nixpkgs disko nix-index-database home-manager;
in
nixpkgs.lib.nixosSystem {
  system = "x86_64-linux";
  specialArgs = { inherit inputs; };
  modules = [
    ./configuration.nix
    ./disk-config.nix
    disko.nixosModules.disko
    # ...他のモジュール...
  ];
}

hardware-configuration.nix

disko が fileSystems を管理してくれるので、ここでは書きません。これ地味に大事なところで、書いてしまうと定義が競合します。

# hosts/fuga/hardware-configuration.nix
{ config, lib, pkgs, modulesPath, ... }:

{
  imports = [
    (modulesPath + "/profiles/qemu-guest.nix")
  ];

  boot.initrd.availableKernelModules = [
    "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk"
  ];
  boot.initrd.kernelModules = [ ];
  boot.kernelModules = [ "kvm-intel" ];
  boot.extraModulePackages = [ ];

  # fileSystems are managed by disko (disk-config.nix)

  swapDevices = [ ];

  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

configuration.nix

GRUB の設定は disko が自動的にやってくれるので grub.device の指定は不要です。

# hosts/fuga/configuration.nix(一部抜粋)
{ inputs, config, pkgs, ... }:
{
  boot.loader.systemd-boot.enable = false;

  networking = {
    hostName = "fuga";
    interfaces.ens3.ipv4.addresses = [{
      address = "xxx.xxx.xxx.xxx";
      prefixLength = 23;
    }];
    defaultGateway = "xxx.xxx.xxx.1";
    nameservers = [ "8.8.8.8" ];
    firewall.allowedTCPPorts = [ 99922 80 443 ];
  };

  services.openssh.ports = [ 99922 ];

  system.stateVersion = "26.05";
}

既存サーバーに disko を後付けする場合の注意

ここはちょっとハマりポイントです。手動セットアップ済みの既存サーバー(hoge)に disko を追加する場合、fileSystems の定義が競合します。

具体的には、手動セットアップだと nixos-generate-config が UUID でデバイスを参照する fileSystems を生成するのに対して、disko はパーティションラベル(disk-vda-root)でデバイスを参照する fileSystems を自動生成します。

で、既存のパーティションには disko の命名規則(disk-vda-root)のラベルが付いていないので、disko の fileSystems をそのまま使うとマウントできなくて起動不能になります。怖いですね。

対処法としては lib.mkForce で UUID 参照を優先させます。

# hosts/hoge/hardware-configuration.nix(既存サーバー用)
fileSystems."/" = {
  device = lib.mkForce "/dev/disk/by-uuid/eb1e77bf-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
  fsType = lib.mkForce "ext4";
};

最初から nixos-anywhere + disko でセットアップすればこの問題は起きないので、新規サーバーなら気にする必要はないです。

nixos-anywhere を実行する

VPS の準備

テスト用に Kagoya VPS で Ubuntu 22.04 LTS のインスタンスを用意しました。スペックは 3 コア / 3GB RAM。RAM は 2GB 以上推奨です(インストール中にメモリ上でビルドが走るので)。

あと、SSH で root ログインできるようにしておく必要があります。Kagoya VPS の Ubuntu 22.04 LTS ではデフォルトで PermitRootLoginprohibit-password なので、VPS のコンソールから一時的に許可しました。

sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
systemctl restart sshd

どうせ nixos-anywhere でディスクごと上書きされるので、一時的に開けても問題ないです。どうせまっさらに消されるので豪快にいきましょ。

実行

これをローカルで実行します。ローカルでの処理が進んだら、途中で SSH 接続が入るので必要に応じてパスワード等で認証します。SSH ポートはデフォルトの 22 で実行します。初期の Ubuntu が 22 で待ち受けているので。

nix run github:nix-community/nixos-anywhere -- \
  --flake .#fuga \
  root@xxx.xxx.xxx.xxx

インストール完了後は configuration.nix で指定したポート(99922)に切り替わります。

実行中の流れ

nixos-anywhere は結構色々なことを自動でやってくれます。ざっくりこんな感じ。

  1. kexec イメージのダウンロード — NixOS インストーラーの kexec イメージ(約 350MB)を VPS にダウンロード
  2. kexec で NixOS インストーラーを起動machine will boot into nixos in 6s... と表示されて既存 OS から NixOS インストーラーに切り替わる。この時点で SSH 接続が一度切れる
  3. disko でディスクフォーマットwipefs でディスクをクリーンにして sgdisk で GPT パーティション作成、mkfs.ext4 でフォーマット
  4. NixOS システムクロージャのビルド — ローカル(macOS)で設定を評価して、必要なパッケージをリモートに転送
  5. NixOS のインストールとリブート

途中で以下のようなエラーが出ますが、これは macOS(aarch64-darwin)上で x86_64-linux のビルドを試みたことによるもので、途中で処理が止まるわけではないので多分無視して大丈夫です。

error: Cannot build '/nix/store/...drv'.
       Required system: 'x86_64-linux' with features {}
       Current system: 'aarch64-darwin' with features {apple-virt, ...}

最後に ### Done! ### と表示されて、続けて ssh: connect to host ... port 22: Network is unreachable が出ますが、これはリブート後の確認がポート 22 で試みられているだけ。SSH ポートを 99922 に設定しているので正常な動作です。最初見たときはちょい焦りました。

動作確認

インストール完了後、設定したポートで SSH 接続してみます。

ssh -p 99922 siraken@xxx.xxx.xxx.xxx
[siraken@fuga:~]$ hostname
fuga

[siraken@fuga:~]$ nixos-version
26.05.20260202.cb369ef (Yarara)

[siraken@fuga:~]$ lsblk -o NAME,PARTLABEL,FSTYPE,MOUNTPOINT /dev/vda
NAME   PARTLABEL     FSTYPE MOUNTPOINT
vda
├─vda1 disk-vda-boot
└─vda2 disk-vda-root ext4   /

ホスト名が fuga になっていて、NixOS が動いていて、パーティションラベルが disk-vda-boot / disk-vda-root(disko で作成されたもの)になっています。完璧ですね。

まとめ

ということで、nixos-anywhere + disko で VPS に NixOS を宣言的にセットアップしていきました。

ディスク構成まで Nix で管理できるのは結構気持ちが良いです。「このサーバー壊れたから同じ構成でもう一台立てたい」みたいなときに、nix run github:nix-community/nixos-anywhere -- --flake .#fuga root@新しいIP で終わるのは最高ですね。何より前回のような苦労をしなくて良い。

今回は使い捨て前提で作成したインスタンスのテスト用の VPS(fuga)で試しましたが、既存のメインサーバー(hoge)や今後追加するインスタンスでも活用していきたいと思っています。既存サーバーへの後付けは lib.mkForce での対処が必要だったりとちょっと面倒ではあるので、タイミングを見て再セットアップするのが良いかなと思います。

NixOS、触れば触るほど「全部宣言的に管理できる」ところが嬉しくて楽しいです。あと今回改めて思ったのは、Opus 4.6 の Nix 力がかなり高くなってきているということ。disko の設定も既存サーバーへの後付け対応も、ほぼ Claude Code に任せて進められました。Nix は情報が少なめな分野なので、ここまで的確にコードを書いてくれるのはありがたいですね。