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:
- Use a StageX pallet as your build environment
- Build a static binary with hermetic (network-isolated) compilation
- Package into a scratch image with zero unnecessary dependencies
- 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_EPOCHto 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
- Understanding Full-Source Bootstrapping — How that 3 MB binary traces back to a 190-byte seed
- Building a Real Rust Application — Production patterns with multi-stage builds
- Verifying Multi-Signature Attestations — Checking GPG signatures on StageX images