Skip to content

Simplify reproducible builds: single version pin, add binary tarball + AppImage outputs#9147

Open
antondlr wants to merge 9 commits intounstablefrom
rework-reproducible-builds
Open

Simplify reproducible builds: single version pin, add binary tarball + AppImage outputs#9147
antondlr wants to merge 9 commits intounstablefrom
rework-reproducible-builds

Conversation

@antondlr
Copy link
Copy Markdown
Member

@antondlr antondlr commented Apr 20, 2026

Rationale

  • To us, it looks like the current state of Lighthouse reproducible builds is that they are not widely used, and the one confirmed user, apparantly rolls their own
  • We'd like to cut down on maintenance burden, where possible
  • That being said, there's definitely some value in having robust reproducible builds available, as well as distribution images.
  • Docker image + binary + AppImage should cover most deployments without too much hassle

Summary

  • Replaces docker-reproducible.yml with reproducible.yml — one workflow producing three artifacts per arch: Docker image, binary tarball, AppImage
  • Single version tag to maintain: the RUST_IMAGE ARG in Dockerfile.reproducible (multi-arch index digest). Makefile and CI no longer carry their own per-arch image references
  • Bumped builder and runtime to Debian Bookworm (was Bullseye), Rust 1.95 (was 1.88)
  • Switched runtime base from distroless/cc-debian12 (broken — wrong OpenSSL ABI) to distroless/cc-debian12:nonroot with only libz.so.1 copied from the builder; libssl/libcrypto are statically linked in this build
  • Adds packaging/appimage/ template (AppRun, .desktop, SVG icon)
  • All version pins are at the top of their respective files: build-time pins in Dockerfile.reproducible, appimagetool SHA256s in the workflow env: block

Caveats

  • appimagetool has no stable release tagscontinuous is the only release. We pin by SHA256 of the downloaded binary and verify before use. Updating requires downloading the new binary, running sha256sum, and bumping APPIMAGETOOL_SHA256_AMD64/ARM64 in the workflow env block
  • AppImage reproducibility is best-effort: the ELF binary inside is byte-for-bit verified; the squashfs wrapper produced by appimagetool is not verified across runs (would require pinning the appimagetool binary across CI runs, which the SHA256 check enables going forward)
  • publish job assumes the GitHub release draft already exists (created by release.yml running in parallel on the same tag). There is no cross-workflow ordering enforcement — if release.yml is significantly slower, gh release upload will fail. This matches the design of the existing PR feat: Add reproducible Debian package builds and distribution #7617 and is a known limitation
  • Supersedes PR feat: Add reproducible Debian package builds and distribution #7617

Testing

Built and smoke-tested locally (lighthouse:reproducible-arm64 --version) on arm64 (Bookworm/Rust 1.95).

TODO

  • I don;t think the build twice and compare has any value as we are building in the same environment on the same system
  • the SVG for the AppImage needs love (color and/or a background)

antondlr and others added 7 commits April 20, 2026 10:55
…+ AppImage outputs

- Replace docker-reproducible.yml with reproducible.yml which produces
  three artifacts per arch: Docker image, binary tarball, and AppImage
- Use a single multi-arch index digest in Dockerfile.reproducible as the
  sole version tag to maintain; Makefile and CI no longer carry their own
  per-arch image references
- Add packaging/appimage/ template (AppRun, .desktop, lighthouse.svg)

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
… from builder

The previous final stage used distroless/cc-debian12 (Bookworm) which carries
no libssl and uses OpenSSL 3, making the Bullseye-built binary non-functional.

- Switch to distroless/cc-debian11:nonroot (pinned by index digest) — same
  Bullseye ABI as the builder, already includes libc and libgcc
- Copy libssl.so.1.1 and libcrypto.so.1.1 from the builder stage into /usr/lib/
  so no package manager is invoked in the final image (stays fully pinned)
- Normalise the arch-specific triplet lib path via a `find` into /libs/ so the
  COPY instructions work identically for both amd64 and arm64 builds

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
…ssl COPY

ldd on the built binary shows only libz.so.1 is missing from distroless/cc-debian11;
libssl/libcrypto are statically linked by this build and do not need to be copied.
libstdc++.so.6 and libgcc_s.so.1 are already present in the distroless/cc variant.

Also consolidates the mv + mkdir into a single RUN layer.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
- Rust builder: rust:1.88-bookworm (multi-arch index digest)
- Runtime: distroless/cc-debian12:nonroot (Bookworm, pinned by index digest)
- Build deps bumped to Bookworm versions:
    libclang-dev 1:14.0-55.7~deb12u1
    cmake         3.25.1-1
    libjemalloc-dev 5.3.0-1
- libz.so.1 search path updated /lib → /usr/lib (moved in Bookworm)

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
rust:1.95-bookworm@sha256:225aa827...

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
- Pin appimagetool by SHA256 digest rather than floating 'continuous' tag.
  The tool has no stable releases; we verify the download hash before use.
  To update: download new binary, sha256sum it, bump the matrix value.
- Add --clobber to gh release upload to handle reruns cleanly.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
All version pins are now visible at the top of their respective files:
- Dockerfile.reproducible: Rust image, apt packages, distroless runtime
- reproducible.yml: appimagetool SHA256s (APPIMAGETOOL_SHA256_AMD64/ARM64)

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
antondlr and others added 2 commits April 21, 2026 09:12
The double-build check ran both passes on the same runner/daemon/filesystem —
any non-determinism it could catch is already eliminated by the build pins
(SOURCE_DATE_EPOCH, compiler digest, pinned deps). Replace with a single build
that prints the binary SHA256 for external verification.

