Chapter 21Lesson 01~175 minutes

GitHub Packages, Container Registry, Package Permissions, Provenance, and Distribution: Concepts, Architecture, and Mental Model

Chapter 20 narrowed workflow identity and credentials. Chapter 21 follows the output of those workflows beyond one run: how GitHub Packages and the Container registry name, authorize, version, distribute, and retire software artifacts without confusing a mutable label with immutable identity or package hosting with provenance.

GitHub PackagesGHCRPackage permissionsDigest identityProvenance

Learning objectives

  • Distinguish a package/version from an Actions artifact and a repository Release asset.
  • Map the GitHub Packages registries to granular or repository-scoped permission models.
  • Explain GITHUB_TOKEN, package access roles, cross-repository Actions access, and non-Actions registry authentication.
  • Separate mutable tags/versions from immutable container digest identity and consumer verification.
  • Explain why repository/package hosting is not itself provenance and where artifact attestations fit.

Availability. The mandatory path targets GitHub.com, GitHub Free, a disposable public personal repository, and GitHub Container Registry (GHCR). Public Packages usage is free; GitHub currently also states that Container registry image storage and bandwidth are free. Private/internal distribution, enterprise namespaces, and organization policy are taught as optional variants rather than requirements.

1. The practical problem: a successful build is not yet a governed distribution

Chapter 17 produced run-scoped artifacts and Chapter 19 promoted known bytes through environments. Production consumers, however, need a durable distribution address with version history, access control, and lifecycle. If a deployment says “pull latest,” the package exists, but its identity is ambiguous. If a private package is published correctly but the consuming repository has no package access, authentication succeeds and authorization still fails. If an image is on GitHub, that proves where it is hosted—not how it was built.

The operating model is therefore: namespace → package → version/tag → immutable identifier → permissions → provenance evidence → lifecycle policy. Each layer answers a different question.

2. Four artifact surfaces that solve different lifecycle problems

Surface Primary lifetime Consumer model Identity Use it when
Actions artifact Workflow/run evidence Workflow jobs and operators Artifact ID + digest/run identity Passing files between jobs, test evidence, temporary CI output
Release asset Repository release Humans/tools fetching a release Release/tag + asset bytes/digest Distributing a finite file attached to a release
GitHub package Package/version lifecycle Native package manager or registry client Package namespace + version metadata Dependencies and versioned distribution
Container package in GHCR Registry lifecycle Docker/OCI clients Tag plus immutable manifest digest Distributing Docker/OCI images

Moving a ZIP from an Actions run into a Package registry does not make it the same object. Retention, permissions, download APIs, namespace ownership, and consumer tooling all change. Chapter 11 covered release assets; Chapter 17 covered workflow artifacts. This chapter focuses on the package/registry boundary.

3. Supported registries and their permission models

Registry/ecosystem Typical client Package scope model Important implication
Container registry Docker / OCI tooling Granular user/organization scope; may link to repository Visibility/access can be managed separately unless inherited
npm npm Granular user/organization scope Package can have package-level access independent of repository
NuGet dotnet / nuget Granular user/organization scope Package-level read/write/admin roles available
RubyGems gem Granular user/organization scope Package-level access available
Apache Maven mvn Repository-scoped only Package inherits repository permissions/visibility
Gradle gradle Repository-scoped only Package inherits repository permissions/visibility

“Linked to a repository” and “owned by a repository” are not synonyms. Granular registries scope the package to a personal account or organization; linking supplies source context and can enable inherited access. Maven and Gradle remain repository-scoped, so their authorization model is structurally different.

4. Package objects, container versions, tags, and digests

Concept / workflow diagram
              flowchart TD
                R["Repository + workflow"] -->|packages: write / GITHUB_TOKEN| P["GHCR package namespace"]
                P --> V1["Version manifest digest D1"]
                P --> V2["Version manifest digest D2"]
                T1["tag 1.0.0"] --> V1
                TL["tag latest"] --> V2
                C["Consumer"] -->|pull by digest| V1
                A["Attestation, if generated"] -->|binds subject-name + digest| V1
            

The repository workflow publishes into the account namespace. GHCR records image versions whose durable content identity is a manifest digest, such as sha256:…. Tags such as 1.0.0 and latest are labels that can point at a manifest; a tag can later move to another digest. A consumer that needs byte-for-byte identity therefore records and pulls the digest.

The last arrow is intentionally conditional: an attestation exists only if the producer generated one. Hosting the package does not automatically create provenance evidence for an ordinary package publish.

5. Authentication is not package authorization

