Draft: Build the EE assets with Nix from this repository

What does this MR do?

Moves the Nix build of the EE assets, and their /assets image, from hardened-builds into this repository, and adds CI jobs that build them and check their file names against build-assets-image's.

hardened-builds kept this recipe, so a frontend change here broke the Nix assets downstream, in CNG, after the merge. With the recipe here, the merge request that breaks it can fix it. The flake builds on hardened-builds' curated package set, its legacyPackages, and has no nixpkgs of its own.

  • nix/ is a flake that builds gitlab-assets-ee from this checkout, with or without source maps, on x86_64-linux and aarch64-linux, the platforms GitLab supports, and on aarch64-darwin. Its /assets image is built on Linux only, as nix2container builds Linux images. Nothing has to be locked per commit beyond what the lockfiles hold:
    • Gems: nix/gemset.json comes from Gemfile.lock's CHECKSUMS, written offline by scripts/nix/generate_gemset.rb the way bundix would.
    • Yarn: each yarn.lock entry is one fetch, checked against its integrity, so no mirror hash is committed.
    • Rust gems: nix/lock.json holds the cargo vendor hashes of the four Rust gems, keyed by gem version. scripts/nix/update_lock.rb refreshes both files.
    • glql: gitlab_query_language ships its Cargo.lock at the gem's root, where cargo, building in ext/, doesn't look, and without a source for glql, which glql's release builds from a checkout. The gem is repacked with that lockfile in ext/ and glql at its tag's commit, from nix/lock.json, so the crates are the ones glql released with and nothing is resolved here.
    • Drift: Nix checks every hash it downloads, but not that nix/gemset.json and nix/lock.json still match Gemfile.lock. The gem env depends on a derivation that runs scripts/nix/update_lock.rb --check, so a stale file fails every build, in this repository's CI or elsewhere. The flake also exposes it as checks.<system>.nix-lock.
  • CI:
    • build-assets-image-nix builds the image in Nix's sandbox and pushes gitlab-assets-ee-nix:$CI_COMMIT_SHA. The sandbox needs a privileged runner, which saas-linux-large-amd64 is. It builds in /build, as on NixOS, instead of a new /nix/var/nix/builds/nix-<pid>-<random> directory each time, where a tool that writes its path into the output changes the assets.
    • compare-assets-image-nix checks that every file its manifests name is in gitlab-assets-ee:$CI_COMMIT_SHA. It restores the build's cache for its tools, because building them downloads skopeo's patches from GitHub.
    • nix-lock-check fails when nix/gemset.json or nix/lock.json no longer match Gemfile.lock.

Commits:

  1. Add a Nix derivation for the EE assets: the hand-written nix/ files and the two scripts that generate the lock files, about 1,200 lines.
  2. Add the Nix lock files: nix/gemset.json, nix/lock.json, nix/flake.lock and the .gitattributes entry. scripts/nix/update_lock.rb --check regenerates nix/gemset.json and checks the versions in nix/lock.json.
  3. Build the EE assets image with Nix in CI: the three jobs, their rules, the build and comparison scripts.

