← Lab
VJML / LAB
2026.10 ACTIVE

From an amd64 pipeline to multi-architecture CI

Architectures / Infrastructure

CRUX Portbot started around an amd64 Live pipeline able to discover port updates, prepare candidates, build them, sign them and generate publication proposals.

The next problem is considerably more interesting: bringing that same model to multiple architectures without turning it into several independent pipelines and without losing the guarantees that already exist between its different stages.

The starting point

Portbot is not designed as a sequence of scripts modifying the same working tree.

Its stages are separated by explicit boundaries. A prepared candidate is serialized before reaching the build; the build result crosses another boundary; signing is isolated from the process that builds the port; and publication has its own credentials and validation.

In simplified form:

discovery
    ↓
selected candidates
    ↓
prepare
    ↓
candidate handoff
    ↓
build
    ↓
build-output handoff
    ↓
signature preparation
    ↓
isolated signer
    ↓
finalizer
    ↓
publication handoff
    ↓
publisher

The arrows matter: they represent serialized and validated state, not a mutable working tree left behind for the next stage.

The multi-architecture problem

Adding architectures might look like a matter of running the build several times.

It is not.

Each architecture needs its own execution environment, builder and port collections, while the general behaviour of the pipeline should remain the same.

The current implementation is evolving towards a common matrix:

                    Portbot Live
                         │
                 reusable worker
                         │
        ┌────────────────┼────────────────┬────────────────┐
        │                │                │                │
     x86_64            armhf            arm64           riscv64
        │                │                │                │
      CRUX            CRUX-ARM         CRUX-ARM        CRUX-RISCV

The goal is not to maintain four Portbots.

It is to maintain one execution model capable of introducing the necessary differences at the appropriate boundaries.

One worker, several environments

Each matrix entry selects the runner, builder image and collections corresponding to its architecture.

The rest of the workflow is delegated to the same reusable worker.

This allows the physical architecture to change without duplicating the logic that defines how a candidate is discovered, prepared, built, validated, signed or published.

It also forces us to identify which parts of the system are genuinely common and which belong to a particular architecture.

That boundary is one of the things we are exploring now.

When a port needs to diverge

Not every architecture-specific port evolves independently.

Part of the work involves maintaining overlays that introduce the required differences over existing collections without turning them into completely separate copies.

Portbot can compare the state of those overlays with the repository acting as their reference.

reference port
      │
      │ version / release
      ▼
architecture overlay
      │
      ├── synchronized ──────── no candidate
      │
      └── different ─────────── overlay candidate

When a relevant difference is detected, it can be normalized as an overlay candidate and enter the preparation and validation process.

This turns the multi-architecture problem into something more than deciding where to build.

We also need to decide where architectural differences live and how they propagate without losing their context.

Preserving the boundaries

The transition to multi-architecture has an important constraint: it should not weaken the existing trust model.

Some of its properties are deliberately strict:

  • candidates must be reconstructable from serialized state;
  • prepare and build do not receive the production signing key;
  • the signer has no network and no repository checkout;
  • the publication token exists only in the publisher;
  • important stages validate data before importing it;
  • a candidate failure does not automatically mean a pipeline failure.

The objective is therefore not:

amd64 pipeline × 4

but something closer to:

        existing pipeline invariants
                    │
                    ▼
           architecture-aware
               execution
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
      ARM        RISC-V       x86_64

The architecture may change. The guarantees should not change accidentally.

Where we are now

Live execution already has a matrix for x86_64, armhf, arm64 and riscv64 using a common worker.

Overlay support is also entering that same model: detecting differences, representing them as candidates and preparing them are part of the current work.

The transition is therefore not finished.

That is precisely why it belongs in the Lab.

We are not documenting a finished architecture. We are observing what happens when a pipeline designed around one architecture starts having to represent several.

What we are trying to prove

The question is not whether CRUX can build ports on ARM or RISC-V. We already know it can.

The question is whether a single automation system can understand those architectures, preserve their differences and maintain the same trust boundaries throughout the process.

If it works, multi-architecture will stop being a collection of exceptions around the CI.

It will become a property of the pipeline itself.