Skip to content

Unified Container Build with Cache Mounts - #318

Open
yairpod wants to merge 1 commit into
trusted-execution-clusters:mainfrom
yairpod:build_optimizing
Open

Unified Container Build with Cache Mounts#318
yairpod wants to merge 1 commit into
trusted-execution-clusters:mainfrom
yairpod:build_optimizing

Conversation

@yairpod

@yairpod yairpod commented Jul 23, 2026

Copy link
Copy Markdown
Member

Until now our builds have compiled the same files 4 times, As the builds were done in separate containers and had no access to the compilation objects from the other containers or previous builds. By Unifying the Build stage into one Container and interceding build caches we will prevent unnecessary work and achieve faster builds.

To check the effectiveness of these changes I used Opus 4.6 to write a build time benchmark script (see attached) and run a short test (also see attached).
The Highlights are these:
Without this PR the Operator container build was up to 30% faster the second build to the first build (~220 second time ~310 first time) , with the smaller gains for the other containers.
With the PR the difference is 70% (~110 second time ~305 first time) , but the other containers show a much larger improvement, as the speed up is about 90% (~30 seconds first build to ~3 in second build), also the build time of the first build of the other containers is reduced from the original code build time of ~220 seconds to ~30 in united build, is is due to the containers being build togather.

benchmark-builds.sh
results.csv
summary.txt

Summary by Sourcery

Unify Rust binary container builds into a single cached builder stage and derive multiple runtime images from it to reduce duplicate compilation and improve build performance.

New Features:

  • Introduce multi-stage Containerfile targets for operator, compute-pcrs, register-server, and attestation-key-register binaries.
  • Add integration of the reference-values Git repository into the build to bundle required reference data into compute-pcrs images.

Enhancements:

  • Enable shared cargo build caches and a unified cargo build invocation to accelerate debug and release builds across all binaries.
  • Refine Cargo workspace membership and dev-dependency handling to support unified builds and dependency pre-compilation.
  • Update Makefile targets to build all images from the unified Containerfile, and add container builder cache pruning to the clean target.

Build:

  • Set a default build_type argument and introduce multi-stage, target-based container builds driven from a single Containerfile while removing per-binary Containerfiles.

Tests:

  • Wire the reference-values crate into the tests workspace for consistent usage in test binaries.

@sourcery-ai

sourcery-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

Unifies all Rust-based container images into a single multi-stage Containerfile using a shared builder with cached cargo mounts, adds reference-values as a workspace dependency, and updates the Makefile and ignore rules to consume the unified build pipeline while removing per-component Containerfiles.

File-Level Changes

Change Details Files
Unify Rust binary builds into a single cached builder stage and produce all distribution images from one multi-stage Containerfile.
  • Set a default build_type=release and consolidate all Rust builds into a single builder stage based on the existing buildroot image.
  • Clone the reference-values git dependency during the build and copy its efivars and mok-variables into an /output/reference-values directory.
  • Expand the workspace members and copy minimal Cargo.toml/lib.rs stubs for operator, compute-pcrs, register-server, and attestation-key-register to pre-build dependencies.
  • Use buildkit cache mounts for /build/target and /usr/local/cargo/registry when pre-building dependencies in debug builds.
  • Perform a single cargo build across all four binaries, then copy each resulting binary into /output along with the reference-values artifacts.
  • Define separate distribution stages for operator, compute-pcrs, register-server, and attestation-key-register that copy binaries from the unified builder and configure ports and entrypoints for the services.
Containerfile
Switch Makefile image targets to the unified multi-stage Containerfile and manage builder cache cleanup.
  • Update operator-image, compute-pcrs-image, reg-server-image, and attestation-key-register-image targets to use --target with the shared Containerfile instead of per-component Containerfiles.
  • Extend the clean target to prune container builder caches via the configured container CLI.
Makefile
Wire reference-values as a workspace dependency and into tests.
  • Add reference-values as a git dependency in the main Cargo.toml so it can be cloned and built as part of the workspace.
  • Expose reference-values to tests via the workspace and tests/Cargo.toml so test code can reuse it.
Cargo.toml
tests/Cargo.toml
Cargo.lock
Optimize the container build context and remove legacy per-component Containerfiles.
  • Introduce a .containerignore file to reduce the build context size and improve build efficiency.
  • Remove obsolete per-component Containerfiles for attestation-key-register, compute-pcrs, and register-server now that all images are built from the unified Containerfile.