Granular packages expose read, write, and admin roles. Read can download and inspect metadata; write can upload new versions; admin can additionally manage/delete and grant access. When a package is linked before publication and inherits repository permissions, workflows in that repository normally receive package access automatically. A different repository must be explicitly added under the package’s Actions access when the package is private or otherwise restricted.

Operation Workflow token permission Package-side access Result
Pull linked/private package packages: read Consumer repository has read access Allowed
Push new version packages: write Publisher repository has write access Allowed
Delete/restore via Actions REST preview Token + package admin Repository has admin access Potentially allowed; preview, destructive
Anonymous pull of public GHCR image No GitHub token Package is public Allowed

A token can be valid yet receive 403/denied because the repository is not authorized for that package or because the job reduced packages to read. Chapter 20’s identity model therefore continues directly into package distribution.

6. GITHUB_TOKEN versus registry credentials

Inside GitHub Actions, GitHub recommends GITHUB_TOKEN for registries that support granular permissions when the workflow repository is associated with or granted access to the package. The token is job-scoped and avoids a stored long-lived registry credential.

Outside Actions, GitHub Packages registry clients still document a personal access token (classic) with the corresponding package scopes. This is an important boundary: support for fine-grained or GitHub App tokens on individual Packages REST endpoints does not make a fine-grained PAT a supported Docker/npm registry login credential. Treat registry protocol authentication and REST API authentication as separate interfaces.

7. Repository linking, source labels, and inheritance

For a GHCR image, the OCI label org.opencontainers.image.source=https://github.com/OWNER/REPO connects the image to its source repository when publishing from the command line. Publishing from a workflow with GITHUB_TOKEN also associates the workflow repository, but the source label remains valuable metadata and prevents namespace/link ambiguity.

If a package is linked before it is first published, GitHub can inherit access permissions from that repository by default. Linking a package later does not retroactively mean the same thing as inheriting access; the package can retain its existing granular permission set unless inheritance is explicitly selected.

8. Visibility and lifecycle are governance decisions

A new granular package is private by default. Making a disposable GHCR package public enables anonymous pulls and a clean no-credential consumer lab, but GitHub warns that once a package is public it cannot be made private again. This is why the lab package contains only synthetic text and why the visibility change is explicitly marked irreversible.

Deletion is also not routine cleanup for supported releases. A public package/version with more than 5,000 downloads cannot be deleted through normal controls; deleted packages/versions can normally be restored within 30 days if the namespace has not been reused. Production retention should therefore be defined before consumers depend on a version.

9. Digest integrity is necessary; provenance is separate evidence

A digest answers “are these the same registry bytes?” It does not answer “which reviewed source and workflow produced them?” Artifact attestations can cryptographically bind a subject name and SHA-256 digest to workflow/source claims, and GitHub can push container attestations to an OCI registry. Chapter 25 will build that mechanism in depth. In this chapter the rule is narrower: never infer provenance merely because a package is hosted on GitHub.

10. Read-only inspection before publication

Start by proving repository identity and package absence. Do not create a package merely to discover where it would live.

gh auth status --hostname github.com
OWNER=$(gh api user --jq .login)
REPO="$OWNER/github-packages-lab"
IMAGE="ghcr.io/${OWNER,,}/github-packages-lab"

echo "repo=$REPO"
echo "planned_image=$IMAGE"
gh repo view "$REPO" --json nameWithOwner,visibility,viewerPermission 2>/dev/null || true

If the repository does not exist yet, the last command failing is expected. Existing public package metadata can be inspected from the account’s Packages page; after publication the workflow will also record the exact registry digest and structured package-version metadata.

Knowledge check

Why is an Actions artifact not a substitute for a package registry?

Which current GitHub Packages registries support granular permissions?

If latest points at digest D1 today, is D1 guaranteed to remain the meaning of latest?

Can a fine-grained PAT be assumed to work as a GHCR Docker login because some Packages REST endpoints accept fine-grained tokens?

Does a package hosted on GitHub automatically prove build provenance?

Summary

Packages are distribution resources, not Git objects and not workflow artifacts. Model namespace, permission scope, version/tag, immutable digest, and provenance separately. Prefer GITHUB_TOKEN inside Actions, grant cross-repository package access deliberately, pin consumers to exact digests when identity matters, and define deletion/retention before a version becomes supported.

Next, the lab publishes a synthetic scratch image to GHCR, makes that disposable package public, consumes it anonymously by digest in a second workflow, and proves a read-only token cannot push.

Next lesson

GitHub Packages, Container Registry, Package Permissions, Provenance, and Distribution: Guided Hands-On Workflow and Core Operations

Official references

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.