Chapter 25Lesson 05~210 minutes

Checkpoint Lab — Release Automation, Tags, GitHub Releases, Packages, and Container Registries

Prove a build-once release contract by publishing a uniquely versioned prerelease from an exact commit, verifying bytes and provenance, then applying a safe cleanup or superseding strategy.

CheckpointPrereleaseEvidence packetVerificationCleanup

Learning objectives

  • Predict source, tag/release, permission and artifact-state changes before publication.
  • Create one uniquely versioned prerelease from an exact commit without rebuilding the validated subject.
  • Verify SHA-256 and provenance before/after publication and preserve the release object identities.
  • Demonstrate a safe superseding or cleanup strategy without rewriting release history.
  • Produce a compact evidence packet linking source revision, workflow, artifact, attestation and release state.

1. Checkpoint mission

Create a disposable public repository and publish exactly one tiny prerelease from the exact workflow commit. The built bytes must cross the job boundary unchanged, receive provenance, and be attached to the release. Then prove what was created and choose either exact cleanup or a superseding strategy.

Guardrail: this lab writes a Git tag and GitHub Release. Confirm the repository is disposable. Do not point it at a package registry, production project or customer repository.

2. Current assumptions — verified September 10, 2026

Component Pinned/recorded assumption
Runner ubuntu-24.04; record image/tool details from Set up job
Checkout actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 — v7.0.1
Artifact upload actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a — v7.0.1
Artifact download actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c — v8.0.1
Provenance actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 — v4.2.2; public repos supported on current GitHub plans
CLI GitHub CLI on hosted runner; record gh --version at run time
Release immutability Optional repository setting; if enabled, publish only after assets are final and use release verification commands
Packages/GHCR Not required; simulated locally to avoid persistent registry state

3. Repository/resource/credential preflight

  1. Confirm the repository is disposable and you can delete releases/tags there.
  2. Confirm Actions is enabled and the workflow can request contents: write for the publish job.
  3. Do not add any PAT, registry password or cloud credential. The workflow uses only GITHUB_TOKEN.
  4. Inspect existing tags/releases so the unique lab namespace cannot collide.
  5. If you enable release immutability, record that fact before the run because cleanup semantics change.
gh repo view --json nameWithOwner,visibility,defaultBranchRef
gh release list --limit 20
git ls-remote --tags origin 'refs/tags/v0.0.0-checkpoint.*'

4. Write predictions before dispatch

Prediction Expected observable state
Source Both jobs refer to the same exact event GITHUB_SHA; checkout HEAD equals it.
Artifact Build produces one SHA-256; downloaded release bytes reproduce the same digest.
Permissions Build cannot publish; publish job alone has contents/attestation write authority.
Tag/release Unique tag points to source SHA; new release ID exists and is prerelease, not latest.
Provenance Attestation subject digest equals the published asset digest.
Registry No external package/GHCR state changes in the mandatory checkpoint.

5. Exact checkpoint workflow

This workflow intentionally keeps transfer and publication separate. There is no rebuild in publish.

name: Release checkpoint
on:
  workflow_dispatch:

permissions: {}

jobs:
  build:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    outputs:
      sha256: ${{ steps.build.outputs.sha256 }}
      asset: ${{ steps.build.outputs.asset }}
      source: ${{ steps.build.outputs.source }}
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - id: build
        shell: bash
        run: |
          set -euo pipefail
          test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
          mkdir -p dist
          asset="checkpoint-${GITHUB_SHA::12}.txt"
          printf 'source=%s\nrelease-contract=v1\n' "$GITHUB_SHA" > "dist/$asset"
          d=$(sha256sum "dist/$asset" | awk '{print $1}')
          printf '%s  %s\n' "$d" "$asset" > dist/SHA256SUMS
          echo "sha256=$d" >> "$GITHUB_OUTPUT"
          echo "asset=$asset" >> "$GITHUB_OUTPUT"
          echo "source=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
        with:
          name: checkpoint-subject-${{ github.run_id }}-${{ github.run_attempt }}
          path: dist
          retention-days: 7

  publish:
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      contents: write
      id-token: write
      attestations: write
      artifact-metadata: write
    env:
      GH_TOKEN: ${{ github.token }}
      EXPECTED: ${{ needs.build.outputs.sha256 }}
      ASSET: ${{ needs.build.outputs.asset }}
      SOURCE: ${{ needs.build.outputs.source }}
      TAG: v0.0.0-checkpoint.${{ github.run_id }}.${{ github.run_attempt }}
    steps:
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          name: checkpoint-subject-${{ github.run_id }}-${{ github.run_attempt }}
          path: dist
      - id: verify
        shell: bash
        run: |
          set -euo pipefail
          actual=$(sha256sum "dist/$ASSET" | awk '{print $1}')
          test "$actual" = "$EXPECTED"
          echo "actual=$actual" >> "$GITHUB_OUTPUT"
      - id: attest
        uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6
        with:
          subject-path: dist/${{ env.ASSET }}
      - shell: bash
        run: |
          set -euo pipefail
          gh api -X POST "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/$TAG" -f sha="$SOURCE"
          gh release create "$TAG" "dist/$ASSET" "dist/SHA256SUMS"             --verify-tag --prerelease --latest=false             --title "Checkpoint $TAG"             --notes "source=$SOURCE sha256=$EXPECTED"
          gh api "repos/$GITHUB_REPOSITORY/releases/tags/$TAG"             --jq '{id,tag_name,target_commitish,draft,prerelease,published_at,assets:[.assets[]|{id,name,size,digest}]}'
          echo "attestation_id=${{ steps.attest.outputs.attestation-id }}"

