Documentation workflow (TorchIO-project/torchio)
The Documentation workflow from TorchIO-project/torchio, explained and optimized by Latchkey.
CI health: B - good
Point runs-on at Latchkey and get job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Documentation workflow from the TorchIO-project/torchio repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
# This workflow builds the documentation and either:
# - deploys versioned docs to the `gh-pages` branch with mike (on `v1` and
# `main`), or
# - uploads a Smokeshow preview and comments the URL on the PR (other
# branches), using a single "push" trigger so that secrets are available
# even for PRs from forks.
#
# Versioning uses the Material team's fork of mike, integrated with Zensical
# (https://zensical.org/docs/setup/versioning/):
# - `v1` -> version "<major.minor>" with aliases `stable` + `latest`
# - `main` (v2) -> version "<major.minor>" with alias `dev`
# `latest` is the default alias, so docs.torchio.org/ redirects to v1 while v2
# is a pre-release. Flip with `mike set-default` once v2 becomes stable.
#
# Both `v1` and `main` carry this workflow because Actions runs the version that
# lives on the branch that was pushed.
#
# One-time manual setup for production (cannot be done from CI):
# 1. Let `v1` deploy first so the `latest`/`stable`/default redirect exist
# before the site goes live.
# 2. Settings > Pages > Source = "Deploy from a branch" -> `gh-pages` / root.
# 3. Settings > Pages > Custom domain = `docs.torchio.org`. GitHub writes a
# CNAME file to `gh-pages`, which mike preserves on later deploys.
name: Documentation
on:
push:
branches: ['**'] # all branches, but not tags (avoids deploying on releases)
permissions:
contents: write
pull-requests: write # needed to post PR preview comments
statuses: write # needed by smokeshow to set commit status
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: false
env:
FORCE_COLOR: 1
jobs:
preview:
# Build + Smokeshow preview for branches/PRs that are not deployed versions.
if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/v1'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Install mise-en-place
uses: jdx/mise-action@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Restore TorchIO cached data
id: cache-torchio-data-restore
uses: actions/cache/restore@v6
with:
path: ~/.cache/torchio
key: ${{ runner.os }}-torchio-data
- name: Build docs
run: mise run docs:build
- name: Save TorchIO cached data
if: steps.cache-torchio-data-restore.outputs.cache-hit != 'true'
uses: actions/cache@v6
with:
path: ~/.cache/torchio
key: ${{ steps.cache-torchio-data-restore.outputs.cache-primary-key }}
- name: Upload docs to smokeshow
id: smokeshow
env:
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY || env.SMOKESHOW_AUTH_KEY }}
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Docs preview
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.sha }}
run: |
# Smokeshow limit: 50 MB total, 25 MB per file
# Remove large GIFs to stay under the limit
find ./site -type f -size +25M -delete
find ./site -type f -name '*.gif' -size +2M -delete
uvx smokeshow upload ./site 2>&1 | tee /tmp/smokeshow_output.txt
URL=$(grep -oE 'https://smokeshow[^ ]+' /tmp/smokeshow_output.txt | tail -1 | sed 's/,$//')
echo "preview_url=$URL" >> "$GITHUB_OUTPUT"
# sticky-pull-request-comment auto-detects the PR number only on
# pull_request events. Since this workflow triggers on push, we look up the
# PR number ourselves.
- name: Find PR number
if: steps.smokeshow.outputs.preview_url != ''
id: find-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "${{ github.ref_name }}" --json number --jq '.[0].number' 2>/dev/null || echo "")
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
- name: Comment PR with preview URL
if: steps.smokeshow.outputs.preview_url != '' && steps.find-pr.outputs.pr_number != ''
uses: marocchino/sticky-pull-request-comment@v3
with:
header: docs-preview
number: ${{ steps.find-pr.outputs.pr_number }}
message: |
## π Docs Preview
Preview of the documentation for this PR:
π **${{ steps.smokeshow.outputs.preview_url }}**
<sub>Built from ${{ github.sha }}</sub>
deploy:
# Deploy versioned docs to the gh-pages branch with mike (production).
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v1'
runs-on: ubuntu-latest
# Serialize deploys across branches so concurrent main/v1 runs don't race on
# the gh-pages branch.
concurrency:
group: docs-deploy
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install mise-en-place
uses: jdx/mise-action@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Restore TorchIO cached data
id: cache-torchio-data-restore
uses: actions/cache/restore@v6
with:
path: ~/.cache/torchio
key: ${{ runner.os }}-torchio-data
- name: Configure git and fetch gh-pages
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Make the existing versioned docs available locally so mike updates
# them instead of recreating the branch from scratch.
git fetch origin gh-pages --depth=1 || true
- name: Deploy versioned docs with mike
run: |
VERSION=$(grep -m1 -E '^version = ' pyproject.toml | sed -E 's/^version = "(.*)"/\1/')
MAJOR_MINOR=$(printf '%s' "$VERSION" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
echo "Deploying docs for $VERSION (mike version id: $MAJOR_MINOR)"
if [ "${{ github.ref }}" = "refs/heads/v1" ]; then
mise run docs:deploy -- --push "$MAJOR_MINOR" stable latest
mise run docs:set-default -- --push latest
else
mise run docs:deploy -- --push "$MAJOR_MINOR" dev
fi
- name: Save TorchIO cached data
if: steps.cache-torchio-data-restore.outputs.cache-hit != 'true'
uses: actions/cache@v6
with:
path: ~/.cache/torchio
key: ${{ steps.cache-torchio-data-restore.outputs.cache-primary-key }}
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
# This workflow builds the documentation and either: # - deploys versioned docs to the `gh-pages` branch with mike (on `v1` and # `main`), or # - uploads a Smokeshow preview and comments the URL on the PR (other # branches), using a single "push" trigger so that secrets are available # even for PRs from forks. # # Versioning uses the Material team's fork of mike, integrated with Zensical # (https://zensical.org/docs/setup/versioning/): # - `v1` -> version "<major.minor>" with aliases `stable` + `latest` # - `main` (v2) -> version "<major.minor>" with alias `dev` # `latest` is the default alias, so docs.torchio.org/ redirects to v1 while v2 # is a pre-release. Flip with `mike set-default` once v2 becomes stable. # # Both `v1` and `main` carry this workflow because Actions runs the version that # lives on the branch that was pushed. # # One-time manual setup for production (cannot be done from CI): # 1. Let `v1` deploy first so the `latest`/`stable`/default redirect exist # before the site goes live. # 2. Settings > Pages > Source = "Deploy from a branch" -> `gh-pages` / root. # 3. Settings > Pages > Custom domain = `docs.torchio.org`. GitHub writes a # CNAME file to `gh-pages`, which mike preserves on later deploys. name: Documentation on: push: branches: ['**'] # all branches, but not tags (avoids deploying on releases) permissions: contents: write pull-requests: write # needed to post PR preview comments statuses: write # needed by smokeshow to set commit status concurrency: group: docs-${{ github.ref }} cancel-in-progress: false env: FORCE_COLOR: 1 jobs: preview: timeout-minutes: 30 # Build + Smokeshow preview for branches/PRs that are not deployed versions. if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/v1' runs-on: latchkey-small steps: - name: Checkout repository uses: actions/checkout@v7 - name: Install mise-en-place uses: jdx/mise-action@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Restore TorchIO cached data id: cache-torchio-data-restore uses: actions/cache/restore@v6 with: path: ~/.cache/torchio key: ${{ runner.os }}-torchio-data - name: Build docs run: mise run docs:build - name: Save TorchIO cached data if: steps.cache-torchio-data-restore.outputs.cache-hit != 'true' uses: actions/cache@v6 with: path: ~/.cache/torchio key: ${{ steps.cache-torchio-data-restore.outputs.cache-primary-key }} - name: Upload docs to smokeshow id: smokeshow env: SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY || env.SMOKESHOW_AUTH_KEY }} SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Docs preview SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.sha }} run: | # Smokeshow limit: 50 MB total, 25 MB per file # Remove large GIFs to stay under the limit find ./site -type f -size +25M -delete find ./site -type f -name '*.gif' -size +2M -delete uvx smokeshow upload ./site 2>&1 | tee /tmp/smokeshow_output.txt URL=$(grep -oE 'https://smokeshow[^ ]+' /tmp/smokeshow_output.txt | tail -1 | sed 's/,$//') echo "preview_url=$URL" >> "$GITHUB_OUTPUT" # sticky-pull-request-comment auto-detects the PR number only on # pull_request events. Since this workflow triggers on push, we look up the # PR number ourselves. - name: Find PR number if: steps.smokeshow.outputs.preview_url != '' id: find-pr env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PR_NUMBER=$(gh pr list --head "${{ github.ref_name }}" --json number --jq '.[0].number' 2>/dev/null || echo "") echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - name: Comment PR with preview URL if: steps.smokeshow.outputs.preview_url != '' && steps.find-pr.outputs.pr_number != '' uses: marocchino/sticky-pull-request-comment@v3 with: header: docs-preview number: ${{ steps.find-pr.outputs.pr_number }} message: | ## π Docs Preview Preview of the documentation for this PR: π **${{ steps.smokeshow.outputs.preview_url }}** <sub>Built from ${{ github.sha }}</sub> deploy: timeout-minutes: 30 # Deploy versioned docs to the gh-pages branch with mike (production). if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v1' runs-on: latchkey-small # Serialize deploys across branches so concurrent main/v1 runs don't race on # the gh-pages branch. concurrency: group: docs-deploy cancel-in-progress: false steps: - name: Checkout repository uses: actions/checkout@v7 with: fetch-depth: 0 - name: Install mise-en-place uses: jdx/mise-action@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Restore TorchIO cached data id: cache-torchio-data-restore uses: actions/cache/restore@v6 with: path: ~/.cache/torchio key: ${{ runner.os }}-torchio-data - name: Configure git and fetch gh-pages run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" # Make the existing versioned docs available locally so mike updates # them instead of recreating the branch from scratch. git fetch origin gh-pages --depth=1 || true - name: Deploy versioned docs with mike run: | VERSION=$(grep -m1 -E '^version = ' pyproject.toml | sed -E 's/^version = "(.*)"/\1/') MAJOR_MINOR=$(printf '%s' "$VERSION" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/') echo "Deploying docs for $VERSION (mike version id: $MAJOR_MINOR)" if [ "${{ github.ref }}" = "refs/heads/v1" ]; then mise run docs:deploy -- --push "$MAJOR_MINOR" stable latest mise run docs:set-default -- --push latest else mise run docs:deploy -- --push "$MAJOR_MINOR" dev fi - name: Save TorchIO cached data if: steps.cache-torchio-data-restore.outputs.cache-hit != 'true' uses: actions/cache@v6 with: path: ~/.cache/torchio key: ${{ steps.cache-torchio-data-restore.outputs.cache-primary-key }}
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
2 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
This workflow runs 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.