.containerignore
attestation-key-register/Containerfile
compute-pcrs/Containerfile
register-server/Containerfile

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Containerfile" line_range="58-61" />
<code_context>
+      -p attestation-key-register \
+      $release_flag && \
+    mkdir -p /output && \
+    cp /build/target/${build_type}/operator /output/ && \
+    cp /build/target/${build_type}/compute-pcrs /output/ && \
+    cp /build/target/${build_type}/register-server /output/ && \
+    cp /build/target/${build_type}/attestation-key-register /output/
+
+# Distribution stages
</code_context>
<issue_to_address>
**issue (bug_risk):** The use of `build_type` directly in the target path can break builds for unexpected values.

This logic only works when `build_type` is `release` or `debug`: Cargo will still build to `debug` unless `--release` is used, but the `cp /build/target/${build_type}/...` paths will then be wrong for other values (e.g., `prod`) and the copy will fail. Either derive a `profile_dir` (`release`/`debug`) used consistently for both `cargo build` and copy paths, or validate `build_type` and fail early if it’s not one of the supported values.
</issue_to_address>

### Comment 2
<location path="Containerfile" line_range="32" />
<code_context>
     sed -i '/\[dev-dependencies\]/,$d' operator/Cargo.toml && \
+    sed -i '/\[dev-dependencies\]/,$d' register-server/Cargo.toml && \
     sed -i '/trusted-cluster-operator-test-utils/d' lib/Cargo.toml && \
+    git clone --depth 1 https://github.com/trusted-execution-clusters/reference-values && \
     make crds-rs

</code_context>
<issue_to_address>
**🚨 suggestion (security):** Unpinned `git clone` introduces non-reproducible builds and potential supply-chain risk.

Cloning `reference-values` without pinning a commit, tag, or at least a branch means builds can change unexpectedly as the upstream repo evolves and increases supply-chain risk. Please pin to a specific commit or tag (and ideally verify signatures/checksums) so the builder always uses a known, trusted revision and only changes when explicitly updated.

Suggested implementation:

```
ARG REFERENCE_VALUES_REV=v0.0.0

RUN sed -i 's/members = .*/members = ["lib", "operator", "compute-pcrs", "register-server", "attestation-key-register"]/' Cargo.toml && \
    sed -i '/\[dev-dependencies\]/,$d' operator/Cargo.toml && \
    sed -i '/\[dev-dependencies\]/,$d' register-server/Cargo.toml && \
    sed -i '/trusted-cluster-operator-test-utils/d' lib/Cargo.toml && \
    git clone --depth 1 --branch "${REFERENCE_VALUES_REV}" https://github.com/trusted-execution-clusters/reference-values && \

```

```
    make crds-rs

ARG build_type=release

```

1. Replace the placeholder `v0.0.0` in `REFERENCE_VALUES_REV` with a real, trusted tag (or branch) from `trusted-execution-clusters/reference-values`. Using a tag is preferable for immutability.
2. If you decide to pin to a specific commit instead of a tag, drop `--depth 1` and `--branch` and use `git checkout <commit-sha>` after cloning; this will still be reproducible but with a slightly larger clone.
3. Optionally, you can add checksum or signature verification (e.g., verifying a signed tag) after cloning if your build environment supports that, to further reduce supply-chain risk.
</issue_to_address>

### Comment 3
<location path="Containerfile" line_range="69" />
<code_context>
+
+FROM quay.io/fedora/fedora:43 AS compute-pcrs
+COPY --from=builder /output/compute-pcrs /usr/bin
+COPY --from=builder /build/reference-values /reference-values
+
+FROM quay.io/fedora/fedora:43 AS register-server
</code_context>
<issue_to_address>
**suggestion (performance):** Copying the entire `reference-values` repo into the compute-pcrs image may bloat the runtime and include unnecessary content.

The `/build/reference-values` path in the final image likely contains the entire cloned repo (including `.git` and other non-runtime assets). If `compute-pcrs` only needs specific data or subdirectories, please adjust the Dockerfile to copy just those artifacts from the builder stage to minimize image size and avoid bundling unnecessary files into the runtime image.

Suggested implementation:

```
    cp /build/target/${build_type}/compute-pcrs /output/ && \
    cp /build/target/${build_type}/register-server /output/ && \
    cp /build/target/${build_type}/attestation-key-register /output/ && \
    mkdir -p /output/reference-values && \
    cp -r /build/reference-values/runtime-data /output/reference-values

```

```
FROM quay.io/fedora/fedora:43 AS compute-pcrs
COPY --from=builder /output/compute-pcrs /usr/bin
COPY --from=builder /output/reference-values /reference-values

```

- Replace `/build/reference-values/runtime-data` with the actual path (subdirectory or artifact files) that `compute-pcrs` needs at runtime (for example, a `data/` or `artifacts/` directory, or specific JSON/YAML files).
- Ensure that whatever path you choose is produced/available in the builder stage (e.g., populated by `git clone`, build scripts, or other tooling) so the `cp -r` command succeeds.
- If `compute-pcrs` only requires individual files instead of a directory, adjust the `cp -r` command to copy just those files into `/output/reference-values` and keep the `COPY --from=builder /output/reference-values /reference-values` line unchanged.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread Containerfile Outdated
Comment thread Containerfile Outdated
Comment thread Containerfile Outdated
@yairpod
yairpod force-pushed the build_optimizing branch from 964033f to 0b02453 Compare July 23, 2026 12:03

@Jakob-Naucke Jakob-Naucke left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

very neat with the .containerignore also

Comment thread Makefile Outdated
Comment thread Containerfile Outdated
@uril

uril commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
  1. I assume that having 4 separate Containerfiles, but with cache would result in a similar improvement (but needs to be benchmarked).
  2. Please make sure that make clean also cleans the cache.

Comment thread Containerfile
Comment thread Containerfile
@yairpod
yairpod force-pushed the build_optimizing branch 2 times, most recently from c5b2b35 to 108c2b0 Compare July 30, 2026 14:47
@yairpod yairpod closed this Aug 3, 2026
@yairpod

yairpod commented Aug 3, 2026

Copy link
Copy Markdown
Member Author
  1. I assume that having 4 separate Containerfiles, but with cache would result in a similar improvement (but needs to be benchmarked).
    I made the variation with 4 separate Containerfiles that have build caches, and benchmarked it (see results attached), as you can see, the build time for the first container is not significantly impacted but the next 3 lose much of the improvement.
    split_summary.txt
2. Please make sure that make clean also cleans the cache.

Added cache cleaning to "make clean" command.

@Jakob-Naucke

Copy link
Copy Markdown
Member

@yairpod is this PR closed intentionally?

@yairpod yairpod reopened this Aug 3, 2026
@yairpod

yairpod commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

@yairpod is this PR closed intentionally?

No, thanks for pointing this out

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The RUN step that parses Cargo.lock to derive the reference-values repo/commit looks quite brittle (multiple grep/sed assumptions on lockfile layout) — consider a more robust approach (e.g., cargo metadata, a fixed git URL/commit arg, or at least explicit error handling if the expected entries are not found).
  • Adding $(CONTAINER_CLI) builder prune --all --force to make clean is fairly destructive and may remove unrelated builder cache; it might be safer to scope this to the project (e.g., labels) or guard it behind an opt-in variable/target.
  • The unified cargo build step in the Containerfile bundles several concerns (build, copy, reference-values sync) into one long shell line, which makes it harder to maintain and debug; consider splitting this into smaller RUN blocks or a helper script to separate build, artifact copy, and reference-values preparation logic.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `RUN` step that parses `Cargo.lock` to derive the `reference-values` repo/commit looks quite brittle (multiple `grep`/`sed` assumptions on lockfile layout) — consider a more robust approach (e.g., `cargo metadata`, a fixed git URL/commit arg, or at least explicit error handling if the expected entries are not found).
- Adding `$(CONTAINER_CLI) builder prune --all --force` to `make clean` is fairly destructive and may remove unrelated builder cache; it might be safer to scope this to the project (e.g., labels) or guard it behind an opt-in variable/target.
- The unified `cargo build` step in the `Containerfile` bundles several concerns (build, copy, reference-values sync) into one long shell line, which makes it harder to maintain and debug; consider splitting this into smaller `RUN` blocks or a helper script to separate build, artifact copy, and reference-values preparation logic.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@openshift-ci

openshift-ci Bot commented Aug 3, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Jakob-Naucke, yairpod

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@Jakob-Naucke

Copy link
Copy Markdown
Member

ah yes, the verified signatures

@openshift-ci openshift-ci Bot removed the lgtm label Aug 3, 2026
@openshift-ci

openshift-ci Bot commented Aug 3, 2026

Copy link
Copy Markdown

New changes are detected. LGTM label has been removed.

Comment thread Makefile
cargo clean
rm -rf bin manifests $(CRD_YAML_PATH) $(CRD_RS_PATH)
rm -f trusted-cluster-gen config/rbac/role.yaml .crates.toml .crates2.json
$(CONTAINER_CLI) builder prune --all --force --filter label=project=trusted-cluster-operator

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.

