Skip to content

build: use nix for building release images#3171

Closed
DSharifi wants to merge 9 commits into
mainfrom
dsharifi/nix-builds
Closed

build: use nix for building release images#3171
DSharifi wants to merge 9 commits into
mainfrom
dsharifi/nix-builds

Conversation

@DSharifi

@DSharifi DSharifi commented May 7, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@DSharifi

DSharifi commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented May 8, 2026

Copy link
Copy Markdown

Pull request overview

Replaces the legacy repro-env/podman-based reproducible build pipeline with a Nix-driven flow. The OCI image tarballs (and their manifest digests) are now produced by dockerTools.buildLayeredImage derivations, and the CI workflows push via skopeo copy --preserve-digests directly from the docker-archive output. The shared Crane build configuration has been pulled out of nix/mpc-node.nix into a reusable nix/rust-build-common.nix, and a new tee-launcher derivation is exposed alongside mpc-node.

Changes:

  • New flake outputs: mpc-node, tee-launcher, node-image, node-gcp-image, rust-launcher-image, plus matching *-manifest-digest derivations (Linux only)
  • Extract rust-build-common.nix builder and refactor mpc-node.nix / new tee-launcher.nix to call into it
  • Replace deployment/Dockerfile-* and deployment/build-images.sh with Nix dockerTools.buildLayeredImage derivations and a sandbox-skopeo digest helper
  • Rewrite all four GitHub workflows to install Nix, build the OCI archive, compute digests with the helper, and push via skopeo
  • Update README/docs/TDX guide to the new nix build .#… invocations
  • Bump the docker-build runners to warp-ubuntu-2404-x64-32x

Reviewed changes

Per-file summary
File Description
.github/workflows/ci.yml Replace repro-env install + build-images.sh with nix-installer-action + nix build .#node-image / .#rust-launcher-image and docker load; reuse the new restore-nix-cache composite action
.github/workflows/docker_build_node.yml Switch to nix-built archive; push via skopeo copy --preserve-digests; bump runner to 32x
.github/workflows/docker_build_node_gcp.yml Same as node, for the GCP variant
.github/workflows/docker_build_rust_launcher.yml Same flow for the rust launcher image; bump runner to 32x
README.md, docs/reproducible-builds.md, docs/localnet/docker-localnet.md, docs/running-an-mpc-node-in-tdx-external-guide.md Drop repro-env/build-images.sh references; document the new nix build .#… derivations and *-manifest-digest outputs
deployment/Dockerfile-node, deployment/Dockerfile-node-gcp, deployment/Dockerfile-rust-launcher, deployment/build-images.sh, deployment/repro-sources-list.sh Removed
flake.nix Wire up the shared buildRustBin, expose new packages, gate image / digest outputs to stdenv.isLinux
nix/rust-build-common.nix New shared crane builder (vendor dir, JEMALLOC/CFLAGS/RUSTFLAGS env, path-remap preBuild)
nix/mpc-node.nix Reduced to crate-specific buildInputs and the call into buildRustBin
nix/tee-launcher.nix New leaf derivation for tee-launcher
nix/node-image.nix, nix/node-gcp-image.nix, nix/rust-launcher-image.nix New dockerTools.buildLayeredImage derivations including start.sh, embedded localnet genesis, and the binary symlink
nix/image-manifest-digest.nix New helper that runs skopeo entirely inside the Nix sandbox to deterministically compute the manifest digest
scripts/build-and-verify-rust-launcher-docker-image.sh Source the digest from the new *-manifest-digest derivation instead of skopeo-from-docker-daemon

Findings

CI is green across Build MPC Node TEE Docker image, Build Rust Launcher Docker image and verify, the unit/E2E test matrices, and the Nix build mpc-node job, so the end-to-end flow (build → load → run → push → digest verification) is exercising the new derivations successfully.

Blocking (must fix before merge):

None. The flow looks correct and CI confirms it.

