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.
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
- Confirm the repository is disposable and you can delete releases/tags there.
-
Confirm Actions is enabled and the workflow can request
contents: writefor the publish job. -
Do not add any PAT, registry password or cloud credential. The
workflow uses only
GITHUB_TOKEN. - Inspect existing tags/releases so the unique lab namespace cannot collide.
- 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
sha256sumequals 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-alland 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.
Knowledge check
Why does the checkpoint use a unique tag containing run ID and attempt?
It prevents collisions, makes cleanup exact, and remains safe even when immutable release tag names cannot be reused.
Which state proves the release asset bytes are the same as the validated build subject?
The matching SHA-256 across build output, downloaded transfer and published consumer download, reinforced by provenance/attestation.
Why is contents: write absent from the build
job?
The build should not have publication authority; only the guarded publish job needs to create tag/release state.
A consumer downloads the correct tag but checksum fails. Should they rerun the release workflow?
No. Preserve the mismatch evidence and inspect release asset/provenance/source identities first; a rerun could create new state and hide the original problem.
What is the Chapter 26 handoff?
Promote the already verified release/package/image digest through deployment authorization and target rollout without rebuilding it.
Official references and version notes
- About releases — GitHub Releases are based on Git tags and add release metadata/assets around a versioned source point.
- Managing releases — Current release creation, editing and deletion behavior.
- Immutable releases — Current tag/asset immutability and automatic release-attestation behavior.
- GHCR — Current GitHub Container Registry authentication, package linking and digest guidance.
- GitHub Packages permissions — Repository/granular package permissions and GitHub Actions access.
- GITHUB_TOKEN authentication — Least-privilege token use in workflows.
- gh release create — Current CLI release creation, --verify-tag and immutable-release handling.
- gh release verify — Verification of cryptographically signed immutable-release attestations.
- gh release verify-asset — Verify a local asset against the release attestation and digest.
- actions/attest v4.2.2 — Immutable attestation action revision referenced by optional provenance examples.
- actions/upload-artifact v7.0.1 — Pinned job-transfer action.
- actions/download-artifact v8.0.1 — Pinned job-transfer action.
- actions/checkout v7.0.1 — Pinned exact-source checkout action.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.