A comprehensive demo of GitHub's Linked Artifacts feature β showing how to track what's deployed in each environment and enforce promotion ordering.
Linked Artifacts is a GitHub Enterprise Cloud feature that provides an org-wide view of software artifacts built with GitHub Actions. It tracks:
- Storage records β what was built, where it's stored, and its provenance attestation
- Deployment records β which environments an artifact is deployed to and its runtime risks
Find it at: Organization β Packages tab β Linked artifacts (left sidebar)
| Deployments Dashboard | Linked Artifacts | |
|---|---|---|
| Scope | Per-repo | Org-wide |
| Tracks | Commits/refs | Artifacts (with cryptographic digest) |
| Security | No integration | Feeds into code scanning & Dependabot alert prioritization |
| Provenance | None | Signed SLSA attestations |
| Runtime risks | No | Yes (internet-exposed, sensitive data) |
ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ
β β β β β β β β β β
β Build βββββΆβ Dev βββββΆβ QA βββββΆβ Staging βββββΆβ Prod β
β β β β β β β β β β
ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ
β β β β β
β β β β β
βΌ βΌ βΌ βΌ βΌ
Push to Register Verify Dev Verify QA Verify Staging
GHCR + deployment deployment deployment deployment
Attest record record exists record exists record exists
(auto-creates (Dev) β Register QA β Register β Register
storage Staging Production
record)
Triggered on push to main. Builds a Docker image, pushes to GHCR, attests provenance, then deploys sequentially through all 4 environments. Each promotion step:
- Verifies the artifact was deployed to the prior environment (via Linked Artifacts API)
- Registers a deployment record after successful deployment
Same promotion flow, but with a ZIP file instead of a container. This demonstrates that linked artifacts works with any hashable build output. Key differences:
- Digest is computed by hashing the ZIP file (
sha256sum) - Attestation uses
subject-pathinstead ofsubject-name/subject-digest - Storage record is registered manually via REST API (no auto-creation for files)
- Artifact is stored as a GitHub Actions artifact, not in a registry
Manually triggered. Builds a new image and tries to deploy it directly to a higher environment (e.g., Staging), skipping the lower ones. The verification gate blocks this because no prior deployment records exist for the new image's digest.
This proves the enforcement isn't just needs: job ordering β it's the real API check.
Container (Docker): The build job pushes a Docker image to GHCR and generates a signed provenance attestation using actions/attest. With push-to-registry: true and artifact-metadata: write permission, this automatically creates a storage record on the linked artifacts page.
.NET (File): The build job publishes the app, zips it, computes a sha256 hash, attests using subject-path, and manually registers a storage record via REST API. This shows linked artifacts works without a container registry.
| Container (Docker) | File (.NET ZIP) | |
|---|---|---|
| Digest source | Registry provides it automatically | Hash the file with sha256sum |
| Attestation | subject-name + subject-digest + push-to-registry: true |
subject-path |
| Storage record | Auto-created by actions/attest |
Manual POST to REST API |
| Artifact storage | GHCR | GitHub Actions artifact |
After each deployment, the workflow calls the artifact metadata REST API:
gh api -X POST \
"orgs/{org}/artifacts/metadata/deployment-record" \
-f name="linked-artifacts-demo" \
-f digest="sha256:abc..." \
-f status="deployed" \
-f logical_environment="production" \
-f deployment_name="prod-deploy-42" \
-f github_repository="org/repo"Before each promotion, the verify-deployment.sh script queries the API:
GET /orgs/{org}/artifacts/{digest}/metadata/deployment-recordsIt checks if a deployment record exists for the required prior environment. If not, the job fails and promotion is blocked.
| GitHub Environment | logical_environment |
Requires Prior |
|---|---|---|
| Dev | development |
β (first) |
| QA | testing |
development |
| Staging | staging |
testing |
| Production | production |
staging |
- GitHub Enterprise Cloud organization
- Repository in the organization with Actions enabled
artifact-metadata: writepermission available (GHEC feature)
Create the 4 environments for the repository. You can do this in Settings β Environments or via the API:
# Create all 4 environments
for env in Dev QA Staging Production; do
gh api -X PUT \
"repos/{owner}/{repo}/environments/${env}" \
--silent
done
# Optionally add a wait timer to Production
gh api -X PUT \
"repos/{owner}/{repo}/environments/Production" \
-f "wait_timer=1" \
--silentgit add -A
git commit -m "Add linked artifacts demo"
git push origin mainThe build-and-deploy.yml workflow will trigger automatically.
After the pipeline completes:
- Go to your Organization page
- Click the Packages tab
- Click Linked artifacts in the left sidebar
- Find
linked-artifacts-demoβ you'll see:- Storage record with provenance attestation
- Deployment records for Dev, QA, Staging, and Production
- Go to Actions β "Hotfix: Skip Environment (Demo)"
- Click Run workflow
- Select Staging or Production as the target
- Watch the verification gate fail because the new image was never deployed to the lower environments
βββ .github/
β βββ scripts/
β β βββ verify-deployment.sh # Reusable verification gate
β βββ workflows/
β βββ build-and-deploy.yml # Docker container pipeline
β βββ build-and-deploy-dotnet.yml # .NET file artifact pipeline
β βββ hotfix-skip-env.yml # Negative test (skip environments)
βββ src/
β βββ index.js # Simple Express.js app (Docker demo)
βββ dotnet-app/ # .NET Web API app (file artifact demo)
β βββ Program.cs
β βββ dotnet-app.csproj
β βββ ...
βββ Dockerfile # Docker build for Node.js app
βββ package.json
βββ README.md
| Endpoint | Method | Purpose |
|---|---|---|
orgs/{org}/artifacts/metadata/storage-record |
POST | Register where an artifact is stored |
orgs/{org}/artifacts/metadata/deployment-record |
POST | Register a deployment to an environment |
orgs/{org}/artifacts/{digest}/metadata/deployment-records |
GET | List deployment records for an artifact |
orgs/{org}/artifacts/{digest}/metadata/storage-records |
GET | List storage records for an artifact |