Non-blocking (nits, follow-ups, suggestions):

  • nix/node-image.nix:48, nix/node-gcp-image.nix:43SSL_CERT_FILE moved from /etc/ssl/certs/ca-certificates.crt to /etc/ssl/certs/ca-bundle.crt because that's what nixpkgs cacert ships. Worth calling out in the PR description / release notes for anyone running tools that hardcode the old path; the env var alone is enough for the mpc-node binary, but operators with sidecar tooling that re-reads the path may notice.
  • deployment/cvm-deployment/launcher_docker_compose.yaml:5,12 — These pinned digests were produced by the old build pipeline; switching to Nix-built images will produce different manifest digests, so the existing TODO(Decouple Rust launcher Docker image hash verification from workspace dependency changes #2662) "launcher hash mismatch (temporarily allowed)" warning in scripts/build-and-verify-rust-launcher-docker-image.sh:36-39 will continue to fire until those deployment digests are re-pinned. Not a regression, but a reminder that operators voting on code_hash/launcher_hash will need fresh digests after this lands.
  • nix/rust-launcher-image.nix:33 — Image only contains cacert, docker-client, and the launcher binary. There's no bash / coreutils, so docker exec -it … sh debugging won't work. Cheap to add (~few MB) if that matters operationally; otherwise fine.
  • .github/workflows/docker_build_*.yml — None of the release workflows use the new restore-nix-cache action, so each push rebuilds from scratch. With 32x runners that's tolerable, but a future caching pass on those workflows would noticeably speed up release cuts. Not a blocker.
  • flake.nix:71-86buildRustBin is called once per package, so cargoArtifacts is built twice (once per cargoExtraArgs). Crane could share this if both were structured around the same commonArgs and buildPackage was called per binary; not worth refactoring now, but worth noting.
  • nix/image-manifest-digest.nix:33copy --all against a docker-archive source that only contains a single-platform image is a no-op today; if dockerTools ever gains multi-arch image support this would silently change behavior. Fine as-is, just a flag.

✅ Approved

@gilcu3 gilcu3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM

I know this is just a draft but as I am not going to be able to review anything next week left some comments/suggestions, hopefully they are useful somehow.

Comment thread .github/workflows/docker_build_node.yml Outdated
Comment on lines +48 to +59
export NODE_IMAGE_NAME=mpc-node
./deployment/build-images.sh --node --push
set -euo pipefail

# Tag mirrors the legacy build script: <branch>-<short-sha>, with
# `/` sanitized to `-`.
branch="${GITHUB_REF_NAME//\//-}"
short_sha=$(git rev-parse --short HEAD)
tag="${branch}-${short_sha}"
echo "Pushing as tag: ${tag}"

archive=$(nix build --no-link --print-out-paths .#node-image)
digest_file=$(nix build --no-link --print-out-paths .#node-image-manifest-digest)
echo "Manifest digest: $(cat "$digest_file")"

skopeo copy --preserve-digests \
"docker-archive:${archive}" \
"docker://docker.io/nearone/${IMAGE_NAME}:${tag}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be much nicer to keep the script ./deployment/build-images.sh and just modify it to run the nix steps. In general it is better to keep the ci workflows as lean as possible

Comment on lines +41 to +57
- name: Install skopeo
run: |
sudo apt-get update
sudo apt-get install -y skopeo
branch="${GITHUB_REF_NAME//\//-}"
short_sha=$(git rev-parse --short HEAD)
tag="${branch}-${short_sha}"
echo "Pushing as tag: ${tag}"

- name: Build and push node gcp image
run: |
export NODE_GCP_IMAGE_NAME=mpc-node-gcp
./deployment/build-images.sh --node-gcp --push
archive=$(nix build --no-link --print-out-paths .#node-gcp-image)
digest_file=$(nix build --no-link --print-out-paths .#node-gcp-image-manifest-digest)
echo "Manifest digest: $(cat "$digest_file")"

skopeo copy --preserve-digests \
"docker-archive:${archive}" \
"docker://docker.io/nearone/${IMAGE_NAME}:${tag}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, we could keep the script and avoid adding logic to the workflows

Comment on lines +1233 to +1230
commit hash: 828f816be36aed6f0d2438e0131b3e9d7d0931ad
SOURCE_DATE_EPOCH used: 0
node binary hash: 86c8f7d8913d6fe37a6992bba165d15a3a1d88fbf6cdff605e4827d5183721bc
node manifest digest: sha256:331cfec941671ac343c52847e255eb36a280da65535d2a1e4d002c4c64686e19
$ nix build .#node-image-manifest-digest && cat result
sha256:331cfec941671ac343c52847e255eb36a280da65535d2a1e4d002c4c64686e19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to keep printing the same things as before, else debugging might get more complicated.

Comment thread nix/image-manifest-digest.nix Outdated
@@ -0,0 +1,43 @@
{
runCommand,
skopeo,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hoped skopeo would not be needed when moving to full nix. The way we use it is basically a coincidental trick to guarantee the image manifest is compressed deterministically.

@netrome

netrome commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Closing as stale.

@netrome netrome closed this Jun 1, 2026
DSharifi and others added 3 commits June 12, 2026 15:04
# Conflicts:
#	.github/workflows/docker_build_node.yml
#	.github/workflows/docker_build_node_gcp.yml
#	.github/workflows/docker_build_rust_launcher.yml
#	deployment/build-images.sh
#	docs/reproducible-builds.md
#	flake.nix
#	nix/mpc-node.nix
#	scripts/build-and-verify-rust-launcher-docker-image.sh
The script is kept as the lean entry point for the release workflows per
review feedback on #3171.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
build-images.sh now needs nothing but nix and git: skopeo is re-exported
from the flake (pinned through flake.lock, the same binary image-dir.nix
uses) and invoked with `nix run .#skopeo`. Removes the apt install step
from the three docker build workflows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DSharifi DSharifi reopened this Jun 12, 2026
@DSharifi DSharifi marked this pull request as ready for review June 12, 2026 13:34
@DSharifi DSharifi closed this Jun 12, 2026
@claude

claude Bot commented Jun 12, 2026

Copy link
Copy Markdown

Pull request overview

Replaces the legacy repro-env/podman pipeline with a Nix-driven flow. deployment/build-images.sh is retained as the one entry point for both CI and humans, but its body is now a thin wrapper around new flake outputs: mpc-node, tee-launcher, the three *-image / *-image-dir / *-image-manifest-digest derivations, and a re-exported flake-pinned skopeo. The shared Crane builder has been factored out into nix/rust-build-common.nix, image construction moved to dockerTools.buildLayeredImage, and manifest-digest computation now runs skopeo inside the Nix sandbox via nix/image-dir.nix so the registry digest is a pure function of the flake revision.

Changes:

  • Factor shared Rust-binary build (vendor dir, JEMALLOC/CFLAGS/RUSTFLAGS env, path-remap preBuild) into nix/rust-build-common.nix; reduce nix/mpc-node.nix to crate-specific deps and add a parallel nix/tee-launcher.nix
  • Replace deployment/Dockerfile-* and the repro-env build path with dockerTools.buildLayeredImage derivations + an image-dir.nix helper that runs skopeo entirely inside the Nix sandbox
  • Add image-manifest-digest.nix which hashes manifest.json from the sandbox-produced dir: layout — the same bytes that are pushed via skopeo copy --preserve-digests
  • Rewrite deployment/build-images.sh as a Nix wrapper (nix build .#…, nix run .#skopeo …) that preserves the previous stdout format (commit hash, node binary hash, node manifest digest, …)
  • Update the four GitHub workflows to install Nix and call into the wrapper script (no inline build logic); bump release runners to warp-ubuntu-2404-x64-32x
  • Update README/docs/TDX guide to the new nix build .#… invocations and remove repro-env / repro-sources-list.sh references

Reviewed changes

Per-file summary
File Description
flake.nix Wire up shared buildRustBin, expose mpc-node/tee-launcher, gate image / image-dir / manifest-digest packages to stdenv.isLinux, re-export pinned skopeo
nix/rust-build-common.nix New shared Crane builder: source filter, custom vendor dir (with near-jsonrpc workaround), env, preBuild path-remap hook
nix/mpc-node.nix Reduced to crate-specific buildInputs; injects BUILT_OVERRIDE_mpc_node_GIT_COMMIT_HASH_SHORT from flake's self.shortRev
nix/tee-launcher.nix New thin leaf derivation calling buildRustBin
nix/node-image.nix, nix/node-gcp-image.nix, nix/rust-launcher-image.nix dockerTools.buildLayeredImage definitions; embed start.sh + localnet genesis + binary symlink under /app; pinned created = "1970-01-01T00:00:00Z"
nix/image-dir.nix Helper that converts a docker-archive tarball into skopeo's dir: layout entirely inside the Nix sandbox (with --insecure-policy and a writable --tmpdir)
nix/image-manifest-digest.nix Computes sha256:<hex> from the sandbox-produced manifest.json
deployment/build-images.sh Reworked to call nix build / nix run .#skopeo; same flag interface and stdout format as before; --push uses skopeo copy --preserve-digests dir:… from the sandbox dir: layout
deployment/Dockerfile-{node,node-gcp,rust-launcher}, deployment/repro-sources-list.sh Removed (logic replaced by Nix derivations)
.github/workflows/docker_build_{node,node_gcp,rust_launcher}.yml Drop podman/repro-env setup + system skopeo install; install Nix and call ./deployment/build-images.sh --<image> --push; bump runner to warp-ubuntu-2404-x64-32x
.github/workflows/ci.yml TEE/launcher builds now nix build .#…-image && docker load; the launcher hash verification runs the new script
scripts/build-and-verify-rust-launcher-docker-image.sh Reads the digest from .#rust-launcher-image-manifest-digest (no docker-daemon dependency); strict assert remains removed per #2662
README.md, docs/reproducible-builds.md, docs/localnet/docker-localnet.md, docs/running-an-mpc-node-in-tdx-external-guide.md Drop repro-env/Dockerfile references; document the new nix build .#… derivations and the wrapper script

Findings

CI is green; the prior review's blocking-class concerns have all been resolved:

  • deployment/build-images.sh is preserved and called from each workflow (addresses gilcu3's "keep ci workflows lean" feedback).
  • The stdout format (commit hash, node binary hash, node manifest digest, …) is preserved, so the TDX operator guide's printed examples remain accurate (addresses gilcu3's "keep printing the same things as before").
  • nix/image-dir.nix and nix/image-manifest-digest.nix carry extensive comments explaining why skopeo is still in the loop (deterministic manifest compression inside the sandbox, not a runtime push dependency), responding to gilcu3's "I hoped skopeo would not be needed" note.

Blocking (must fix before merge):

None.

Non-blocking (nits, follow-ups, suggestions):

  • nix/node-image.nix:50, nix/node-gcp-image.nix:45SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt differs from the legacy Dockerfile's /etc/ssl/certs/ca-certificates.crt. Fine for mpc-node itself (reads SSL_CERT_FILE), but operator-side sidecar tooling that hardcodes the old path will need adjusting; worth a release note.
  • .github/workflows/docker_build_*.yml — None of the release workflows restore the Nix cache (no restore-nix-cache step), so each push rebuilds the closure from scratch. With 32x runners that's tolerable, but a small follow-up to add caching here would noticeably speed up release cuts.
  • deployment/build-images.sh:90-92nix run .#skopeo pushes against ~/.docker/config.json populated by docker/login-action. Works on the warp Ubuntu runners today (no credsStore helper), but it is an implicit coupling; consider an explicit nix run .#skopeo -- login docker.io … step (with the registry token via --password-stdin) so the auth path doesn't silently break if the runner image changes.
  • nix/rust-launcher-image.nix:28-33 — Image contains only cacert, docker-client, and the launcher binary, so docker exec … sh debugging won't work. Cheap to add bash+coreutils (~few MB) if that matters operationally.
  • nix/image-dir.nix:42skopeo copy --all against a single-platform docker-archive source is a no-op today; if dockerTools ever grows multi-arch image support this would silently change the layout being hashed. Worth a comment or an explicit single-platform selector.
  • flake.nix:73-81buildRustBin is called twice (mpc-node, tee-launcher), so cargoArtifacts is realized twice. Crane can share a single commonArgs/buildDepsOnly across multiple buildPackage calls if both binaries have compatible cargoExtraArgs; not worth refactoring now, just a flag.
  • flake.nix:79 (with the consumer at nix/mpc-node.nix:49) — gitCommitHashShort falls back to self.dirtyShortRev or "unknown". For dirty trees this still produces a reproducible-looking string but the resulting binary's embedded GIT_COMMIT_HASH_SHORT no longer corresponds to any committed revision; consider asserting non-dirty (or stamping dirty-<rev>) on the release push path so a published binary hash is never silently mislabelled.

Note: the PR is currently CLOSED ("Closing as stale."). The points above only apply if the work is revived.

✅ Approved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants