Skip to content

CI Workflows

Comprehensive guide to every GitHub Actions workflow included in the zircote/rust-template repository. Workflows are organized by purpose and annotated with trigger conditions, required secrets, and activation status.


All CI and release work is orchestrated through a single pipeline.yml that calls reusable workflows via workflow_call. This ensures CI passes before any release work begins and eliminates duplicate checks.

pipeline.yml (push/PR/tag/manual)
push/PR/tag
|
+---------+-----+-----+-------------+
| | | |
[ci] [coverage] [test-matrix]* [pin-check]
| (* PR only)
[docker]
(PR=build-only)
|
[docker-sign] (push/tags only — central signer)
|
[docker-verify] (fail-closed gate)
release.yml (tag push; dispatch = dry-run)
[meta] -> [build x5 + attest] -> [sbom + attest] -> [verify]** -> [release]*
[test] [audit] ----------------------------^
(* tags only; ** fail-closed BEFORE the release exists)
publish.yml (tag push; dispatch = dry-run)
checks -> Trusted Publishing (OIDC) -> registry .crate byte-compare -> attest
package-homebrew.yml
release.yml completion --workflow_run--> source formula -> <owner>/homebrew-tap

Pipeline Workflows (Orchestrator + Reusable)

Section titled “Pipeline Workflows (Orchestrator + Reusable)”
WorkflowFileCalled ByPurpose
Pipelinepipeline.ymlpush, PR, tag, manualOrchestrator — routes triggers, enforces dependencies
CI Checksci-checks.ymlpipeline.ymlfmt, clippy, test (3-OS), doc, deny, msrv, gate
Code Coverageci-coverage.ymlpipeline.ymlLCOV/HTML/JSON coverage, Codecov, PR comment
Test Matrixci-test-matrix.ymlpipeline.yml (PR only)12-combo matrix, integration tests, Miri
Dockerrelease-docker.ymlpipeline.ymlMulti-platform Docker build/push to GHCR; outputs the manifest digest
Releaserelease.ymltags, manual (dry-run)5 attested binaries + attested SBOM + fail-closed verify → GH Release
Publishpublish.ymltags, manual (dry-run)crates.io Trusted Publishing (OIDC) + registry .crate attestation
Homebrewpackage-homebrew.ymlworkflow_run (Release), release, manualSource formula in <owner>/homebrew-tap, metadata from Cargo.toml
Pin Checkzircote/.github » pin-check.ymlpipeline.ymlAsserts every uses: is pinned to a 40-char SHA
Sign and Attest Imagezircote/.github » sign-and-attest.ymlpipeline.yml (push/tags)Cosign signature, SLSA provenance, SBOM + vuln report as OCI referrers
Verify Image Attestationszircote/.github » verify-attestation.ymlpipeline.yml (push/tags)Fail-closed verification gate before release
WorkflowFileTriggerRequired SecretsStatus
Security Auditsecurity-audit.ymlschedule (daily), push, manualActive
Quality Gates (SAST/SCA/posture/IaC)quality-gates.ymlpush, PR, schedule (weekly), manualActive
Secrets Scansecrets-scan.ymlpush, PR, manualGITLEAKS_LICENSEActive
Benchmarkbenchmark.ymlpush, PR, manualActive
Benchmark Regressionbenchmark-regression.ymlPR, manualActive
Mutation Testingmutation-testing.ymlPR (src/tests paths), manualActive
Fuzz Testingfuzz-testing.ymlmanualOpt-in
Code Quality Metricscode-quality.ymlPR, manualActive
Spell Checkspell-check.ymlpush, PR, manualActive
Dependabot Auto-Mergedependabot-automerge.ymlPR (dependabot actor)Active
Stale Issue Managementstale.ymlmanualOpt-in
Contributor Recognitioncontributors.ymlmanualOpt-in
Template Inittemplate-init.ymlpush to main, manualActive
Nightly Buildsnightly.ymlmanualOpt-in
Deploy Documentationdocs-deploy.ymlpush to main (docs/site/CLAUDE.md/Cargo.toml paths), manualActive
ADR Validationadr-validation.ymlpush, PR (docs/adr paths), manualActive
ADR Vieweradr-viewer.ymlpush (docs/adr paths), manualActive
Docker Hub Multi-Registrydocker-hub.ymlmanualDOCKERHUB_USERNAME, DOCKERHUB_TOKENOpt-in
Copilot Setup Stepscopilot-setup-steps.ymlmanualActive

“Active” means the workflow has at least one automatic trigger (push, PR, schedule, release, or tag). “Opt-in” means only workflow_dispatch (manual) is enabled; automatic triggers are commented out and must be uncommented to activate.


What it does: Single entry point for all CI and release work. Routes triggers to reusable workflows with explicit needs: dependencies ensuring CI passes before any release work begins.

Trigger: Push to main/master, pull request to main/master, push tag v*.*.*, manual (with stage selector).

Concurrency: Cancels in-progress runs for branches/PRs; never cancels tag runs.

Manual dispatch stages: all, ci, docker. Stage docker also runs ci (a dependency), making the build → push → sign → verify chain exercisable without cutting a tag. Releases are NOT orchestrated by the pipeline — they run as independent tag-triggered workflows (release.yml, publish.yml, package-homebrew.yml).

Job dependency chain:

  • ci, coverage, test-matrix, pin-check — run in parallel (test-matrix is PR only)
  • docker — needs ci (PR = build-only via push: false)
  • docker-sign — needs docker (push/tags only); calls the centralized zircote/.github signer workflow, pinned by full commit SHA (SLSA Build L3 isolation — the signing identity is the central workflow, not this repository)
  • docker-verify — needs docker-sign; fail-closed verification of signature, provenance, and SBOM attestations

What it does: The primary quality gate. Runs formatting, linting, tests on three operating systems, documentation build, dependency license/advisory checks (cargo-deny), MSRV verification. A final all-checks-pass job gates merge readiness.

Inputs: rust-version (default: stable), msrv (default: 1.92).

Secrets: CODECOV_TOKEN (optional).

What it does: Generates detailed code coverage reports with cargo-llvm-cov in LCOV, HTML, and JSON formats. Uploads to Codecov, posts a summary as a PR comment, and checks against an 80% coverage threshold.

Secrets: CODECOV_TOKEN (optional).

What it does: Runs the full test suite across a matrix of operating systems (Ubuntu, macOS, Windows) and Rust toolchains (stable, beta, nightly, MSRV). Includes integration tests, Miri undefined-behavior detection, and a summary report.

Inputs: msrv (default: 1.92).


External publication is disabled in the template: publish = false in Cargo.toml gates the three external channels — crates.io publishing, the container image push, and Homebrew tap updates (each workflow reads it via cargo metadata at runtime). It does not gate the GitHub Release: a pushed tag always produces an attested GitHub Release (binaries + SBOM + source snapshot), because a release is a tag primitive, not an external publish. Downstream projects delete that single line to arm the three external channels.

All release workflows are var-driven: crate name, binary name, version, description, and license are resolved at runtime from cargo metadata; owner/repo come from the GitHub context. Instantiating the template requires editing only Cargo.toml — nothing in these files is renamed. They mirror the verified architecture of zircote/rlm-rs.

What it does: On a v*.*.* tag: resolves project metadata, builds five platform binaries ({bin}-{version}-{platform} naming; linux-amd64, linux-arm64 on native arm runners, macos-arm64, macos-amd64 via cross-target, windows-amd64) with SLSA build provenance attested at build time, runs test and cargo-audit gates (tags are untrusted input), generates and attests a CycloneDX SBOM bound to every binary, then fail-closed verifies every attestation before the GitHub Release is created. The tag-gated release job attaches binaries, the SBOM, and a checksums file, with auto-generated release notes. A tag publishes nothing unattested.

Triggers: tag push; workflow_dispatch from any branch is a dry-run (version suffixed -dev, publish job skipped).

Secrets: HOMEBREW_TAP_TOKEN (optional — PAT lets the release event propagate to downstream workflows; workflow_run is the fallback).

What it does: Pre-publish gauntlet (fmt, clippy, test, doc, deny, package, dry-run publish), then crates.io Trusted Publishing via OIDC — no long-lived registry token. After publish it downloads the .crate the registry actually serves, byte-compares it against the local package, and attests the registry bytes.

One-time setup per crate: crates.io → crate Settings → Trusted Publishing → this repo, workflow publish.yml, environment copilot.

Triggers: tag push; workflow_dispatch is a dry-run (publish and attest steps are tag-gated).

What it does: Generates a source formula (name, description, and license read from Cargo.toml at the released tag) and pushes it to {owner}/homebrew-tap (override with repo variable HOMEBREW_TAP_REPO). Triggered by workflow_run on Release completion because bot-authored release events do not trigger workflows; idempotent on repeated firings.

Triggers: workflow_run (Release), release published, manual dispatch with version and dry_run inputs.

Secrets: HOMEBREW_TAP_TOKEN (required for tap pushes).

What it does: Builds a multi-platform Docker image (linux/amd64, linux/arm64) and optionally pushes to GHCR. Uses GitHub Actions cache for layer caching. Tags follow semver and include latest for the default branch.

Inputs: push (boolean, default: false).

Outputs: image-digest — the pushed manifest digest (sha256:...), consumed by the docker-sign/docker-verify attestation chain.


What it does: Runs cargo audit against the RustSec advisory database to detect known vulnerabilities in dependencies.

Trigger: Daily schedule at 00:00 UTC, push when Cargo.toml or Cargo.lock change, manual.

What it does: Thin caller of the zircote/.github central reusable quality-gate workflows: SAST (CodeQL, Rust), SCA (OSV-Scanner + dependency review), supply-chain posture (OpenSSF Scorecard), and IaC/license (Trivy). Each gate normalizes on SARIF and surfaces in the repository Security tab; supersedes the former standalone codeql-analysis.yml.

Trigger: Push to main, pull request to main, weekly schedule, manual.

What it does: Scans the repository history for accidentally committed secrets using Gitleaks.

Trigger: Every push, every pull request, manual.

Secrets: GITLEAKS_LICENSE (optional).


What it does: Runs cargo bench --workspace and uploads Criterion results as artifacts.

Trigger: Push to main/master, pull request, manual.

What it does: Compares benchmark results against a cached baseline from the main branch. Posts a performance report as a PR comment.

Trigger: Pull request, manual.

What it does: Runs cargo-mutants to evaluate test suite effectiveness. Posts results as a PR comment.

Trigger: Pull request when source or test files change, manual.

What it does: Runs cargo-fuzz against all fuzz targets. Opens GitHub issues for crashes.

Trigger: Manual only (daily schedule commented out).

What it does: Collects code quality metrics including unsafe code analysis, binary size breakdown, and documentation coverage.

Trigger: Pull request, manual.

What it does: Checks spelling across all project files using crate-ci/typos.

Trigger: Push, pull request, manual.


What it does: Automatically enables auto-merge for Dependabot patch and minor version PRs.

Trigger: Pull request (dependabot actor only).

What it does: Marks issues and PRs as stale after inactivity.

Trigger: Manual only (daily schedule commented out).

What it does: Generates CONTRIBUTORS.md from git history.

Trigger: Manual only (monthly schedule commented out).

What it does: Automatically renames the project when a new repository is created from this template.

Trigger: Push to main, manual.

What it does: Builds with Rust nightly, creates rolling pre-release.

Trigger: Manual only (daily schedule commented out).


What it does: Builds API docs and optionally an mdBook guide. Deploys to GitHub Pages.

Trigger: Manual only.

What it does: Validates Architecture Decision Records using adrscope.

Trigger: Push/PR when ADR files change, manual.

What it does: Generates an HTML viewer for all ADRs.

Trigger: Push when ADR files change, manual.


What it does: Prepares the CI environment for GitHub Copilot coding agent sessions.

Trigger: Manual only.


Running a specific pipeline stage manually

Section titled “Running a specific pipeline stage manually”

Navigate to Actions > Pipeline > Run workflow and select a stage from the dropdown: all, ci, or docker. To dry-run the release chain without a tag, dispatch Release or Publish to crates.io instead — both tag-gate their publish-side steps.

All reusable workflows also support workflow_dispatch. Navigate to Actions > (workflow name) > Run workflow to trigger any reusable workflow independently.

  1. Open the workflow file in .github/workflows/.
  2. Uncomment the desired triggers under the on: key.
  3. Configure required secrets in Settings > Secrets and variables > Actions.
  4. Commit and push.

Option A: Replace automatic triggers with only workflow_dispatch:.

Option B: Delete the workflow YAML file.

Option C: Actions > (workflow) > … > Disable workflow in GitHub UI.


SecretUsed ByPurposeRequired
GITHUB_TOKENmultipleGitHub API accessBuilt-in
CODECOV_TOKENci-coverage.yml, ci-checks.ymlCoverage uploadOptional
GITLEAKS_LICENSEsecrets-scan.ymlGitleaks licenseOptional
DOCKERHUB_USERNAMEdocker-hub.ymlDocker Hub usernameIf using Docker Hub
DOCKERHUB_TOKENdocker-hub.ymlDocker Hub tokenIf using Docker Hub
HOMEBREW_TAP_TOKENpackage-homebrew.yml, release.ymlHomebrew tap write access; release-event propagationIf using Homebrew

crates.io publishing uses Trusted Publishing (OIDC) — there is no registry token secret. One-time setup on crates.io: crate Settings → Trusted Publishing → this repo, workflow publish.yml, environment copilot.

VariableUsed ByPurposeDefault
HOMEBREW_TAP_REPOpackage-homebrew.ymlTap repository name under the ownerhomebrew-tap

All other project specificity (crate name, binary name, version, description, license) is resolved from cargo metadata at runtime.

Configure secrets at Settings > Secrets and variables > Actions > New repository secret.