DarwinホストでNixでDocker imageをビルド、するんだぜ
小ネタ
皆さんご存知Dockerfileを用意せずともNixでDocker imageをビルドすることができる。
{
pkgs,
tag ? "latest",
app ? pkgs.hello,
}:
pkgs.dockerTools.buildImage {
name = "app";
inherit tag;
created = "now";
copyToRoot = pkgs.buildEnv {
name = "app-root";
extraPrefix = "/usr/local";
paths = [ app ];
};
config = {
Env = [
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
];
Entrypoint = [
"${pkgs.dumb-init}/bin/dumb-init"
"--"
];
ExposedPorts = {
"8080/tcp" = { };
};
};
compressor = "zstd";
}
{
# ...
outputs = { nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
# ...
image = pkgs.callPackage ./nix/image.nix { };
# ...
in
{
packages = { inherit image; };
}
);
}
そしてimageをビルドし、dockerでロードして実行する:
$ nix build .#image --no-link -o image.tar.zst
$ docker image load -i image.tar.zst
$ docker container run --rm app:latest hello
hello
すんなり動くだろう…… あなたのホストOSがLinuxならば 。
まず大前提として、Dockerコンテナの実体はLinuxである。 macOSホストでDockerを実行する場合、LinuxのVMがDocker Desktop等により立ち上げられており、そのLinuxカーネルが各コンテナに共有されて動作する。
ところで、nix build .#image の指す .#image は .#packages.${builtins.currentSystem}.image のエイリアスであり、現在のアーキテクチャのパッケージをビルドする。
つまりホストOSがx86_64-linuxならばx86_64向けのELFから成るバイナリの詰まったDockerイメージをビルドしてくれるが、*-darwinなら Mach-O形式のバイナリから成るDockerイメージ がビルドされる。
これをLinuxが動いているコンテナ上で実行すると当然executable format errorになる。
ではどうするか? いくつかやり方はあるが、今回は2つ紹介する。
一番カンタンな方法として、対象をDocker上でビルドする。 Dockerに完全に非依存でOCIイメージをビルドしたいというモチベーションならば本末転倒だが、アプリケーションのDockerfileをNixで管理・記述したいだけならば、メタなDockerfileは許容されるだろう。 NixOSベースでもよかったが、experimental featuresを有効化しないといけなかったりnixos/nixがキャッシュされてなくて引っ張るのに時間かかったりするので、一旦DebianベースにしてDeterminateSystem/nix-installerを利用する。
# syntax=docker/dockerfile:1
FROM debian:stable-slim AS builder
RUN apt-get update && apt-get install -y git curl
RUN curl -fsSL https://install.determinate.systems/nix \
| sh -s -- install linux --init none --no-confirm \
--extra-conf \
"cores = 0" \
"filter-syscalls = false" # Apple Siliconにて必要
ENV PATH="${PATH}:/nix/var/nix/profiles/default/bin"
WORKDIR /app
RUN --mount=type=bind,source=.,rw \
nix -L build '.#image' --show-trace -o /image.tar.zst
FROM scratch AS out
COPY --from=builder /image.tar.zst /
buildxでlinux/${ホストのCPU arch}を指定し、output optionを利用することでimage.tar.zstが生成されるので、あとは上記同様にdocker image load -i image.tar.zstすれば無事helloを実行できるコンテナが展開される。
$ docker buildx build --platform linux/arm64 -t app:latest . -o .
この方法の注意点は、当然ながらDocker上でビルドされるため、Nixのキャッシュがホストに保存されない。
これの解決のため、もう一つのdarwin.linux-builderを使う手法を紹介する。
簡単に申し上げると、(Linuxベースである)NixOSのVMをQEMUから起動し、そこにホストからコピーしたソースをderivationとしてビルドする。 つまり分散コンパイルですね。
まずユーザをtrusted-userとして登録し、${ホストのCPU arch}-linux用のビルダーとして追加する。
darwin.linux-builderはSSHで接続するが、SSH鍵はdarwin.linux-builderが生成してくれるのでSSHの設定だけ追加する。
$ echo "extra-trusted-users = $USER" >> /etc/nix/nix.conf
$ echo 'builders = ssh-ng://builder@linux-builder' \
'aarch64-linux /etc/nix/builder_ed25519 4' \
'- - - c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSUpCV2N4Yi9CbGFxdDFhdU90RStGOFFVV3JVb3RpQzVxQkorVXVFV2RWQ2Igcm9vdEBuaXhvcwo='
$ cat <<EOL >> /etc/ssh/ssh_config.d/100-linux-builder.conf
Host linux-builder
Hostname localhost
HostKeyAlias linux-builder
Port 31022
User builder
IdentityFile /etc/nix/builder_ed25519
EOL
$ sudo launchctl kickstart -k system/org.nixos.nix-daemon
そしてイメージのビルド前にlinux-builderを起動しておく。
$ nix run nixpkgs#darwin.linux-builder
Creating Nix store image...
Created Nix store image.
[ 0.082206] armv8-pmu pmu: hw perfevents: failed to probe PMU!
<<< NixOS Stage 1 >>>
<<< Welcome to NixOS 26.05pre-git (aarch64) - ttyAMA0 >>>
Run 'nixos-help' for the NixOS manual.
nixos login: builder (automatic login)
[builder@nixos:~]$ uname -a
Linux nixos 6.12.64 #1-NixOS SMP Thu Jan 8 09:15:06 UTC 2026 aarch64 GNU/Linux
Ctrl-Dを連打してもVMは止まらんのでshutdown nowで終了する。
VMが立ち上がったところで、ホスト側でおもむろに
$ nix build .#packages.aarch64-linux.image --no-link -o image.tar.zst
を叩くとビルドに成功する。-Lオプションなどつけてログを観察すると、linux-builderにcopyして生成物をpullしているのが見える。
この手法の良い点は、Nixキャッシュがホスト側に保存されるところにある。
ソースからderivationのハッシュが得られ、 どうやって生成されるかにかかわらず ホスト側にderivationが得られれば/nix/storeに置かれるので、linux-builder上でビルドしてもOKなんやね。
なのでlinux-builder VMを再起動したり変更をほどこしても、ちゃんと差分だけをビルドしてくれる。
SSHでビルダーと通信できるちゅうことはリモートにビルドサーバを用意してもええわけですし、他にもDeterminate Nixのnative Linux builderを利用してもよい。 とまあやり方は様々だが簡単な順に12つ紹介した。
-
これって私の感想ですよ ↩

投稿されたコメントはCC BY 4.0ライセンスの下で公開されます。