Validation so far

  • Local build, before !262551 (merged), with hardened-builds at the export commit: gitlab-assets-ee with source maps built in 76 minutes on a 16-core builder, and its image in about 2. Against gitlab-assets-ee:fa79cc62f4d632420dc365b20c9939a577e160cc (master), the names are identical: webpack 4594 files, Rspack 1696 files, 939 entrypoints, Sprockets 1015 files.
  • CI comparison (2967035688, sandboxed, on master e85ad990, after !259425 (merged) removed the frontend islands): every name matches a fresh compile-production-assets-rspack compile. webpack 4585 files and 943 entrypoints, Rspack 1699 files and 943 entrypoints, Sprockets 1016 files. These pipelines ran with a test-only commit on top, since dropped, that set ENABLE_COMPILE_PRODUCTION_ASSETS so they built the reference image, and fetched hardened-builds from the branch of gitlab-org/software-supply-chain-security/hardened-builds!245 (merged). 2962754292 on master 89f9fcc6, the sandboxed build of 2955581957 on master 24df4d74 and the unsandboxed build of 2952009663 matched too. Before !262551 (merged), Rspack names differed (2950755954: 234 names), because Rspack's output depended on the build directory.
  • CI build: build-assets-image-nix passed in eleven pipelines of this merge request: 44 minutes cold (2949364597), then 23 to 29 with the CI cache (2949875030, 2950755954, 2952009663, 2955581957, 2962754292, 2967035688, 2968161029, 2975236712 on master 9ab01f38 with hardened-builds main, 2975327540, 2980629163 with hardened-builds main f6f4db1b, after gitlab-org/software-supply-chain-security/hardened-builds!249 (merged)). That covers fetching the now public hardened-builds without credentials (2975327540), the shallow clone, the skopeo push, and the no-source-maps variant that merge request pipelines build.
  • Sandbox: the build log of 2955581957 has 4874 lines with /build/source and none with /nix/var/nix/builds. The unsandboxed log of 2952009663 has the reverse: none and 4872, all in /nix/var/nix/builds/nix-12016-<random>.
  • Systems: every output evaluates on x86_64-linux, aarch64-linux and aarch64-darwin; only x86_64-linux has been built. Supporting several systems left the x86_64-linux assets and ci-tools as they were: their derivation paths are the same before and after the change.
  • Gemset: for the Gemfile.lock of the prototype's last run, the generator reproduces bundix's gemset exactly (771 gems).
  • Lock check: passes on aarch64-darwin, and on x86_64-linux as part of the gem env in 2968161029. With gitlab-email_handler switched back from gem to path in nix/gemset.json, the drift master had on 2026-10-20, the build fails with nix/gemset.json is out of date. The check alone adds no gem rebuilds: it changes only the gem env's inputs. scripts/nix/update_lock.rb took master's gitlab_query_language 0.42.0 bump end to end: the gemset, the tag's commit and the cargo hash.
  • glql: cargo accepts the gem's own Cargo.lock plus the one source line and changes nothing else, and the lockfile in the vendor tree is byte-identical to the one cargo fetch writes. The gem built on aarch64-darwin with those crates (rb-sys 0.9.117, bindgen 0.69.5), loads, and compiles a query. It built on x86_64-linux in 2968161029, which pushed the image.
  • Lint: yamllint (both profiles), shellcheck, nixfmt and RuboCop are clean. The new rules pass the checks of spec/dot_gitlab_ci/rules_spec.rb.
Not validated yet
  • Specs for scripts/nix/*.rb.

Open questions

  • Dependency bot gem bumps. Once this merges, a GitLab Dependency Bot merge request that changes only Gemfile.lock fails nix-lock-check, as master's oj 3.17.7 bump did on this branch. The bot already regenerates Gemfile.next.lock in a postUpgradeTasks command. Running scripts/nix/update_lock.rb there too would keep nix/gemset.json current with Ruby only. A bump of one of the four Rust gems still needs Nix for its cargo hash.
  • nix/ owner in .gitlab/CODEOWNERS: validate-codeowners-coverage fails until there is one.

Checklist

Pre-merge

  • Pipeline types. The build and comparison are manual in MR pipelines that change the assets patterns, nix/** or scripts/nix/**, and run automatically only when ENABLE_BUILD_ASSETS_IMAGE_NIX is "true". nix-lock-check runs in MR and master pipelines that change Gemfile{,.lock}, nix/*.json or scripts/nix/*.rb. All three are allowed to fail. Expedited MR pipelines skip the Nix jobs.
  • Non-canonical projects:
    • gitlab-foss, security, dev, personal forks: the build and comparison run only in gitlab-org/gitlab on gitlab.com, because the build holds a large runner for 25 to 45 minutes. nix-lock-check needs Ruby only.
  • Pipeline performance. No change to existing jobs. A build holds a saas-linux-large-amd64 runner for 25 to 45 minutes, and only when someone plays it. The comparison takes minutes on the default runner; the lock check takes seconds.

New jobs:

  • Change-related rules: above.
  • Frequency: manual in MRs; automatic only where ENABLE_BUILD_ASSETS_IMAGE_NIX is set, for example a schedule.
Edited by Alessio Caiazza

Merge request reports

Loading
Loading