should "builder" be replaced with -- image , system, volume, other ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

builder prune is different meaning the the others:
builder prune - Removes BuildKit build cache (intermediate layers)
image prune - Removes Dangling/unused images
system prune - Removes Everything (containers, images, networks, cache)
volume prune - Removes Unused volumes

So we need to clean the BuildKit build cache, but I just found that it does not clean the mount caches.
I will add the command "$(CONTAINER_CLI) builder prune --force --filter type=exec.cachemount" for that, note that filter and labels are not supported here! so we will have to clean all mount caches in the system, or none. I think that in this case cleaning all of them should be acceptable.

Comment thread Makefile

image: operator-image compute-pcrs-image reg-server-image attestation-key-register-image
image:
$(CONTAINER_CLI) build $(IMAGE_BUILD_OPTIONS) --target operator -t $(OPERATOR_IMAGE) -f Containerfile .

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.

What's the purpose of this change -- to always build all 4 containers ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To avoid performing identical steps in parallel with multithreaded make. If we want to keep the targets, one could also do

Suggested change
$(CONTAINER_CLI) build $(IMAGE_BUILD_OPTIONS) --target operator -t $(OPERATOR_IMAGE) -f Containerfile .
$(MAKE) operator-image
$(MAKE) compute-pcrs-image
$(MAKE) reg-server-image
$(MAKE) attestation-key-register-image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Also, the caches are behind a lock, so multithreading is sequential anyway.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

oh, but when on your previous revision 108c2b0, make -j4 image yields something like

[1/5] STEP 17/23: RUN make crds-rs
--> 1a27730f3d14
[1/3] STEP 16/23: RUN sed -i 's/members = .*/members = ["lib", "operator", "compute-pcrs", "register-server", "attestation-key-register"]/' Cargo.toml &&     sed -i '/\[dev-dependencies\]/,$d' operator/Cargo.toml &&     sed -i '/\[dev-dependencies\]/,$d' register-server/Cargo.toml &&     sed -i '/trusted-cluster-operator-test-utils/d' lib/Cargo.toml
--> 376893283ee7
[1/4] STEP 17/23: RUN make crds-rs
--> 33d731781ac3
[1/2] STEP 17/23: RUN make crds-rs
    Updating crates.io index
    Updating git repository `https://github.com/trusted-execution-clusters/compute-pcrs`
--> 4d4aca09ee8d
[1/3] STEP 17/23: RUN make crds-rs
    Updating crates.io index
    Updating git repository `https://github.com/trusted-execution-clusters/compute-pcrs`
    Updating crates.io index
    Updating git repository `https://github.com/trusted-execution-clusters/compute-pcrs`
    Updating git repository `https://github.com/latchset/clevis-pin-trustee`
    Updating git repository `https://github.com/latchset/clevis-pin-trustee`
    Updating crates.io index
    Updating git repository `https://github.com/trusted-execution-clusters/compute-pcrs`
    Updating git repository `https://github.com/latchset/clevis-pin-trustee`
    Updating git repository `https://github.com/latchset/clevis-pin-trustee`
 Downloading crates ...
 Downloading crates ...

which doesn't look so sequential to me?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm not sure, I read about the Lock and did not experiment with it.
But that seems to be parallelization of line 40 "RUN make crds-rs" and the first use of --mount=type=cache (where the lock should be) is line 43 (in revision 108c2b0) .
Also that is a huge waste of build effort as a sequential build would not run these stages 4 times, the layers would be pulled from the intermediate layer cache (not to be confused with the mount cache).

@yairpod yairpod Aug 4, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added mount caches to the make crds-rs stage, it shows improvement in build times when the libs are modified.

Benchmarks:
New is with make crds-rs caches, old is the PR without them 60dff635

approach build_type repetition cache_state edit_target total_time_s
new debug 1 cold none 402.5
new debug 2 warm lib 92.5
new debug 3 warm lib 93.1
new debug 2 warm operator 22.5
new debug 3 warm operator 23.3
old debug 1 cold none 404.0
old debug 2 warm lib 114.0
old debug 3 warm lib 119.4
old debug 2 warm operator 22.5
old debug 3 warm operator 23.3

Until now our builds have compiled the same files 4 times,
As the builds were done in separate containers and had no access to the compilation objects from the other containers or previous builds.
By Unifying the Build stage into one Container and interceding build caches we will prevent unnecessary work and achieve faster builds.

Signed-off-by: Yair Podemsky <ypodemsk@redhat.com>
Assisted-by: Opus:4.6

Testing additional cacheing
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.

4 participants