Last Verified: September 2026
Scope: GitHub.com / GitHub Enterprise Cloud unless explicitly stated otherwise
Audience: Developers, DevOps engineers, platform engineers, administrators, architects, trainers, and engineering teams
GitHub Packages is GitHub’s package-hosting platform. It lets teams publish, store, secure, discover, automate, and consume software packages and container images while keeping package ownership, source code, CI/CD, permissions, security metadata, and billing close to the GitHub development workflow.
This guide is organized as a progressive learning journey: concept → why → how → example → practice → real-world use case → best practices → troubleshooting.
1. What GitHub Packages Is
GitHub Packages is a set of package registries integrated with GitHub. A package is a distributable software artifact such as a container image, an npm library, a Java artifact, a NuGet package, or a Ruby gem.
Why teams use it
A typical software organization produces more than source code. It also produces reusable libraries, container images, command-line tools, SDKs, internal frameworks, application dependencies, and release artifacts. Those outputs need a trusted place to live.
GitHub Packages solves that problem by giving teams a registry that is integrated with:
- GitHub repositories
- GitHub organizations and teams
- GitHub Actions
- GitHub permissions and tokens
- GitHub Releases
- Supply-chain security features
- Artifact attestations
- Linked artifacts
- REST APIs and webhooks
- Billing and usage controls
Core lifecycle
flowchart LR
A[Source Code] --> B[Build]
B --> C[Test]
C --> D[Package]
D --> E[Authenticate]
E --> F[Publish]
F --> G[Registry]
G --> H[Install / Pull]
H --> I[Application or Deployment]
I --> J[Observe / Audit]
J --> K[Update / Retire]
The important idea is that a registry sits between producers and consumers. Producers create immutable versions; consumers fetch known versions through an authenticated and governed channel.
GitHub Packages vs GitHub Releases vs Actions artifacts
| Capability | GitHub Packages | GitHub Releases | GitHub Actions artifacts |
|---|---|---|---|
| Primary purpose | Dependency/package distribution | Product release publishing | Temporary workflow outputs |
| Typical consumers | Applications, builds, deployments | Humans and release automation | Workflow jobs and engineers |
| Versioned package semantics | Yes | Release/tag semantics | No package-manager semantics |
| Package manager integration | Yes | No | No |
| Container registry | Yes | No | No |
| Long-term reusable dependency | Yes | Sometimes | Usually no |
| CI intermediate file | Possible but not ideal | No | Yes |
| Best example | ghcr.io/acme/api:1.4.2 | v1.4.2 release + binaries | test reports, build outputs |
A useful rule:
- Use Packages when another build, runtime, or developer will consume the artifact as a package.
- Use Releases when you are publishing a release event, release notes, and downloadable assets.
- Use Actions artifacts for workflow-produced files that are primarily tied to a workflow run.
2. Supported Registries and Permission Models
GitHub Packages supports these principal registries:
| Ecosystem | Registry / endpoint | Typical client | Permission model |
|---|---|---|---|
| Containers | ghcr.io | Docker / OCI clients | Granular user/org scoped |
| npm | npm.pkg.github.com | npm / Yarn | Granular user/org scoped |
| NuGet | nuget.pkg.github.com | dotnet / NuGet CLI | Granular user/org scoped |
| RubyGems | rubygems.pkg.github.com | gem / Bundler | Granular user/org scoped |
| Apache Maven | maven.pkg.github.com | Maven | Repository scoped |
| Gradle | Maven-compatible GitHub Packages endpoint | Gradle | Repository scoped |
GitHub’s legacy Docker registry used docker.pkg.github.com. It has been replaced by the Container registry at ghcr.io and should be treated primarily as a migration/backward-compatibility topic.
Granular packages vs repository-scoped packages
This is one of the most important architectural distinctions in GitHub Packages.
Granular-permission packages
Supported by:
- Container registry
- npm
- NuGet
- RubyGems
These packages can be scoped to a personal account or organization. Their visibility and access can be managed separately from a repository, although they can be connected to one.
Repository-scoped packages
Supported by:
- Apache Maven
- Gradle
These packages inherit their repository’s permissions and visibility.
Package scope model
flowchart TD
A[GitHub Package]
A --> B{Registry permission model}
B -->|Granular| C[User or Organization Scope]
C --> D[Optional repository connection]
C --> E[Independent Read / Write / Admin]
B -->|Repository scoped| F[Repository]
F --> G[Repository visibility]
F --> H[Repository permissions]
3. Core Concepts and Terminology
| Term | Meaning |
|---|---|
| Package | A distributable software artifact managed by a registry |
| Registry | Service that stores and distributes package versions |
| Package manager | Client that publishes or installs packages |
| Namespace | Owner/name space used to prevent naming ambiguity |
| Package owner | User, organization, or repository that controls package scope |
| Version | A named package release such as 1.4.2 |
| Tag | A movable or human-friendly alias, especially common for containers |
| Digest | Content-addressed immutable identifier, especially for OCI images |
| Metadata | Description, source repository, license, tags, publication data, etc. |
| Visibility | Public, private, or internal where supported |
| Permission | Read, write, or admin access |
| Repository connection | Association between a granular package and source repository |
| Publisher | User or automation that creates a package version |
| Consumer | Build, developer, service, or deployment that downloads the package |
Version vs tag vs digest
For containers, these three terms must not be confused:
- Version: a package version as represented by GitHub’s package system.
- Tag: a friendly image reference such as
1.8.0,main, orlatest. - Digest: immutable content identifier such as
sha256:....
For production deployment, digest pinning is the strongest way to guarantee that the exact bytes tested are the bytes deployed.
4. Where Packages Appear in GitHub
GitHub exposes packages from multiple contexts:
Organization packages
Navigate to the organization and select Packages. This is the natural management surface for shared internal libraries and container images owned by an organization.
User packages
A user’s profile can expose packages scoped to that user.
Repository packages
A repository sidebar can show packages associated with that repository. For repository-scoped registries, the repository is the package’s permission boundary. For granular registries, the repository is a connection that can provide source metadata and optionally inherited access.
Package search and discovery
Use organization, user, or repository Packages surfaces to browse packages you can access. From a package landing page, follow source-repository and version links to understand ownership and usage. For private/internal packages, discovery is permission-aware: a package that exists but is outside your access boundary may not be visible as a normal browsable asset.
Package landing page
A package page can expose:
- Description
- Installation and usage instructions
- Source repository
- Version history
- Publication dates
- Download activity
- Metadata
- Package settings
- Access configuration
5. Access Control, Visibility, and Permissions
Package roles
| Role | Typical capability |
|---|---|
| Read | View metadata and download/install |
| Write | Read plus upload/publish |
| Admin | Publish, delete/manage package, change access, grant permissions |
For organization-scoped granular packages, access can be granted to individuals or teams. Organization owners also have administrative control.
Visibility
Depending on the registry and account context, packages can be:
- Public — intended for broad consumption.
- Private — restricted to explicitly authorized users/repositories.
- Internal — organization/enterprise-oriented visibility where supported by granular registries and plan context.
For most GitHub Packages registries, even public packages require package-client authentication. The major exception is the Container registry: public container images can be pulled anonymously.
Permission inheritance
When a granular package is connected to a repository, GitHub can let the package inherit repository permissions. Organizations can also disable automatic inheritance for newly published packages.
Use inheritance when repository membership should define package access. Use explicit granular access when one package is shared across many repositories or teams.
Repository transfer behavior
Repository transfers affect the two package models differently:
- Granular packages: the package remains owned by its user/organization scope. A repository connection can be removed during transfer, and workflows/Codespaces can lose access.
- Repository-scoped packages: the package follows the repository and its ownership/permission model.
This is why migration planning must include package ownership, not only repository ownership.
6. Authentication and Authorization
Authentication answers who are you? Authorization answers what may you do?
Personal access token (classic)
GitHub Packages package-client authentication uses a personal access token (classic) with package scopes.
| Scope | Purpose | Typical minimum package permission |
|---|---|---|
read:packages | Download/install | Read |
write:packages | Publish/upload | Write |
delete:packages | Delete | Admin |
For organizations using SAML SSO, the token may also need authorization for that organization.
GITHUB_TOKEN
Inside GitHub Actions, prefer the repository’s automatically generated GITHUB_TOKEN where possible.
Example minimum workflow permissions for publishing:
permissions:
contents: read
packages: write
For a consumer workflow that only installs a package:
permissions:
contents: read
packages: read
For cross-repository use of granular packages, grant the consumer repository access under the package’s Manage Actions access settings.
Important distinction: package clients vs REST API tokens
Do not generalize token rules blindly.
- Package-manager authentication is documented around PAT classic and
GITHUB_TOKENin Actions. - Some Packages REST API endpoints also support GitHub App user/installation access tokens or fine-grained token types as documented per endpoint.
Always follow the authentication section of the exact API endpoint you are using.
Secure token handling
Recommended:
- Prefer
GITHUB_TOKENin Actions. - Use minimal permissions.
- Store long-lived credentials in GitHub secrets or an external secret manager.
- Rotate PATs.
- Revoke unused credentials.
- Avoid tokens in checked-in
.npmrc,settings.xml,nuget.config,Gemfile, shell scripts, or Dockerfiles. - Treat self-hosted runners as part of your security boundary.
- Pin third-party Actions to trusted commit SHAs for high-assurance release workflows.
Avoid:
- Shared developer PATs
- Admin-scoped tokens for read-only consumers
- Printing tokens in diagnostic output
- Publishing from untrusted pull-request contexts
- Reusing the same token across unrelated systems
7. Package Lifecycle
A healthy package lifecycle is deliberate and auditable.
stateDiagram-v2
[*] --> Build
Build --> Test
Test --> Publish: pass
Test --> Build: fail/fix
Publish --> Active
Active --> NewVersion
NewVersion --> Active
Active --> Deprecated: ecosystem supports deprecation
Active --> Deleted
Deleted --> Restored: within restore rules
Deleted --> [*]: retention window expires
Typical lifecycle stages
- Build source.
- Run unit/integration/security tests.
- Choose version.
- Authenticate.
- Publish package.
- Verify package metadata.
- Grant consumer access.
- Install/pull by consumers.
- Publish new versions rather than overwriting releases.
- Retire old versions based on retention policy.
- Restore only when supported and still inside the restoration window.
Semantic Versioning
For libraries, Semantic Versioning is a useful convention:
MAJOR: incompatible changeMINOR: backward-compatible featurePATCH: backward-compatible fix
Example:
2.7.4
│ │ └─ patch
│ └─── minor
└───── major
Pre-releases can use identifiers such as:
2.8.0-alpha.1
2.8.0-beta.2
2.8.0-rc.1
Container image tags can mirror SemVer, but production deployment should prefer immutable digests.
8. Getting Started: Publish a Container to GHCR
Containers are the easiest way to see the complete GitHub Packages workflow end to end.
Prerequisites
- GitHub account
- Repository such as
acme/hello-api - Docker installed locally, or GitHub Actions
- Permission to publish to the target namespace
Example application
Create a minimal Dockerfile:
FROM nginx:alpine
COPY ./index.html /usr/share/nginx/html/index.html
LABEL org.opencontainers.image.source="https://github.com/acme/hello-api"
LABEL org.opencontainers.image.description="Hello API training image"
LABEL org.opencontainers.image.licenses="MIT"
Create index.html:
<h1>Hello from GitHub Container Registry</h1>
Authenticate locally
Export a PAT classic with the minimum required scopes:
export CR_PAT="YOUR_TOKEN"
echo "$CR_PAT" | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
Build and tag
docker build -t hello-api:1.0.0 .
docker tag hello-api:1.0.0 ghcr.io/acme/hello-api:1.0.0
Push
docker push ghcr.io/acme/hello-api:1.0.0
Verify
docker pull ghcr.io/acme/hello-api:1.0.0
For a public container, an anonymous pull can work without a registry login.
Pull by digest
After publishing, capture the image digest and deploy the immutable reference:
docker pull ghcr.io/acme/hello-api@sha256:REPLACE_WITH_DIGEST
Expected result
The package appears under the owning user/organization’s Packages page. If source metadata is configured correctly, the package page can link back to its repository.
9. GitHub Container Registry Deep Dive
What GHCR stores
GHCR supports Docker Image Manifest V2 Schema 2 and OCI image specifications. It can host Docker and OCI images, including foreign layers such as Windows image layers.
Common reference structure
ghcr.io/OWNER/IMAGE:TAG
Examples:
ghcr.io/acme/payments-api:2.3.1
ghcr.io/acme/payments-api:sha-4c912ab
ghcr.io/acme/payments-api@sha256:...
OCI metadata
Useful OCI annotations/labels include:
org.opencontainers.image.source
org.opencontainers.image.description
org.opencontainers.image.licenses
org.opencontainers.image.revision
org.opencontainers.image.version
These improve traceability and package presentation.
Tagging strategy
A strong release can publish several tags pointing to the same digest:
2.4.1
2.4
2
sha-a1b2c3d
Use latest only if your team defines exactly what it means. Never use latest as the sole production deployment reference.
Multi-platform images
With Buildx, one logical image tag can point to an OCI image index containing multiple platform manifests.
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/acme/api:2.4.1 \
--push .
flowchart TD
A[ghcr.io/acme/api:2.4.1] --> B[OCI Image Index]
B --> C[linux/amd64 manifest]
B --> D[linux/arm64 manifest]
Container registry best practices
- Use multi-stage builds.
- Use small, maintained base images.
- Add OCI source/license metadata.
- Publish immutable release tags.
- Add commit-SHA tags for traceability.
- Capture and promote digests.
- Generate provenance attestations.
- Generate an SBOM.
- Scan dependencies and base images.
- Use least-privilege workflow permissions.
10. npm Registry
GitHub’s npm registry is used for Node.js packages. It supports granular user/organization-scoped permissions.
Package naming
GitHub Packages npm packages are scoped packages. A typical package name is:
{
"name": "@acme/shared-utils",
"version": "1.2.0",
"description": "Shared utilities for Acme services",
"repository": {
"type": "git",
"url": "git+https://github.com/acme/shared-utils.git"
}
}
Configure .npmrc
@acme:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
Do not commit a literal token into .npmrc.
Publish locally
export NODE_AUTH_TOKEN="YOUR_PAT_CLASSIC"
npm publish
Install
npm install @acme/shared-utils@1.2.0
GitHub Actions publishing
name: Publish npm package
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: '20.x'
registry-url: 'https://npm.pkg.github.com'
scope: '@acme'
- run: npm ci
- run: npm test
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
npm use case
A company maintains a shared TypeScript library consumed by ten internal services. The package is owned by the organization, write access is limited to the platform team, and each consuming repository receives read access through Manage Actions access.
npm gotchas
- Ensure the package is scoped to the expected user/organization.
- Ensure
.npmrcroutes only the intended scope to GitHub Packages. - Avoid routing all npm traffic to a private registry unless that is explicitly intended.
- Keep npmjs.org and GitHub Packages sources clear to avoid dependency confusion and accidental publication.
11. Apache Maven Registry
Maven packages on GitHub Packages are repository-scoped. This means the package follows repository permissions rather than having an independent granular permission model.
Maven coordinates
A Maven artifact is identified by:
groupId:artifactId:version
Example:
com.acme:payments-sdk:2.1.0
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.acme</groupId>
<artifactId>payments-sdk</artifactId>
<version>2.1.0</version>
<distributionManagement>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/acme/payments-sdk</url>
</repository>
</distributionManagement>
</project>
settings.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
<servers>
<server>
<id>github</id>
<username>${env.GITHUB_ACTOR}</username>
<password>${env.GITHUB_TOKEN}</password>
</server>
</servers>
</settings>
Publish
mvn --batch-mode deploy
Consume
A consumer repository can configure the GitHub Packages Maven repository and add a dependency:
<dependency>
<groupId>com.acme</groupId>
<artifactId>payments-sdk</artifactId>
<version>2.1.0</version>
</dependency>
GitHub Actions
name: Publish Maven package
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- run: mvn --batch-mode verify
- run: mvn --batch-mode deploy
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Maven design implication
Because Maven packages are repository-scoped, do not design access as though the package has independent organization-level ACLs. If many teams need the package, model the repository permissions accordingly.
12. Gradle Registry
GitHub’s Gradle support uses Maven-format package publishing and is repository-scoped.
Groovy DSL example
plugins {
id 'java-library'
id 'maven-publish'
}
version = '1.3.0'
group = 'com.acme'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/acme/shared-java")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
}
Kotlin DSL example
plugins {
`java-library`
`maven-publish`
}
group = "com.acme"
version = "1.3.0"
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/acme/shared-java")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
}
Publish
./gradlew publish
GitHub Actions
name: Publish Gradle package
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- run: ./gradlew test
- run: ./gradlew publish
env:
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
For high-assurance release pipelines, pin third-party actions to a reviewed commit SHA rather than a moving tag.
13. NuGet Registry
GitHub Packages supports NuGet packages with granular user/organization-scoped permissions.
Add a package source
dotnet nuget add source \
--username YOUR_GITHUB_USERNAME \
--password YOUR_PAT_CLASSIC \
--store-password-in-clear-text \
--name github \
"https://nuget.pkg.github.com/acme/index.json"
The --store-password-in-clear-text option is convenient for demonstrations but is a security tradeoff. In CI, prefer injected credentials and ephemeral runners.
Pack
dotnet pack --configuration Release
Publish
dotnet nuget push "bin/Release/Acme.Tools.1.0.0.nupkg" \
--api-key YOUR_PAT_CLASSIC \
--source github
Install
dotnet add package Acme.Tools --version 1.0.0 --source github
nuget.config with source mapping
Source mapping helps prevent dependency confusion by ensuring private package IDs resolve only from the intended source.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="github" value="https://nuget.pkg.github.com/acme/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="github">
<package pattern="Acme.*" />
</packageSource>
</packageSourceMapping>
</configuration>
Current size note
GitHub documents that a NuGet .nupkg archive for a package version must be smaller than 2.147 GB.
14. RubyGems Registry
GitHub Packages supports Ruby gems with granular user/organization-scoped permissions.
Prerequisites
GitHub’s current documentation lists RubyGems 2.4.1+ and Bundler 1.6.4+ as minimums. In production, use a supported modern Ruby/RubyGems/Bundler toolchain.
Authenticate with RubyGems
gem sources --add \
https://YOUR_USERNAME:YOUR_PAT_CLASSIC@rubygems.pkg.github.com/acme/
Authenticate with Bundler
bundle config https://rubygems.pkg.github.com/acme \
YOUR_USERNAME:YOUR_PAT_CLASSIC
Gem metadata
Gem::Specification.new do |spec|
spec.name = "acme-tools"
spec.version = "1.0.0"
spec.summary = "Internal Acme Ruby utilities"
spec.files = Dir["lib/**/*"]
spec.require_paths = ["lib"]
spec.metadata = {
"github_repo" => "ssh://github.com/acme/acme-tools"
}
end
Build and publish
gem build acme-tools.gemspec
gem push --key github \
--host https://rubygems.pkg.github.com/acme \
acme-tools-1.0.0.gem
Bundler consumption
source "https://rubygems.org"
source "https://rubygems.pkg.github.com/acme" do
gem "acme-tools", "1.0.0"
end
15. Connecting Packages to Repositories
For granular registries, package ownership and repository ownership are separate concepts.
Why connect a package to a repository?
A connection can provide:
- Source repository link
- README/context on the package page
- Permission inheritance, if enabled
- Automatic Actions access from the linked repository
- Better provenance and discoverability
Manual connection
Typical UI flow:
- Open the package landing page.
- Open Package settings.
- Find the repository connection area.
- Choose Connect repository.
- Select the source repository.
- Decide whether repository permissions should be inherited.
- Verify Actions/Codespaces access if needed.
Automatic container linkage
OCI source metadata can establish repository context:
LABEL org.opencontainers.image.source="https://github.com/acme/payments-api"
Design pattern: one producer, many consumers
flowchart LR
P[Producer repository] --> W[Build/Test workflow]
W --> R[Organization package]
R --> C1[Consumer repo A]
R --> C2[Consumer repo B]
R --> C3[Consumer repo C]
For this pattern, granular permissions are ideal: the package can remain organization-owned while individual repositories receive only read access.
16. GitHub Actions and GitHub Packages
GitHub Actions is the preferred automation layer for build-test-publish workflows.
Standard pipeline
flowchart LR
A[Push / Tag / Release] --> B[Checkout]
B --> C[Build]
C --> D[Test]
D --> E[Package]
E --> F[Authenticate]
F --> G[Publish]
G --> H[Verify]
H --> I[Attest / SBOM]
Trigger choices
| Trigger | Good for | Risk / consideration |
|---|---|---|
push to branch | Snapshot/dev builds | Can create many versions |
| Git tag | Version-driven releases | Protect tag creation |
| GitHub Release | Human-reviewed release flow | Release creation must be governed |
workflow_dispatch | Manual controlled publish | Requires operational discipline |
schedule | Periodic rebuilds | Must avoid accidental republish collision |
Recommended release workflow permissions
permissions:
contents: read
packages: write
attestations: write
id-token: write
Only request attestations and id-token when the workflow actually generates attestations.
Test before publish
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: ./scripts/test.sh
publish:
needs: test
if: github.event_name == 'release'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- run: ./scripts/publish.sh
The important control is the dependency: publishing does not start unless tests succeed.
17. Container Publishing Workflow with Metadata and Provenance
A production-quality container workflow should build, test, publish, capture the digest, and generate provenance.
name: Publish container
on:
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v6
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
id: push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate provenance attestation
uses: actions/attest@v4
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
For high-assurance production workflows, pin non-GitHub and third-party Actions to a reviewed full commit SHA. The major-version tags above are easier for training and should not be mistaken for the strongest supply-chain posture.
Multi-platform extension
Add Buildx setup and configure:
with:
platforms: linux/amd64,linux/arm64
push: true
18. Actions and Codespaces Package Access
Granular packages can grant access to repositories separately for GitHub Actions and GitHub Codespaces.
Actions access
Use package settings when a workflow in one repository needs a package owned elsewhere.
Typical flow:
- Open the package.
- Open Package settings.
- Find Manage Actions access.
- Add the workflow repository.
- Choose the minimum required role.
- Set workflow
permissionstopackages: readorpackages: writeas appropriate. - Test with
GITHUB_TOKENbefore introducing a PAT.
Codespaces access
For supported granular packages, package settings can also allow selected repositories’ Codespaces to install the package. This is useful when developer environments need private dependencies but you do not want developers maintaining broad package credentials.
Cross-repository access decision
flowchart TD
A[Workflow needs package] --> B{Same repository/package relationship?}
B -->|Yes| C[Use GITHUB_TOKEN]
B -->|No| D{Granular registry?}
D -->|Yes| E[Grant repository package access]
E --> C
D -->|No / repo-scoped| F[Use repository permission model]
F --> G[Validate token and repository access]
19. Deleting, Restoring, and Cleaning Up Packages
Deletion is a destructive operation and should be governed like a release operation.
Current GitHub.com rules
GitHub documents that users with sufficient access can delete:
- An entire private package.
- A specific version of a private package.
- A public package if no version has more than 5,000 downloads.
- A public package version if that version does not have more than 5,000 downloads.
For a public package/version above the documented 5,000-download threshold, GitHub Support is required for deletion assistance.
Restoration
A deleted package or package version can normally be restored when:
- It is restored within 30 days of deletion.
- The package namespace/version has not been reused in a way that blocks restoration.
GitHub Actions deletion/restoration
GitHub currently documents workflow-based delete/restore through the REST API as public preview. For granular packages, the workflow repository must have package admin access and the workflow token must be configured appropriately.
Safe cleanup policy
A practical organization policy could be:
| Package class | Retention suggestion |
|---|---|
| Production releases | Keep indefinitely or per compliance policy |
| Supported major/minor releases | Keep while supported |
| Release candidates | Keep last 5–10 |
| Branch/dev builds | Keep 7–30 days |
| Pull-request builds | Keep only while PR is active plus grace period |
| Untagged container manifests | Clean after verifying no digest references depend on them |
Example: list container versions with GitHub CLI
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"/orgs/acme/packages/container/payments-api/versions?per_page=100"
Example: delete a version
gh api \
--method DELETE \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"/orgs/acme/packages/container/payments-api/versions/PACKAGE_VERSION_ID"
Use a dry-run/reporting step before automated deletion.
20. REST API for GitHub Packages
The Packages REST API can list and manage packages and versions for users and organizations.
Common package types
The REST API recognizes package types including:
npm
maven
rubygems
docker
nuget
container
Gradle packages use the API package type maven. Legacy Docker-registry images can be represented by docker, while GHCR images use container.
List organization packages
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"/orgs/acme/packages?package_type=container&per_page=100"
Get a package
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"/orgs/acme/packages/container/payments-api"
List versions
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"/orgs/acme/packages/container/payments-api/versions?state=active&per_page=100"
Pagination
The API defaults to paginated responses. For large estates:
gh api --paginate \
-H "X-GitHub-Api-Version: 2026-03-10" \
"/orgs/acme/packages?package_type=container&per_page=100"
API authentication caution
Endpoint support differs by operation. PAT classic is the baseline package token model, while current REST documentation also lists GitHub App and fine-grained token support for selected endpoints. Do not assume every endpoint accepts every token type.
21. GitHub CLI for Package Administration
There is no need to wait for a dedicated gh package command to automate package operations. gh api can call the Packages REST API directly.
Inventory script
#!/usr/bin/env bash
set -euo pipefail
ORG="${1:?usage: $0 ORG}"
TYPE="${2:-container}"
gh api --paginate \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"/orgs/${ORG}/packages?package_type=${TYPE}&per_page=100" \
--jq '.[] | [.name, .visibility, .version_count, .updated_at] | @tsv'
Example output:
payments-api private 42 2026-09-24T03:10:21Z
shared-ui internal 18 2026-09-20T09:04:03Z
Why gh api is useful
- Auth can reuse GitHub CLI login.
- Pagination is easy.
--jqsupports lightweight reporting.- Scripts can run locally or in Actions.
- It exposes new REST features without waiting for a specialized CLI subcommand.
22. GraphQL and GitHub Packages
Treat GraphQL support as a compatibility area, not the default management API for GitHub Packages.
GitHub documents that the Packages GraphQL API cannot be used with registries that support granular permissions. GraphQL remains relevant mainly to repository-scoped package models and historical integrations.
For new automation across Container, npm, NuGet, and RubyGems, prefer the REST API unless a documented GraphQL object is specifically required.
Decision table
| Need | Preferred interface |
|---|---|
| List/manage modern granular packages | REST API |
| Automate from shell | gh api |
| Receive publish event | Webhook |
| Package manager publish/install | Native package client |
| Historical/repository-scoped graph use case | GraphQL where documented |
23. Package Webhooks and Event Automation
GitHub exposes a package webhook event for GitHub Packages activity. The documented published action fires when a package is published.
Typical automation
sequenceDiagram
participant Publisher
participant GitHubPackages as GitHub Packages
participant Webhook
participant Scanner
participant Chat as Notification System
Publisher->>GitHubPackages: Publish package
GitHubPackages->>Webhook: package.published
Webhook->>Scanner: Start external scan
Scanner-->>Webhook: Result
Webhook->>Chat: Publish status
Example use cases
- Start an external vulnerability scan after publication.
- Synchronize metadata to a CMDB.
- Notify a release channel.
- Trigger downstream deployment orchestration.
- Register the artifact in an internal catalog.
package vs registry_package
GitHub’s webhook documentation recommends the newer package event over the older registry_package event. Existing integrations should be reviewed before migration rather than changed blindly.
Webhook security
- Use a webhook secret.
- Validate GitHub signatures.
- Reject stale/replayed requests where practical.
- Make handlers idempotent.
- Do not treat an incoming publish event as sufficient authorization for production deployment.
24. GitHub App Integration
GitHub Apps are appropriate when a service needs organization-wide automation without relying on a person’s credentials.
Possible responsibilities:
- Receive package webhooks.
- Read package metadata through supported REST endpoints.
- Create audit records.
- Enforce internal policy outside GitHub.
- Integrate package events with deployment or security tooling.
App design principles
- Request only required repository/organization permissions.
- Use short-lived installation access tokens.
- Verify webhook signatures.
- Store App private keys securely.
- Model each REST endpoint’s accepted token types explicitly.
25. Billing, Storage, Data Transfer, and Budgets
GitHub Packages usage is tied to the owning account and plan.
Included usage currently documented by GitHub
| Plan | Included storage | Included data transfer / month |
|---|---|---|
| GitHub Free | 500 MB | 1 GB |
| GitHub Pro | 2 GB | 10 GB |
| GitHub Free for organizations | 500 MB | 1 GB |
| GitHub Team | 2 GB | 10 GB |
| GitHub Enterprise Cloud | 50 GB | 100 GB |
GitHub documents that the included storage pool is shared with GitHub Actions artifacts.
Container registry billing status
As of September 2026, GitHub’s billing documentation says Container registry image storage and bandwidth are currently free, with at least one month of notice promised before a policy change.
Treat that as a current policy, not a permanent architecture assumption.
Actions package transfer
GitHub documents that package downloads performed by GitHub Actions using a GITHUB_TOKEN do not count against the hosting repository’s data-transfer usage. Current billing documentation further distinguishes token and runner type: GITHUB_TOKEN access is free for GitHub-hosted and self-hosted runners; PAT-based package access is documented as free on GitHub-hosted runners but billable on self-hosted runners.
Cost controls
Recommended:
- Define version-retention rules.
- Clean snapshot/dev packages.
- Avoid duplicating the same large binary under unnecessary versions.
- Monitor package and Actions storage together.
- Configure budgets/spending limits where applicable.
- Enable budget alerts at 75%, 90%, and 100% where useful.
- Enable included-usage alerts at 90% and 100% for GitHub Packages storage/bandwidth.
- Review sudden changes in download patterns.
- Track organization ownership before moving repositories/packages.
Storage forecasting
A simple estimate:
Monthly retained storage growth
≈ average package size × new retained versions per month × package count
For containers, deduplicated layer behavior can make real storage different from a naive image-size multiplication, so billing/usage reports remain the source of truth.
26. Organization Package Administration
Organization owners should treat package configuration as part of platform governance.
Organization-level concerns
- Who may create packages?
- Which visibility levels are allowed?
- Should granular packages inherit repository permissions automatically?
- Which teams can administer shared packages?
- Who may publish production packages?
- What is the naming convention?
- What is the retention policy?
- How are external/public packages reviewed?
- What is the response plan for a compromised package?
Suggested organization standard
| Area | Recommended standard |
|---|---|
| Ownership | Organization, not individual users, for business packages |
| Publishing | CI-first; developer-laptop publishing only for controlled exceptions |
| Credentials | GITHUB_TOKEN in Actions; minimal PAT classic for local use |
| Visibility | Private/internal by default for proprietary packages |
| Source link | Required |
| Versioning | SemVer for libraries; immutable release tags/digests for containers |
| Release | Protected tags/releases or environment approval |
| Retention | Explicit per package class |
| Provenance | Attest production artifacts |
| SBOM | Generate for production/security-sensitive artifacts |
27. Enterprise Considerations
Enterprise Managed Users
GitHub documents that Enterprise Managed Users can publish to organization namespaces but cannot publish packages to their own user namespace because there is no personal storage allocation.
GitHub Enterprise Cloud
Enterprise Cloud is relevant when you need organization/enterprise governance, internal visibility, broader security capabilities, or private/internal artifact attestations.
For GitHub Enterprise Cloud with data residency, do not assume the GitHub.com registry matrix is identical. Current GitHub documentation says the Maven and Gradle registries are unavailable in the data-residency offering. Service URLs can also differ; for example, the Container registry uses a tenant-specific containers.SUBDOMAIN.ghe.com endpoint rather than ghcr.io. Review the exact GHE.com feature matrix before migration.
Artifact attestations and plan behavior
GitHub currently documents artifact attestations as available on current plans, with an important visibility distinction:
- On Free, Pro, and Team, attestations are available for public repositories.
- Private/internal repository attestations require GitHub Enterprise Cloud.
Always recheck plan requirements before making compliance commitments.
GitHub Enterprise Server
GitHub Enterprise Server capabilities vary by server version. Do not copy GitHub.com registry availability, action versions, API versions, or security-feature assumptions directly to GHES. Check the documentation for the exact GHES release you operate.
28. Package Architecture Patterns
Centralized organization package registry
flowchart TB
subgraph Producers
A[Service A Repo]
B[Library Repo]
C[UI Repo]
end
A --> P[Organization Packages]
B --> P
C --> P
P --> X[Consumer Repo 1]
P --> Y[Consumer Repo 2]
P --> Z[Runtime / Deployment]
Benefits:
- Central ownership
- Easier discovery
- Consistent access model
- Shared CI patterns
- Easier audit and retention
Multi-repository shared library
Producer repository publishes @acme/auth-client. Many services consume a pinned version. The producer team owns write/admin; consumers receive read-only access.
Monorepo with multiple packages
repo/
├── packages/
│ ├── api-client/
│ ├── ui-kit/
│ └── config/
├── .github/workflows/
└── package.json
Publishing options:
- Independent package versions
- One shared release version
- Changed-package detection
- Matrix publishing
- Path filters
Use tools such as workspace managers/change detection only when their additional complexity is justified.
29. Build Once, Deploy Many
A mature release pipeline should avoid rebuilding the artifact independently for each environment.
flowchart LR
A[Commit] --> B[Build + Test]
B --> C[Immutable Artifact]
C --> D[Dev]
D --> E[Stage]
E --> F[Production]
The artifact moving through environments should be the same bytes.
Containers
Good:
ghcr.io/acme/api@sha256:abc123...
Riskier:
ghcr.io/acme/api:latest
Promotion patterns
- Promote a digest, not a rebuild.
- Add an environment/release tag to an existing digest if your workflow needs tags.
- Store release metadata that records the exact digest/version.
- Roll back by redeploying a previously approved immutable version.
Why this matters
Rebuilding per environment creates a provenance gap: production may contain different bytes from the artifact tested in staging even when source revision is the same.
30. Versioning and Release Strategy
Libraries
Recommended:
- Semantic versions
- Clear compatibility policy
- Pre-release identifiers
- Changelog/release notes
- No version reuse
Containers
Recommended tag set:
2.7.3
2.7
2
sha-74cce21
Optional:
latest
main
release-candidate
Only use mutable tags if your automation understands that they are pointers, not identities.
Release-driven publishing
A controlled release path:
flowchart LR
A[Merge to main] --> B[CI]
B --> C[Create version tag]
C --> D[GitHub Release]
D --> E[Publish package]
E --> F[Attest]
F --> G[Promote exact version]
Rollback
Do not overwrite 2.7.3 with fixed bytes. Publish 2.7.4 or redeploy a prior known-good immutable artifact.
31. Software Supply-Chain Security
Publishing a package is only one part of the problem. A secure software supply chain must answer:
- Where did this artifact come from?
- Which source revision produced it?
- Which workflow built it?
- Was the build trusted?
- Has the artifact changed?
- What dependencies are inside it?
- Is it deployed to production?
Security model
flowchart LR
A[Source] --> B[Trusted Workflow]
B --> C[Build]
C --> D[Package]
D --> E[Provenance]
D --> F[SBOM]
D --> G[Registry]
G --> H[Verification]
H --> I[Deployment]
I --> J[Runtime / Linked Artifact Context]
Core strategies
- Pin dependencies where practical.
- Use lockfiles.
- Protect release branches/tags/workflows.
- Minimize workflow token permissions.
- Pin third-party Actions for high-assurance builds.
- Use immutable package versions.
- Use container digests for deployment.
- Generate provenance attestations.
- Generate SBOMs.
- Scan dependencies and images.
- Record runtime/deployment context.
32. Artifact Attestations and Build Provenance
Artifact attestations establish verifiable information about where and how an artifact was built.
Why provenance matters
Without provenance, a package digest proves identity but not origin. Provenance connects an artifact to trusted build metadata such as repository, workflow, and commit.
Workflow permissions
A provenance workflow commonly needs:
permissions:
contents: read
id-token: write
attestations: write
For linked-artifact storage records using registry publishing, GitHub’s current linked-artifacts flow can also require:
artifact-metadata: write
Verify an attestation
For a built artifact:
gh attestation verify ./dist/my-binary \
-R acme/my-project
For a container, verification uses the subject image/digest and the repository/owner context supported by gh attestation verify.
SLSA
SLSA is a framework for reasoning about supply-chain security and provenance. GitHub attestations can provide signed build provenance that contributes to a stronger SLSA-oriented build process. Do not claim a particular SLSA level solely because an attestation exists; the complete build system and controls matter.
33. Software Bill of Materials (SBOM)
An SBOM is a machine-readable inventory of software components and dependencies.
Common formats include:
- SPDX
- CycloneDX
Why SBOMs matter
They help teams answer:
- Which components are inside this build?
- Do we use a vulnerable library?
- Which version is deployed?
- What should be reviewed during an incident?
Attesting an SBOM
After generating an SBOM file, GitHub Actions can attach an SBOM attestation. GitHub’s current documentation supports SPDX and CycloneDX predicate flows.
Example verification for an SPDX SBOM attestation:
gh attestation verify PATH/TO/ARTIFACT \
-R acme/repository \
--predicate-type https://spdx.dev/Document/v2.3
SBOM is not a vulnerability scan
An SBOM tells you what is present. Vulnerability-management tooling tells you whether known vulnerabilities affect those components. Use both.
34. Dependency Graph and Dependabot
The dependency graph summarizes dependencies detected from repository manifests/lockfiles and dependencies submitted by supported automation/API flows.
It can show:
- Direct dependencies
- Transitive dependencies where supported
- Versions
- Licenses
- Known vulnerabilities
- Dependents for public ecosystems where available
Dependency data sources
GitHub can populate the graph through:
- Static manifest/lockfile analysis
- Dependabot graph jobs where supported
- Automatic dependency submission
- Dependency submission API
Why lockfiles matter
A lockfile records the exact resolved dependency set. That improves reproducibility and gives security tooling better evidence than a broad version range alone.
Dependabot integration
Once dependency data is present, GitHub can use it for:
- Dependabot alerts
- Dependabot security updates
- Dependabot version updates
- Dependency review in pull requests
Private registry access
If Dependabot must resolve private dependencies, configure private registry access according to the ecosystem and repository security settings. Do not place credentials in the dependency manifest itself.
35. Package Vulnerability Management
A package-security response should connect advisories, releases, and consumers.
Example response flow
flowchart TD
A[Vulnerability identified] --> B[Assess affected versions]
B --> C[Fix source/dependency]
C --> D[Build + test]
D --> E[Publish patched version]
E --> F[Create advisory/release notes]
F --> G[Update consumers]
G --> H[Verify production rollout]
Practical controls
- Track CVEs and GitHub Security Advisories (GHSA).
- Publish patched versions rather than replacing existing versions.
- Use Dependabot/security campaigns where appropriate.
- Identify production impact using deployment/runtime context.
- Revoke compromised credentials immediately.
- Quarantine or delete malicious/accidentally published packages where safe and permitted.
- Preserve evidence during supply-chain incidents.
36. Linked Artifacts
Linked artifacts provide an organization-level view of software artifacts built with GitHub Actions, even when the artifact is stored outside GitHub Packages.
The linked artifacts page is metadata-centric: it does not act as the registry storing the artifact bytes.
What it connects
flowchart LR
A[GitHub Repository] --> B[GitHub Actions Build]
B --> C[Artifact]
C --> D[Storage Record]
C --> E[Deployment Record]
D --> F[Linked Artifacts]
E --> F
F --> G[Security / Audit / Policy Context]
Storage records
Storage records can describe:
- Source repository
- Artifact registry
- Artifact repository, where applicable
- Provenance/attestations
- Build information
Deployment records
Deployment records can include:
- Environment
- Cluster/runtime context
- Production status
- Runtime risks such as public internet exposure
- Sensitive-data context
GitHub explicitly distinguishes these records from the repository deployments dashboard; they are separate data sources.
Ways to populate linked artifacts
Current GitHub documentation lists:
- Artifact-attestation workflows
- JFrog Artifactory integration
- Dynatrace integration
- Microsoft Defender for Cloud integration
- Custom integrations using the artifact metadata REST API
GitHub currently marks the Microsoft Defender for Cloud integration as public preview.
Attestation-based storage records
GitHub documents automatic storage-record creation when the attestation action uses:
push-to-registry: true
and the workflow has:
permissions:
artifact-metadata: write
Why linked artifacts are useful
- Trace a built artifact to source.
- See where it is stored.
- See whether it is deployed.
- Prioritize Dependabot/code-scanning findings affecting production.
- Export evidence for audit/compliance.
- Associate runtime risk with source repositories.
37. Access Governance
A registry can become a shadow production system if package governance is weak.
Recommended access model
| Persona | Read | Write | Admin |
|---|---|---|---|
| Application developer | Yes | Usually no | No |
| Package maintainer | Yes | Yes | Sometimes |
| CI release workflow | Yes | Yes | Only when operationally required |
| Platform team | Yes | Controlled | Yes for shared packages |
| Consumer service | Yes | No | No |
| Security/audit automation | Metadata read | No | No |
Team-based permissions
Prefer team access over individual grants for organization packages. This reduces long-term permission drift.
Repository inheritance
Use inheritance when the package’s lifecycle matches one repository. Break inheritance when the package is a shared organization resource requiring a different audience.
38. Visibility Governance
Default posture
For proprietary packages:
- Start private/internal.
- Grant read access intentionally.
- Make a package public only after review.
Public package checklist
Before changing visibility to public:
- Confirm package contains no proprietary code.
- Confirm no embedded credentials/secrets.
- Confirm source/license metadata is correct.
- Confirm public distribution is legally approved.
- Confirm package name/namespace is correct.
- Confirm published versions cannot disclose internal build paths or sensitive metadata.
Public package deletion has additional constraints, so publication should be treated as potentially irreversible at scale.
39. Credential Governance
Preferred order
For GitHub Actions:
GITHUB_TOKEN- GitHub App token when a service/app model is needed and endpoint support exists
- PAT classic only when required by package-client or cross-boundary behavior
For developer package-client use, PAT classic remains the normal GitHub Packages model.
Rotation policy
Define:
- Owner
- Purpose
- Required scopes
- Expiry
- Rotation period
- Revocation procedure
- Incident response
Machine users
Do not create a machine user merely to avoid understanding package permissions. Prefer GitHub Apps or repository/organization automation patterns when they better match the use case.
40. Publication Governance
CI-only publishing
The strongest default is to publish production packages only from controlled CI.
Benefits:
- Reproducibility
- Centralized credentials
- Audit trail
- Consistent tests
- Provenance generation
- Easier policy enforcement
Protected release workflow
A common design:
flowchart TD
A[PR reviewed] --> B[Merge]
B --> C[CI passes]
C --> D[Create protected tag/release]
D --> E[Approval if required]
E --> F[Publish]
F --> G[Attest + SBOM]
G --> H[Promote immutable artifact]
Naming convention example
Libraries:
@acme/<domain>-<purpose>
Containers:
ghcr.io/acme/<service>
Java:
com.acme.<domain>:<artifact>
NuGet:
Acme.<Domain>.<Package>
41. Consumption Governance
Consumption is as important as publication.
Recommended controls
- Approved registries
- Approved package sources
- Lockfiles
- Version constraints
- Digest pinning for production containers
- Vulnerability review
- License review
- Source mapping where supported
- Explicit private registry authentication
Dependency confusion
A private package name can collide with a package in a public registry if source resolution is not controlled. Mitigations include scoped npm packages, NuGet package source mapping, explicit Maven/Gradle repository ordering, and private namespace standards.
42. CI/CD Design Patterns
Build → Test → Publish
The baseline pattern:
build → unit test → integration test → package → publish → verify
Release-driven publishing
Only release/tag events produce production packages.
Multi-job pipeline
flowchart LR
A[Build] --> B[Unit Tests]
B --> C[Integration Tests]
C --> D[Security Checks]
D --> E[Publish]
E --> F[Attest]
Environment-gated publish
GitHub Environments can add approvals or protection controls before a production publishing job executes.
Reusable workflow
Centralize release logic in a reusable workflow so many repositories inherit the same permission model, tagging rules, and attestation behavior.
43. Monorepo and Multi-Package Publishing
Challenges
- Which package changed?
- Which version should increment?
- Should all packages share one version?
- How do you avoid publishing unaffected packages?
Changed-path matrix pattern
strategy:
matrix:
package:
- api-client
- ui-kit
- config
Combine a matrix with changed-path detection or your monorepo tool of choice.
Independent versions
Best when packages have separate consumers and release cadences.
Shared versions
Best when packages form one tightly coupled product release.
Avoid publishing every package on every commit just because the repository contains many packages.
44. Container CI/CD Advanced Patterns
Multi-stage build
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
Advantages:
- Smaller runtime image
- Build tools absent from final image
- Reduced attack surface
Layer caching
BuildKit/Buildx can reuse unchanged layers. Cache configuration should never cause secrets or credentials to be baked into image layers.
Digest capture
The build/push action exposes the published digest. Save it as release metadata and use it for deployment/attestation.
Multi-architecture
Use Buildx and QEMU/native builders to publish AMD64 and ARM64 manifests under one tag.
Secure publishing
- Minimal
packages: write - Trusted trigger
- Protected source
- Pinned Actions
- Provenance
- SBOM
- Vulnerability scanning
- Digest-based deployment
45. Deployment Integration
GitHub Packages containers can be consumed by Docker and container orchestration platforms such as Kubernetes and cloud container services.
Kubernetes private image pull
A typical secret-based approach:
kubectl create secret docker-registry ghcr-pull \
--docker-server=ghcr.io \
--docker-username=YOUR_USERNAME \
--docker-password=YOUR_PAT_CLASSIC \
--namespace=my-app
Pod snippet:
apiVersion: v1
kind: Pod
metadata:
name: api
spec:
imagePullSecrets:
- name: ghcr-pull
containers:
- name: api
image: ghcr.io/acme/api@sha256:REPLACE_WITH_DIGEST
For production, evaluate workload identity, secret rotation, external secret managers, and platform-native registry integration rather than relying on manually maintained long-lived pull secrets.
Common deployment targets
| Target | Typical GitHub Packages use | Main consideration |
|---|---|---|
| Docker hosts | Pull GHCR image directly | Credential rotation and digest pinning |
| Kubernetes | imagePullSecrets or platform identity | Namespace secret scope and rotation |
| Amazon ECS / EKS | Deploy GHCR-hosted OCI image | Private-registry credentials / secret integration |
| Azure AKS / Container Apps | Deploy OCI image | Registry credentials and platform secret model |
| Google GKE / Cloud Run | Deploy OCI image | External-registry authentication and egress policy |
| Other OCI runtimes | Pull by tag or digest | Verify OCI compatibility and auth model |
GitHub Packages is the artifact source; each runtime still has its own secure secret, identity, network, and deployment controls.
46. Package Automation Patterns
Automated publishing
Triggers can include:
- Tag
- GitHub Release
- Protected branch
- Manual dispatch
- Schedule for rebuilds
Automated cleanup
A safe cleanup automation should:
- List versions.
- Classify protected releases.
- Identify candidates by age/count/tag.
- Print a dry-run report.
- Require approval for destructive classes if risk is high.
- Delete by immutable version ID.
- Log results.
Automated promotion
For containers, promotion should retag or record an existing digest rather than rebuild.
Event automation
Use package webhooks to trigger notifications, scanning, catalog registration, or downstream orchestration.
47. Troubleshooting Authentication
Authentication failures are usually caused by the wrong token type, insufficient scopes, SSO authorization, an incorrect registry endpoint, or a credential not being presented by the client.
| Symptom | Likely cause | Diagnose | Fix |
|---|---|---|---|
401 Unauthorized | Invalid/expired token | Re-authenticate; inspect token age/scopes | Replace/rotate token |
| Login succeeds but install fails | Missing read:packages or package ACL | Check package access and token scope | Add minimum read access |
| Publish denied | Missing write:packages or write role | Check token + package/repository permission | Grant write only where required |
| Works locally, fails in Actions | GITHUB_TOKEN permissions too narrow | Inspect workflow permissions | Add packages: read/write |
| Cross-repo package not found | Workflow repo not granted package access | Package settings → Actions access | Grant repo access |
| Organization access fails | PAT not SSO-authorized | Check org SSO authorization | Authorize token for org |
| Wrong endpoint | Client points to public/default registry | Inspect client config | Use correct GitHub Packages endpoint |
Diagnostic principle
Separate the layers:
- Can the client reach the registry?
- Is the credential accepted?
- Does the credential have the required scope?
- Does the underlying user/repository have package permission?
- Is the package in the expected namespace?
48. Troubleshooting Authorization
A valid token can still be unauthorized.
Read denied
Check:
- Package visibility
- User/team read role
- Repository inheritance
- Actions access grant
packages: readworkflow permission- Repository-scoped package permissions
Write denied
Check:
- Package write role
write:packagesPAT scopepackages: writeworkflow permission- Publishing namespace
- Repository ownership for Maven/Gradle
Admin denied
Deletion/access changes require admin-level control. A CI workflow should not receive admin unless its specific operation needs it.
Inheritance confusion
If a package inherits repository permissions, changing explicit package ACLs may not behave as expected until inherited access is removed. Understand which model is active before changing permissions.
49. Troubleshooting Publishing
| Problem | Likely cause | Better approach |
|---|---|---|
| Version already exists | Immutable version collision | Increment version; do not reuse release number |
| Namespace conflict | Wrong owner/package scope | Verify package name and owner |
| Package published to wrong repo | Incorrect source/destination metadata | Check repository/package config |
| npm publish goes to npmjs.org | .npmrc/publishConfig incorrect | Explicitly configure scope/registry |
| Maven deploy fails | distributionManagement/server ID mismatch | Match pom.xml server ID to settings.xml |
| Gradle credentials missing | Environment variables unavailable | Inject GITHUB_ACTOR/GITHUB_TOKEN |
| NuGet source not found | nuget.config source mismatch | Validate source URL/name |
| Container push denied | Wrong namespace/token permission | Validate ghcr.io/OWNER/IMAGE and write access |
Verify before publish
# npm
npm pack --dry-run
# Maven
mvn --batch-mode verify
# Gradle
./gradlew build
# .NET
dotnet pack --configuration Release
# Ruby
gem build your-package.gemspec
# Container
docker build -t local/test:verify .
50. Troubleshooting Installation and Dependency Resolution
Package not found
Possible causes:
- Wrong package name/version
- Wrong registry URL
- Package is private and authentication is missing
- Cross-repository access is missing
- Client is querying the public registry first/only
Dependency resolution failure
Inspect the package manager’s source configuration before blaming GitHub Packages.
Examples:
npm config list
mvn help:effective-settings
./gradlew repositories
dotnet nuget list source
bundle config list
Version mismatch
Use explicit versions while diagnosing. Avoid floating ranges or mutable tags until basic connectivity is confirmed.
51. Container Registry Troubleshooting
Login
echo "$CR_PAT" | docker login ghcr.io -u "$GITHUB_USER" --password-stdin
If login fails, verify the PAT classic, scopes, SSO authorization, and account name.
Image not found
docker pull ghcr.io/acme/api:1.0.0
Check:
- Owner capitalization/name
- Package visibility
- Tag existence
- Package ACL
- Authentication
Architecture mismatch
Symptom:
exec format error
Inspect image platforms:
docker buildx imagetools inspect ghcr.io/acme/api:1.0.0
Publish a multi-architecture image or deploy the matching platform.
Tag vs digest confusion
docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/acme/api:1.0.0
Use digests when validating whether two environments run identical content.
52. CI/CD Troubleshooting
Workflow permission error
Inspect the job’s effective permissions:
permissions:
contents: read
packages: write
Do not assume repository defaults provide write access.
Pull-request security
Do not publish packages from untrusted fork PR code using privileged secrets/tokens. Split validation from trusted publish jobs.
Self-hosted runners
A compromised self-hosted runner may expose credentials, source, build outputs, or package tokens. Isolate runner groups and use ephemeral runners for high-value release workloads where practical.
Version collisions
If many parallel jobs publish the same version, only one can logically own that immutable release. Generate the version once and coordinate publishing.
53. Registry Migration
A registry migration is not just copying files. It includes metadata, credentials, consumers, automation, access, and rollback.
Migration phases
flowchart LR
A[Inventory] --> B[Map namespaces/versions]
B --> C[Design access]
C --> D[Copy/publish artifacts]
D --> E[Update CI publishers]
E --> F[Update consumers]
F --> G[Validate]
G --> H[Freeze old registry]
H --> I[Retire]
Inventory
Capture:
- Packages
- Versions/tags/digests
- Owners
- Visibility
- Consumers
- CI publishers
- Credentials
- Retention rules
- Download/usage patterns
Consumer migration
Update:
- Registry URL
- Package source config
- Credentials
- Lockfiles if source metadata changes
- CI workflows
- Deployment pull secrets
Rollback
Keep the previous registry read-only for a defined period when business requirements allow. Do not delete source artifacts until consumers have been verified.
54. Legacy Docker Registry Migration
The legacy endpoint:
docker.pkg.github.com
has been replaced by:
ghcr.io
What changes
- Image naming
- Authentication configuration
- Workflow registry endpoint
- Package permission model
- Repository association behavior
- Consumer references
Example conceptual change
Old:
docker.pkg.github.com/acme/api/api:1.0.0
Modern:
ghcr.io/acme/api:1.0.0
Do not start new platform designs on the legacy Docker registry.
55. Observability, Activity, and Auditing
Package observability asks more than “is the registry up?”
Track:
- Package inventory
- Version count
- Publication activity
- Download activity where exposed
- Package ownership
- Repository links
- Storage usage
- Data transfer
- Deletion events
- Provenance
- Deployment context
Audit questions
- Who can publish this package?
- Which workflow published version
2.7.3? - Which commit produced this digest?
- Which repositories can consume it?
- Is it deployed to production?
- Does it have provenance?
- Is an SBOM available?
- Which vulnerable dependencies affect it?
Linked-artifact value
Linked artifacts help connect build, storage, provenance, and deployment metadata into an auditable view rather than forcing teams to reconstruct the story manually.
56. Organization Usage Reporting
At minimum, establish a periodic inventory report containing:
| Field | Why it matters |
|---|---|
| Package | Asset identity |
| Type | Registry/ecosystem |
| Visibility | Exposure risk |
| Owner | Accountability |
| Source repository | Traceability |
| Versions | Retention pressure |
| Last updated | Staleness |
| Production use | Business criticality |
| Provenance | Supply-chain assurance |
| Storage | Cost/capacity |
Simple REST inventory
gh api --paginate \
-H "X-GitHub-Api-Version: 2026-03-10" \
"/orgs/acme/packages?package_type=container&per_page=100" \
--jq '.[] | {name,visibility,version_count,updated_at,html_url}'
Repeat per package type as needed.
57. Best Practices
Package design
Recommended:
- Clear, stable names
- Organization ownership for company artifacts
- Semantic versions for libraries
- Immutable release versions
- Source repository linkage
- License/description metadata
- Installation documentation
- Explicit package owner/team
Avoid:
- Ambiguous names
- User-owned business-critical packages
- Version reuse
- Packages without source traceability
- Packages nobody owns
Security
Recommended:
- Least privilege
GITHUB_TOKENin Actions- Minimal PAT classic scopes
- Credential rotation
- Protected releases
- Pinned third-party Actions for high-assurance workflows
- Provenance
- SBOM
- Vulnerability scanning
- Digest-based production deployments
Avoid:
- Long-lived admin tokens
- Shared PATs
- Secrets in package config files
- Publishing from untrusted PR jobs
- Mutable-only production tags
CI/CD
Recommended:
- Test before publish
- Release/tag-driven production publishing
- Reusable workflows
- Build once, deploy many
- Automated cleanup with dry run
- Release metadata that captures digest/version
Avoid:
- Rebuilding per environment
- Manual production publishing as the normal path
- Publishing every commit forever without retention
Organization governance
Recommended:
- Team-based permissions
- Default visibility rules
- Naming policy
- Versioning policy
- Retention policy
- Usage monitoring
- Audit ownership
58. Common Anti-Patterns
| Anti-pattern | Why it is risky | Better approach |
|---|---|---|
| Hardcoded token | Credential leak | Secret manager / GITHUB_TOKEN |
| One PAT shared by team | No individual accountability | Team ACL + individual auth |
| Admin token for downloads | Excess privilege | Read-only token/access |
| Publish only from laptop | Weak reproducibility/audit | CI-first publishing |
| Reuse version numbers | Breaks immutability/trust | New version per change |
Only use latest | Cannot identify artifact | SemVer + SHA + digest |
| No source link | Weak traceability | Connect package to repo |
| Unlimited snapshots | Storage/cost noise | Retention policy |
| Public by accident | Data exposure | Private/internal default |
| No provenance | Hard to verify origin | Artifact attestations |
| No SBOM | Poor component visibility | Generate SBOM |
| Rebuild for prod | Different bytes than tested | Promote same immutable artifact |
59. Quick Reference Cheat Sheet
Registry endpoints
| Registry | Endpoint / pattern |
|---|---|
| Container | ghcr.io/OWNER/IMAGE |
| npm | https://npm.pkg.github.com |
| Maven | https://maven.pkg.github.com/OWNER/REPOSITORY |
| Gradle | Maven-compatible GitHub Packages repository URL |
| NuGet | https://nuget.pkg.github.com/NAMESPACE/index.json |
| RubyGems | https://rubygems.pkg.github.com/NAMESPACE |
Token scopes
read:packages
write:packages
delete:packages
Workflow permissions
Read:
permissions:
contents: read
packages: read
Publish:
permissions:
contents: read
packages: write
Attest:
permissions:
contents: read
packages: write
attestations: write
id-token: write
Container commands
docker login ghcr.io
docker build -t ghcr.io/acme/api:1.0.0 .
docker push ghcr.io/acme/api:1.0.0
docker pull ghcr.io/acme/api:1.0.0
npm
npm publish
npm install @acme/pkg@1.0.0
Maven
mvn --batch-mode verify
mvn --batch-mode deploy
Gradle
./gradlew build
./gradlew publish
NuGet
dotnet pack --configuration Release
dotnet nuget push PACKAGE.nupkg --source github --api-key TOKEN
RubyGems
gem build package.gemspec
gem push --host https://rubygems.pkg.github.com/NAMESPACE package.gem
REST with GitHub CLI
gh api "/orgs/ORG/packages?package_type=container"
gh api "/orgs/ORG/packages/container/NAME/versions"
60. Beginner → Intermediate → Advanced Learning Map
| Level | Learn and practice |
|---|---|
| Beginner | Registry purpose, supported ecosystems, auth, publish/install, visibility, versions |
| Intermediate | Actions integration, cross-repo access, REST API, cleanup, billing, repository links |
| Advanced | Build-once promotion, attestations, SBOM, linked artifacts, governance, migration, organization reporting |
Recommended training order
- Publish/pull one public or private container.
- Publish one language package.
- Move publishing into GitHub Actions.
- Add a separate consumer repository.
- Add versioning and release triggers.
- Add provenance/SBOM.
- Add retention automation.
- Build an organization-level governance model.
61. Hands-On Exercises
Exercise 1 — Beginner: Publish a container
Objective: Publish and pull a versioned container from GHCR.
Tasks:
- Create a repository with a small Dockerfile.
- Build
1.0.0locally. - Authenticate to GHCR.
- Push the image.
- Open the package page.
- Pull the image on a clean machine/session.
- Record the digest.
Expected outcome: a package exists under the intended owner and can be pulled using the expected access model.
Exercise 2 — Intermediate: CI package publishing
Objective: Publish from GitHub Actions without storing a PAT.
Tasks:
- Create a release-triggered workflow.
- Set
contents: readandpackages: write. - Authenticate with
GITHUB_TOKEN. - Run tests before publishing.
- Publish the package.
- Verify the package version from a second workflow.
Expected outcome: publishing is reproducible and tied to a GitHub workflow run.
Exercise 3 — Intermediate: Cross-repository consumption
Objective: Allow a consumer repository to read a private granular package.
Tasks:
- Publish a private package from repo A.
- Add repo B under package Actions access.
- Give repo B read access only.
- Set
packages: readin repo B workflow. - Install/pull the package.
- Remove access and observe the failure.
- Restore read access.
Expected outcome: students understand package ACLs separately from token scopes.
Exercise 4 — Advanced: Immutable promotion
Objective: Demonstrate build once, deploy many.
Tasks:
- Build and push a container.
- Capture digest.
- Deploy that digest to dev.
- Promote the same digest to stage.
- Verify both environments use identical digest.
- Promote to production.
Expected outcome: no environment performs a rebuild.
Exercise 5 — Security: Provenance and SBOM
Objective: Add supply-chain evidence.
Tasks:
- Add artifact-attestation permissions.
- Generate provenance after publishing.
- Generate an SPDX or CycloneDX SBOM.
- Attest the SBOM.
- Verify the artifact with GitHub CLI.
- Inspect the attestation output.
Expected outcome: students can prove where an artifact came from and inspect its component inventory.
62. Complete Practical Project — Organization Package Platform
Requirement
Design a small but production-oriented GitHub Packages platform for an organization named acme.
The organization has:
payments-api— containerized serviceorders-api— containerized serviceshared-types— npm packagepayments-sdk— Maven package- Staging and production environments
- GitHub Actions for CI/CD
- A requirement for least privilege, traceability, reproducibility, and cleanup
Target architecture
flowchart TB
subgraph Source
P[payments-api]
O[orders-api]
N[shared-types]
M[payments-sdk]
end
P --> PA[Payments CI]
O --> OA[Orders CI]
N --> NA[npm CI]
M --> MA[Maven CI]
PA --> GHCR[(GHCR)]
OA --> GHCR
NA --> NPM[(GitHub npm Registry)]
MA --> MAVEN[(GitHub Maven Registry)]
GHCR --> STG[Staging]
GHCR --> PROD[Production]
PA --> AT[Attestations]
OA --> AT
AT --> LA[Linked Artifacts]
STG --> LA
PROD --> LA
Design decisions
- Containers use
ghcr.io/acme/<service>. - Production image deployment uses digest references.
- npm package is organization scoped with granular permissions.
- Maven package remains repository scoped.
- Publishing happens only through trusted workflows.
- Workflows use
GITHUB_TOKENwherever the package access model allows it. - Production publishing/deployment requires protected release controls.
- Production containers receive provenance attestations.
- SBOMs are generated for production images.
- Snapshot/dev package retention is automated.
Repository structure
payments-api/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── release.yml
├── src/
├── tests/
├── Dockerfile
└── README.md
Release workflow
name: Release container
on:
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
release:
runs-on: ubuntu-latest
environment: production-release
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v6
- name: Test
run: ./scripts/test.sh
- name: Login
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=sha
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Attest
uses: actions/attest@v4
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Save digest
run: echo '${{ steps.build.outputs.digest }}' > image-digest.txt
Staging deployment
Consume the digest produced by the release pipeline rather than rebuilding:
containers:
- name: payments-api
image: ghcr.io/acme/payments-api@sha256:RELEASE_DIGEST
Production promotion
The production change references the exact same digest that passed staging validation.
npm shared package access
shared-types is granted:
- Write: owning platform/package-maintainer team
- Read:
payments-apiandorders-apirepositories through Actions access - Admin: small package-owner group
Consumer workflow:
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: '20.x'
registry-url: 'https://npm.pkg.github.com'
scope: '@acme'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Cleanup policy
- Production release versions: protected from automated deletion.
- Release candidates: keep last 10.
- Branch builds: keep 14 days.
- Pull-request builds: keep 7 days after PR close.
- Cleanup workflow first generates a report, then deletes only approved candidates.
Validation checklist
- Package visible under correct organization/repository.
- Source connection correct.
- Producer has write; consumers have read only.
- Workflow succeeds without long-lived PAT.
- Release tags/version match policy.
- Container digest recorded.
- Staging and production use same digest.
- Provenance verifies.
- SBOM is retrievable/inspectable.
- Cleanup excludes protected releases.
- Audit owner is documented.
Improvement ideas
- Pin all non-GitHub Actions to reviewed full commit SHAs.
- Add reusable organization release workflows.
- Add policy-as-code around naming and allowed registries.
- Integrate linked artifact deployment metadata.
- Add runtime risk context through supported integrations/API.
- Export monthly package inventory and orphan-package report.
63. Interview and Knowledge-Check Questions
Conceptual
- What problem does GitHub Packages solve?
- How is a package registry different from GitHub Releases?
- Which GitHub Packages registries support granular permissions?
- Which registries are repository scoped?
- What is the difference between package visibility and package role?
- What is the difference between a container tag and digest?
- Why is
GITHUB_TOKENusually preferable to a PAT in GitHub Actions? - Why should release versions be immutable?
- What is repository permission inheritance?
- What happens to granular packages when a connected repository is transferred?
Scenario based
- A workflow in repo B must install a private package published from repo A. How would you design access?
- A team deploys
latestto production. What risks does that introduce? - A package has 100 old development versions. How would you automate cleanup safely?
- An npm package publishes to npmjs.org instead of GitHub Packages. What would you inspect?
- A container works on AMD64 but fails on ARM64. How would you diagnose it?
- A developer can authenticate to GHCR but cannot push. What layers of authorization do you check?
- You must prove which commit produced a production image. Which GitHub features help?
- You are migrating from
docker.pkg.github.comtoghcr.io. What must change beyond image copying?
Troubleshooting
- What is the difference between HTTP 401 and a permission-denied workflow failure?
- Why can a valid PAT still fail to download a package?
- How can repository transfer break package consumption?
- Why can rebuilding for production break provenance even when the Git commit is unchanged?
- When would REST be preferred over GraphQL for packages?
- What should be checked before deleting a public package?
64. FAQ
Do GitHub Packages support public and private packages?
Yes, with behavior depending on registry, permission model, and account/plan context. Granular registries can support independent visibility settings. Repository-scoped packages follow their repository.
Can I pull a public package without authentication?
For public GHCR container images, yes. Most other GitHub Packages registries still require authentication even when the package is public.
Can I use a fine-grained PAT to authenticate npm/Docker/Maven package clients?
GitHub’s package-client documentation continues to specify personal access token classic for GitHub Packages authentication. Some REST endpoints separately support GitHub App or fine-grained token types. Treat those as different authentication surfaces.
Should I use GITHUB_TOKEN or a PAT in Actions?
Prefer GITHUB_TOKEN when the package/repository access model supports the operation. It is automatically generated, short lived, and can be permission-scoped in the workflow.
Can one organization package be consumed by many repositories?
Yes for granular registries. Grant the required repositories read access through package settings/Actions access.
Are Maven and Gradle packages organization-scoped with independent ACLs?
No. Their GitHub Packages permission model is repository scoped.
Can I delete any public package?
Not always. GitHub documents a 5,000-download restriction for public package/version deletion; higher-download cases require GitHub Support.
How long can deleted packages be restored?
GitHub documents a 30-day restoration window, provided the required namespace/version conditions remain satisfied.
Is GHCR storage permanently free?
Do not assume that. As of September 2026, GitHub documents Container registry storage and bandwidth as currently free and says notice will be given before a policy change.
What is the safest container deployment reference?
An immutable digest, ideally connected to provenance and release metadata.
Do linked artifacts store my packages?
No. Linked artifacts store/aggregate metadata about artifacts, storage, provenance, and deployments. The artifact itself can live in GitHub Packages or an external registry.
What is the difference between an SBOM and an attestation?
An SBOM inventories components. An attestation is a signed statement about an artifact, such as its build provenance or an SBOM statement.
Should production packages be published from developer laptops?
Prefer controlled CI for normal production publishing because it improves reproducibility, auditing, permissions, and provenance.
65. Topic Coverage Map
The source curriculum contained 123 topic groups. This guide consolidates related topics into teachable chapters rather than repeating the same concepts under many headings.
| Source topic groups | Covered primarily in |
|---|---|
| 1–4 | Sections 1–4 |
| 5–10 | Sections 2–5 |
| 11–14 | Section 6 |
| 15–19 | Sections 7–9, 19 |
| 20–25 | Sections 15, 18 |
| 26–32 | Sections 8–9, 17, 44, 54 |
| 33 | Section 10 |
| 34 | Section 11 |
| 35 | Section 12 |
| 36 | Section 13 |
| 37 | Section 14 |
| 38–45 | Sections 16–17, 42–44 |
| 46–48 | Section 19, 46 |
| 49–51 | Sections 20–22 |
| 52–53 | Sections 23–24 |
| 54–56 | Sections 5, 18, 26–27 |
| 57–61 | Section 25 |
| 62–65 | Sections 28–29 |
| 66–68 | Section 30 |
| 69–74 | Sections 31–35 |
| 75–79 | Section 36 |
| 80–84 | Sections 37–41 |
| 85–89 | Sections 42–43 |
| 90–93 | Sections 44–45 |
| 94–97 | Section 46 |
| 98–103 | Sections 47–52 |
| 104–106 | Sections 53–54 |
| 107–109 | Sections 55–56 |
| 110–114 | Section 57 |
| 115 | Section 58 |
| 116–123 | Sections 16, 23–24, 31–36, 59, and references |
66. Summary
You should now be able to:
- Explain GitHub Packages and its registry architecture.
- Distinguish granular from repository-scoped package permissions.
- Publish and consume containers, npm, Maven, Gradle, NuGet, and RubyGems packages.
- Use PAT classic safely for package clients.
- Use
GITHUB_TOKENfor package operations in GitHub Actions. - Design cross-repository package access.
- Connect granular packages to source repositories.
- Version and promote immutable artifacts.
- Automate publication, cleanup, and reporting with Actions and the REST API.
- Understand deletion/restoration rules.
- Manage billing/storage considerations.
- Generate provenance attestations and SBOMs.
- Use dependency graph and Dependabot as part of supply-chain security.
- Understand linked artifacts and production context.
- Troubleshoot authentication, authorization, publishing, installation, container, and CI failures.
- Plan registry and legacy Docker migrations.
- Establish organization-level package governance.
Recommended next steps
- Build the complete practical project in a sandbox organization.
- Add a second consumer repository and test least-privilege access.
- Replace mutable deployment tags with digests.
- Add provenance and an SBOM.
- Create an inventory/cleanup workflow using the REST API.
- Document your organization’s naming, publication, retention, and visibility policy.
- Review your actual GitHub plan and enterprise settings before turning the examples into production standards.
67. Authoritative References
The guide was verified primarily against current GitHub documentation in September 2026.
GitHub Packages fundamentals
- GitHub Packages documentation
- Introduction to GitHub Packages
- About permissions for GitHub Packages
- Configuring a package’s access control and visibility
- Connecting a repository to a package
- Viewing packages
- Deleting and restoring a package
Registry documentation
- Working with the Container registry
- Working with the npm registry
- Working with the Apache Maven registry
- Working with the Gradle registry
- Working with the NuGet registry
- Working with the RubyGems registry
- Working with the legacy Docker registry
GitHub Actions and automation
- Publishing packages with GitHub Actions
- Publishing Docker images
- Publishing Node.js packages
- Publishing Java packages with Maven
- Publishing Java packages with Gradle
API, webhooks, and billing
Supply-chain security
- Dependency graph
- How the dependency graph recognizes dependencies
- Using artifact attestations
- About linked artifacts
- Uploading linked artifact storage and deployment data
GitHub features, limits, plan entitlements, action versions, and preview status can change. Re-check the authoritative documentation before production rollout or compliance sign-off.