SVG: add Sigma Prime brand color background (#CC00A0), white logo mark.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
@antondlr antondlr marked this pull request as ready for review April 21, 2026 07:16
@chong-he chong-he added the ready-for-review The code is ready for review label Apr 24, 2026
Copy link
Copy Markdown

@bakhtin bakhtin left a comment

Choose a reason for hiding this comment

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

Thanks for the refactor of original PR. I left some comments.

You pointed out that building twice in the same environment does not make sense which I agree with. But to confirm the build is really reproducible one should build on two different hosts that are sufficiently different. At Flashbots we're building on fresh runners with ubuntu22 and ubuntu24 and compare the hashes of a binary. They should match.

Reproducibility test wasn't part of the original PR either but something worth to follow up with later.

jobs:
extract-version:
name: extract version
runs-on: ubuntu-22.04
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unless you have a specific reason, why not running on ubuntu-24.04?

Comment thread Dockerfile.reproducible
RUN apt-get update && apt-get install -y libclang-dev=1:11.0-51+nmu5 cmake=3.18.4-2+deb11u1 libjemalloc-dev=5.2.1-3
# Install pinned versions of the build dependencies
RUN apt-get update && apt-get install -y \
libclang-dev=1:14.0-55.7~deb12u1 \
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pinning to a specific package version does not guarantee the package will be there after some time. Debian tends to remove older versions of packages.

A better approach would be to use a package from the pinned snapshots Debian repo. Check out https://github.com/flashbots/rbuilder/blob/develop/docker/Dockerfile.reproducible for an example. With the pinned snapshot you also don't need to pin package versions.

Comment thread Dockerfile.reproducible
# This multi-arch index digest resolves to the correct arch-specific image at build time.
# To update: run `docker buildx imagetools inspect rust:X.Y-bookworm` and replace the digest below.
# rust:1.95-bookworm
ARG RUST_IMAGE="rust:1.95-bookworm@sha256:225aa827d55fae9816a0492284592827e794a5247c6c6a961c3b471b344295ec"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bookworm is nearing EOL soon. trixie is around since August last year so maybe switch to a newer version. A newer version will also fix an OpenSSL issue you're having.

Comment thread Dockerfile.reproducible

# Move the binary to a standard location
RUN mv /app/target/${RUST_TARGET}/release/lighthouse /lighthouse
# Move the binary and runtime libs to fixed paths for arch-independent copying below.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

trixie distroless docker image comes with libz by default. If you switch you won't need to do this copying.

Comment on lines +117 to +148
- name: Download appimagetool
run: |
curl -fsSL \
"https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.appimage_arch }}.AppImage" \
-o appimagetool
# Verify against pinned SHA256 (see APPIMAGETOOL_SHA256_* env vars at top of file)
EXPECTED="APPIMAGETOOL_SHA256_$(echo '${{ matrix.appimage_arch }}' | tr '[:lower:]' '[:upper:]')"
echo "${!EXPECTED} appimagetool" | sha256sum --check
chmod +x appimagetool

- name: Assemble AppDir
run: |
mkdir -p AppDir/usr/bin
cp lighthouse-bin AppDir/usr/bin/lighthouse
cp packaging/appimage/AppRun AppDir/AppRun
chmod +x AppDir/AppRun
cp packaging/appimage/lighthouse.desktop AppDir/lighthouse.desktop
cp packaging/appimage/lighthouse.svg AppDir/lighthouse.svg

- name: Build AppImage
env:
VERSION: ${{ needs.extract-version.outputs.VERSION }}
# Deterministic squashfs: fixed modification times, no extra metadata
SOURCE_DATE_EPOCH: 0
run: |
./appimagetool \
--comp xz \
AppDir \
lighthouse-${VERSION}-${{ matrix.appimage_arch }}.AppImage
sha256sum lighthouse-${VERSION}-${{ matrix.appimage_arch }}.AppImage \
> lighthouse-${VERSION}-${{ matrix.appimage_arch }}.AppImage.sha256

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.

It came to my attention this is introduced and the cargo debian packaging from the original PR has been removed. Are there plans to add this here in case this PR is going to replace #7617 ?
Also it only uploads the built debian package with a default systemd service file after verifying it is reproducible.

"https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.appimage_arch }}.AppImage" \
-o appimagetool
# Verify against pinned SHA256 (see APPIMAGETOOL_SHA256_* env vars at top of file)
EXPECTED="APPIMAGETOOL_SHA256_$(echo '${{ matrix.appimage_arch }}' | tr '[:lower:]' '[:upper:]')"
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.

There is a potential bug here because the env variables are named APPIMAGETOOL_SHA256_AMD64 and APPIMAGETOOL_SHA256_ARM64 mean while the appimage_arch in the matrix use x86_64 and aarch64.
I would recommend renaming the env variables to follow the same format of the matrix, or add a separate env variable in the matrix above like :

appimagetool_sha_env: APPIMAGETOOL_SHA256_AMD64
appimagetool_sha_env: APPIMAGETOOL_SHA256_ARM64

and replace EXPECTED here with:
EXPECTED="${{ matrix.appimagetool_sha_env }}"

@MoeMahhouk
Copy link
Copy Markdown
Contributor

I don't think the build twice and compare has any value as we are building in the same environment on the same system

I agree to some extent. The above mentioned suggestion by @bakhtin is a reasonable follow-up option to tackle this on different setup/environment. However, I still believe it has value to have this check to catch early on any changes that impacts reproducibility in the same environment/systems. Remember reproducibility was an issue before in the same environment. With such checks, you would be notified that the corresponding modification contributed to the reproducibility violation.

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

Labels

infra-ci ready-for-review The code is ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants