Rust CI/CD Workflows
Generated by rust-ci-library, which is composed by all three Rust service archetypes:
rust-rest-service-archetyperust-grpc-service-archetyperust-graphql-service-archetype
The protocol has no effect on the pipeline - all three render the same two workflow files.
Build Workflow
.github/workflows/build.yaml, shown as rendered for a project named billing-service:
name: Build
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: write
id-token: write
env:
IMAGE_NAME: billing-service
APPLICATION_NAME: billing-service
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: p6m-actions/rust-setup@v1
- name: Install protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- name: Cut Patch Version
if: github.ref_name == 'main'
id: cut-patch
uses: p6m-actions/rust-cut-tag@v1
with:
version-level: patch
- name: Build
uses: p6m-actions/rust-build@v1
# ... shared container build, release and dispatch steps
Step Chain
| # | Step | Scope | Runs on |
|---|---|---|---|
| 1 | Checkout | Shared | Always |
| 2 | Rust setup | Rust | Always |
| 3 | Install protoc | Rust only | Always |
| 4 | Cut Patch Version | Rust | main |
| 5 | Build | Rust | Always |
| 6 | Login to Artifactory Container Registry | Shared | main |
| 7 | Set up Docker Buildx | Shared | Always |
| 8 | Build and Publish Docker Image | Shared | Always |
| 9 | Make Artifacts | Shared | main |
| 10 | Create Github release | Shared | main |
| 11 | Update Application Manifest | Shared | main |
Rust is the only language that installs a system package before building.
Language-Specific Steps
Rust Setup
- uses: p6m-actions/rust-setup@v1
p6m-actions/rust-setup installs a Rust toolchain and restores the cargo registry and target/ caches.
| Input | Default | Passed by the workflow |
|---|---|---|
toolchain | stable | (default) |
components | (empty) | (default) |
targets | (empty) | (default) |
cache | true | (default) - cargo registry and build caching |
cache-key-suffix | (empty) | (default) |
install-dependencies | false | (default) - no separate cargo fetch |
working-directory | . | (default) |
| Output | Description |
|---|---|
rustc-version | The installed rustc version |
cargo-version | The installed cargo version |
cache-hit | Whether the cargo cache was restored |
clippy and rustfmt are not requested explicitlyThe build step runs cargo clippy and cargo fmt by default, which works because the GitHub-hosted runner image ships both with its preinstalled stable toolchain. If you pin toolchain to a specific version, add components: clippy, rustfmt so they are installed for that toolchain.
Install protoc
- name: Install protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
| Inputs | None |
| Outputs | protoc on PATH |
prost-build and tonic-build shell out to protoc at compile time, and GitHub's ubuntu-latest runners do not ship it. Without this step, the first CI run of a freshly generated gRPC service fails during cargo build.
The step runs unconditionally in all three Rust archetypes, including REST and GraphQL where the build may not need it. That is deliberate: one workflow file serves all three protocols, and a few seconds of apt is cheaper than a per-protocol divergence.
Cut Patch Version
- name: Cut Patch Version
if: github.ref_name == 'main'
id: cut-patch
uses: p6m-actions/rust-cut-tag@v1
with:
version-level: patch
p6m-actions/rust-cut-tag uses cargo-release to bump Cargo.toml, commit, and tag.
| Input | Required | Value |
|---|---|---|
version-level | No | patch in build.yaml; the dispatch input in cut-tag.yaml |
working-directory | No | . (default) |
commit-changes | No | true (default) |
commit-message | No | Bump version to {version} [skip ci] (default) |
skip-push | No | false (default) |
workspace | No | true (default) - applies to all workspace members |
| Output | Description |
|---|---|
version | The new version, e.g. 1.4.2 |
tag | The created git tag |
The workspace default of true matters here. The Rust archetypes render a Cargo workspace whose members vary by protocol - *_bin, *_core and *_persistence in all three, plus *_client and *_server for gRPC and *_schema for GraphQL, alongside an xtask crate - and all members are bumped together.
The action runs p6m-actions/token-exchange@v2 first for App-token authentication and the [skip ci] commit hook, then installs cargo-release via baptiste0928/cargo-install@v3.
Build
- name: Build
uses: p6m-actions/rust-build@v1
p6m-actions/rust-build runs format check, lint, test, and build. No inputs are overridden, so all defaults apply:
| Input | Default | What runs |
|---|---|---|
run-format-check | true | cargo fmt -- --check |
run-lint | true | cargo clippy -- -D warnings |
run-test | true | cargo test |
run-build | true | cargo build --release |
workspace | false | Not passed as --workspace |
all-features / no-default-features / features | off / off / empty | Default feature set |
archive-coverage | false | target/coverage is not uploaded |
| Output | Description |
|---|---|
build-status | success or failure |
Rust has the strictest defaults of the six languages. Both the formatting check and clippy -D warnings are hard gates on every branch: a stray unformatted line or a single clippy warning fails the build. Run cargo fmt and cargo clippy -- -D warnings locally before pushing.
Cut Tag Workflow
.github/workflows/cut-tag.yaml:
name: Cut Tag
on:
workflow_dispatch:
inputs:
version-level:
description: "Version bump level"
required: true
default: "patch"
type: choice
options:
- patch
- minor
- major
permissions:
id-token: write
contents: write
jobs:
cut-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- uses: p6m-actions/rust-setup@v1
- uses: p6m-actions/rust-cut-tag@v1
with:
version-level: ${{ inputs.version-level }}
| Input | version-level - patch, minor, or major |
| Outputs | A [skip ci] version bump commit across the workspace and a git tag, both pushed |
Version File
Cargo.toml is the source of truth. In the workspace layout the version lives in the workspace root and members inherit it:
[workspace.package]
version = "1.4.2"
Related
- CI/CD Workflows - the shared steps and the CD handoff
- Rust Actions - the wider Cargo action catalog
- Rust Setup Guide - local development environment