Skip to content

Quick Start: Your First Reproducible Build

Target audience: Developers new to StageX Time to complete: ~10 minutes Goal: Build a simple Rust binary with StageX and verify it builds reproducibly.

Why This Tutorial?

By the end of this tutorial, you'll understand the core StageX workflow:

  1. Use a StageX pallet as your build environment
  2. Build a static binary with hermetic (network-isolated) compilation
  3. Package into a scratch image with zero unnecessary dependencies
  4. Verify reproducibility by rebuilding and comparing hashes

This is the same pattern used for all StageX packages — you're learning the foundation.


Prerequisites

  • Podman installed (or Docker — set ENGINE=docker)
  • See System Requirements for hardware and OS compatibility
  • Basic familiarity with the command line

Step 1: Create the Project

Create a minimal Rust application:

mkdir hello-stagex && cd hello-stagex

# Create Cargo.toml
cat > Cargo.toml << 'EOF'
[package]
name = "hello-stagex"
version = "0.1.0"
edition = "2021"
EOF

# Create the source
mkdir src
cat > src/main.rs << 'EOF'
fn main() {
    println!("Hello from StageX!");
}
EOF

Step 2: Write the Containerfile

StageX images are OCI-native — you use standard Containerfile syntax (identical to Dockerfile). Create a file called Containerfile:

# Stage 1: Build
FROM docker.io/stagex/pallet-rust@sha256:2fbe7b164dd92edb9c1096152f6d27592d8a69b1b8eb2fc907b5fadea7d11668 AS build
WORKDIR /app
COPY Cargo.toml ./
COPY src/ ./src/

# --network=none ensures hermetic builds
# RUSTFLAGS enables static linking for scratch deployment
RUN cargo generate-lockfile && \
    RUSTFLAGS="-C target-feature=+crt-static" \
    cargo build \
        --frozen \
        --release \
        --target x86_64-unknown-linux-musl \
        --bin hello-stagex && \
    cp target/x86_64-unknown-linux-musl/release/hello-stagex /hello-stagex

# Stage 2: Minimal runtime — FROM scratch
FROM scratch
COPY --from=build /hello-stagex /hello-stagex
ENTRYPOINT ["/hello-stagex"]

What's happening here?

Element Purpose
FROM stagex/pallet-rust@sha256:... Pins to an exact, signed StageX build image — never "latest" in production
WORKDIR /app Sets the working directory inside the build container
--network=none Hermetic build — no network access during compilation. All dependencies must be present in the image
RUSTFLAGS="-C target-feature=+crt-static" Produces a fully static binary — no runtime library dependencies
--target x86_64-unknown-linux-musl Targets musl libc (StageX's default), producing a portable binary
FROM scratch Starts from empty — only what you explicitly copy ends up in the final image
COPY --from=build Copies only the built binary, leaving build tools and caches behind

Why pin by digest? Pinning by @sha256: (not by tag like :latest) ensures you always get the exact same build environment. This is the foundation of reproducibility. The digest is cryptographically verified by the container runtime.


Step 3: Build the Image

podman build -t hello-stagex:first-build .

You'll see BuildKit-style output:

[1/2] STEP 1/5: FROM docker.io/stagex/pallet-rust@sha256:2fbe7...
[1/2] STEP 5/5: RUN cargo build ...
   Compiling hello-stagex v0.1.0 (/app)
    Finished `release` profile in 0.20s
[2/2] COMMIT hello-stagex:first-build
Successfully tagged localhost/hello-stagex:first-build

Step 4: Run It

podman run --rm hello-stagex:first-build

Output:

Hello from StageX!

Your binary runs from a FROM scratch image — the final image contains only the 3 MB binary. Nothing else. No shell, no package manager, no libraries.

Check the image size:

podman images hello-stagex:first-build

You'll see something like 3.28 MB — that's the binary alone.


Step 5: Verify Reproducibility

This is where StageX proves its value. Let's rebuild and confirm the images are bit-for-bit identical.

Record the first digest

podman inspect hello-stagex:first-build --format '{{.Digest}}'

Save this output somewhere — this is your reference digest.

Rebuild with a clean cache

podman build --no-cache -t hello-stagex:second-build .

Compare digests

echo "First:  $(podman inspect hello-stagex:first-build --format '{{.Digest}}')"
echo "Second: $(podman inspect hello-stagex:second-build --format '{{.Digest}}')"

If both digests are identical, you've just verified a reproducible build. This means:

  • Two different people building at different times get the exact same image
  • Anyone can verify your build matches the official release
  • A compromised build server would be detected by a digest mismatch

If the digests differ, don't worry — this is common when learning. Check for: - Timestamps in the binary (Rust embeds compile time by default — StageX sets SOURCE_DATE_EPOCH to avoid this) - Different compiler versions (the pinned digest guarantees the same toolchain) - Network-dependent behavior (hermetic builds prevent this)


Step 6: Inspect the Image Contents

podman run --rm --entrypoint="" hello-stagex:first-build ls -la /hello-stagex

You'll see:

-rwxr-xr-x    1 root     root       3288704 Jan  1  1970 /hello-stagex

Notice the timestamp: Jan 1 1970 (epoch 0). StageX sets SOURCE_DATE_EPOCH=1 for all builds, ensuring deterministic timestamps. Without this, every build would have a different timestamp, breaking reproducibility.


What You've Learned

Concept How StageX Handles It
Build environment Pinned, signed OCI image with @sha256: digest
Dependencies Pre-installed in the pallet image — no network during build
Linking Static binary with +crt-static — no runtime deps
Runtime image FROM scratch — minimal attack surface
Reproducibility SOURCE_DATE_EPOCH, pinned toolchain, hermetic build
Verification Compare digests across independent builds

Next Steps