6. Dispatch and preserve run identity

gh workflow run release-checkpoint.yml
gh run list --workflow release-checkpoint.yml --limit 5
# Record the run ID before any rerun:
gh run view RUN_ID --json databaseId,attempt,event,headSha,headBranch,status,conclusion,jobs

Record the first run ID and attempt even if it fails. Rerunning after cache, runner image or repository state changes can produce different observations.

7. Independent verification checklist

  • Source: headSha, GITHUB_SHA, tag target and build evidence agree.
  • Artifact: local/downloaded sha256sum equals the build output and release note value.
  • Release: API returns one release ID, prerelease: true, draft: false, and expected asset names.
  • Permissions: workflow source shows no write-all and no package permission.
  • Provenance: attestation ID is recorded and verification checks expected repository identity plus subject digest.
  • External state: no GHCR/package/deployment target changed.

8. Verify the published asset as a consumer

tag='v0.0.0-checkpoint.RUN_ID.ATTEMPT'
mkdir -p verify && cd verify
gh release download "$tag" --repo OWNER/REPO
sha256sum -c SHA256SUMS
gh attestation verify checkpoint-*.txt --repo OWNER/REPO

If immutable releases are enabled, additionally use gh release verify "$tag" and gh release verify-asset "$tag" checkpoint-*.txt to validate GitHub’s release attestation. The two attestation paths answer related but distinct questions: artifact provenance versus integrity of the GitHub Release record.

9. Negative verification without rewriting the release

cp checkpoint-*.txt tampered.txt
printf 'tampered\n' >> tampered.txt
sha256sum tampered.txt
# Compare with SHA256SUMS; it must differ.

Do not upload tampered.txt. The negative test is local and proves that the recorded digest rejects modified bytes without mutating the release.

10. Evidence packet

Field Capture
Event/run event, run ID, attempt, workflow path/revision, exact source SHA
Action manifest full commit SHAs for checkout/upload/download/attest
Runner/tool ubuntu-24.04, runner image metadata, gh --version
Release identity version/tag, tag target SHA, release database ID, prerelease/latest/draft state
Artifact identity asset filename/ID/size, SHA-256 and any API asset digest
Provenance attestation ID/URL, subject digest, repository/workflow identity checked by verifier
Permissions build read-only; publish contents + attestation rights; no package/cloud secret
Recovery cleanup/superseding decision plus immutable-release limitation if enabled

11. Safe cleanup or superseding strategy

Choose one path and document it. For an ordinary disposable prerelease, after evidence capture run gh release delete "$TAG" --cleanup-tag --yes. If immutable releases are enabled, prefer leaving the historical release or deleting it only if the lab policy allows; the tag name cannot be reused afterward. A production defect should normally be superseded with a new version while consumers roll back to a known-good digest.

12. Faithful package simulation

If package publishing is unavailable or intentionally avoided, copy the final checkpoint asset into a local versioned directory, fail on overwrite, record SHA-256, and create a separate mutable latest pointer. This preserves the semantic difference between immutable version content and mutable discovery aliases without requiring billing or external registry cleanup.

13. What Chapter 25 adds — and the bridge to Chapter 26

This chapter extends the secure GitHub Actions model from “this revision passed CI” to “these exact verified bytes were intentionally published under this version and distribution record.” Chapter 26 will carry that same verified digest through protected environments and provider-specific deployment mechanisms instead of rebuilding during delivery.

14. Checkpoint summary

You have linked source SHA, workflow revision, artifact digest, provenance, tag and release ID into one auditable chain, then proved tampering fails and recovery does not require rewriting the release identity.

Next lesson

Continuous Delivery to AWS, Azure, Google Cloud, and Kubernetes: Core Concepts and Mental Model

Continue with the next lesson to build on the current concepts, evidence, security boundaries, and operational practices.

Knowledge check

Why does the checkpoint use a unique tag containing run ID and attempt?

Which state proves the release asset bytes are the same as the validated build subject?

Why is contents: write absent from the build job?

A consumer downloads the correct tag but checksum fails. Should they rerun the release workflow?

What is the Chapter 26 handoff?

Official references and version notes

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.