Chapter 21Lesson 03~155 minutes

GitHub Packages, Container Registry, Package Permissions, Provenance, and Distribution: Configuration, Design Choices, and Tradeoffs

The guided lab proved one safe GHCR path. Production architecture requires choosing the distribution surface, ownership model, visibility, identity convention, and credential boundary deliberately. This lesson turns those choices into an auditable policy instead of letting package-manager defaults decide governance.

Design choicesPackage scopePublic vs privateTag vs digestExternal registry

Learning objectives

  • Choose package, Release asset, Actions artifact, or external repository based on consumer/lifecycle needs.
  • Choose inherited repository access or granular package access for supported registries.
  • Design semantic tags for humans while preserving digest/version identity for machines.
  • Separate public, private, and Enterprise internal distribution concerns.
  • Justify registry authentication and provenance controls by maintainability, security, reliability, compatibility, and cost.

Availability. Examples target GitHub.com. Granular permissions currently apply to Container, npm, NuGet, and RubyGems; Maven and Gradle remain repository-scoped. Internal package visibility is an organization/enterprise concept and Maven/Gradle do not currently support internal repository visibility. GHES package hostnames and availability depend on administrator configuration; GHES 3.21 requires Packages configuration and subdomain isolation for its Container registry.

1. Package registry versus Release asset versus Actions artifact versus external repository

Start with consumer behavior. A developer declaring an npm dependency expects semver resolution and a package registry. A Kubernetes deployment expects an OCI registry. A customer downloading one installer may prefer a Release asset. CI passing a test bundle between jobs needs an Actions artifact. A company already operating Nexus/Artifactory may need proxying, promotion repositories, retention, replication, or ecosystem breadth beyond the scope of this GitHub course.

Choice Maintainability Security/governance Reliability/compatibility Cost/operating burden
GitHub Packages/GHCR Low friction near GitHub source/workflows Package roles + repository linkage + Actions identity Native package/OCI clients; GitHub availability dependency Public packages free; private usage/quotas apply
Release assets Simple for release-coupled files Governed by repository/release permissions Good for explicit downloads; not dependency resolution Low operational burden
Actions artifacts Excellent for CI evidence Run/repository access + retention Not a long-term package distribution contract Storage/retention tied to Actions
External artifact repository Centralized enterprise capabilities Can unify policy across many SCM/CI systems Adds external availability/integration dependency Software/infrastructure/admin cost

2. Repository-linked inheritance versus granular package access

Inherited access minimizes configuration when package lifecycle follows one repository: repository readers/writers and its Actions workflows naturally align with package consumers/publishers. Granular access is useful when one package is a shared platform resource consumed by a carefully selected set of repositories independent of source-repository membership.

Granularity creates another policy object to maintain. If teams cannot answer “which repositories have package read/write/admin and why?”, the flexibility can become authorization drift. Prefer inheritance until independent package access solves a real boundary.

3. Cross-repository workflow access: explicitly grant the consumer

For a private granular package, adding packages: read to the consumer workflow is only half the configuration. Package settings must also grant that repository read access under Manage Actions access. The producer can retain write/admin while consumers get read only.

permissions:
  contents: read
  packages: read

# Registry authentication in that approved consumer job:
printf '%s' "$GITHUB_TOKEN" | docker login ghcr.io   -u "$GITHUB_ACTOR" --password-stdin

Do not grant a public repository access to a private package casually: GitHub warns that forks of that public repository may be able to access the private packages. Package access therefore participates in the fork trust model from Chapters 13 and 20.

4. Semantic version/tag for navigation; digest for immutable deployment identity

Humans need labels such as 2.4.1, stable, or latest. Automation needs a statement that survives label movement. For containers, record both:

human_selector = ghcr.io/acme/service:2.4.1
immutable_ref  = ghcr.io/acme/service@sha256:0123...cdef

A release policy can forbid moving semantic version tags after publication while still treating the digest as the cryptographic content identifier. Deployments should record the digest actually pulled, not merely the requested tag.

5. Public, private, and internal distribution are different threat models

Visibility Who can discover/read Typical use Governance note
Public Anyone; public GHCR images support anonymous pull Open source and public runtime images Visibility cannot later be reverted to private
Private Explicitly authorized users/teams/repositories Restricted dependencies/images Storage/data transfer quotas and access mapping matter
Internal Enterprise members for supported granular registries Enterprise-wide shared components Enterprise Cloud/organization policy; not a GitHub Free personal path

6. Credential design: prefer workflow identity; do not broaden registry tokens by habit

Inside Actions, use GITHUB_TOKEN when the package is associated with or grants access to the workflow repository. Outside Actions, GitHub Packages registry docs use classic PAT package scopes. For automation spanning many repositories, a GitHub App may be a better GitHub API identity, but do not assume that makes its installation token a supported native registry-client login in every ecosystem.

Never place registry tokens directly in .npmrc, Dockerfiles, image layers, command history, Actions artifacts, or cache paths. If a package credential is exposed, revoke/rotate it first; deleting a config file later does not invalidate a leaked token.

7. Provenance policy: hosted, digest-pinned, attested, verified are four different states

Define maturity explicitly:

  1. Hosted: bytes exist in a registry.
  2. Digest-pinned: consumer can request exact bytes.
  3. Attested: producer generated signed provenance binding subject name/digest to build identity.
  4. Verified: consumer checked that attestation against an acceptance policy.

Skipping from “hosted” to “trusted” is a supply-chain category error. Chapter 25 will implement and verify attestations in depth.

8. Cost and retention belong in the package architecture

Public Packages usage is free. Private Packages usage has plan-specific included storage/data transfer and may incur cost beyond that allowance. GitHub currently states Container registry image storage and bandwidth are free, but the documentation also says this policy could change with advance notice. Avoid freezing a cost assumption into architecture: monitor current billing docs and define cleanup/retention by supported-version policy rather than “delete whatever is old.”

9. Worked decision: internal service platform

A platform team builds a base container used by 40 private services. The services need reproducible deployments; only the platform repository may publish; all service repositories may pull; security wants build provenance.

Decision Selected approach Why
Surface GHCR granular package OCI-native and close to GitHub workflows
Scope Organization package linked to platform repo; explicit consumer repositories Publisher ownership stays narrow; consumers get read only
Identity Semver tag + recorded digest Humans get release semantics; machines get immutable identity
Visibility Private/internal according to enterprise policy Avoid public disclosure of internal base image
Credentials GITHUB_TOKEN per approved workflow repo Short-lived; no shared registry PAT
Provenance Attest digest, require verification in deploy policy Hosting alone does not satisfy provenance requirement
Retention Keep every supported digest; delete only after support/EOL review Prevents rollback/support breakage

Knowledge check

When is repository-inherited package access usually preferable?

Why must a private cross-repository consumer configure both packages:read and package-side Actions access?

Why record a digest even if policy says semantic version tags never move?

What changes when a package is made public?

What extra claim is needed before “this GitHub-hosted image has provenance”?

Summary

Choose the distribution surface from the consumer lifecycle, package access from the ownership boundary, and identity from the verification requirement. Tags are ergonomic; digests are immutable content identity. Prefer short-lived workflow identity, keep visibility decisions deliberate, and do not let package hosting substitute for provenance. Next, diagnose the ways these layers fail independently.

Next lesson

GitHub Packages, Container Registry, Package Permissions, Provenance, and Distribution: Diagnostics, Failure Modes, Security, and Performance

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.