Last Verified: September 2026
Scope: GitHub.com / GitHub Enterprise Cloud unless explicitly stated otherwise
Duration: ~2 hours including hands-on practice
Audience: Developers, DevOps engineers, platform engineers, administrators, trainers, and students
Primary hands-on registry: GitHub Container Registry (GHCR)
GitHub Packages is GitHub’s package-hosting platform. It lets teams publish, store, secure, discover, and consume software packages and container images while keeping source code, CI/CD, permissions, and package ownership close to the GitHub workflow.
This Essentials tutorial intentionally does not attempt to cover every advanced capability. It focuses on the smallest set of concepts and practical skills a learner should understand before working with GitHub Packages in real projects.
1. What You Should Be Able to Do After 2 Hours
By the end of this tutorial, you should be able to:
- Explain what GitHub Packages is and when to use it.
- Distinguish GitHub Packages from GitHub Releases and GitHub Actions artifacts.
- Identify the six main GitHub Packages registries.
- Understand granular packages vs repository-scoped packages.
- Explain package visibility and Read / Write / Admin permissions.
- Authenticate using PAT classic locally and
GITHUB_TOKENin GitHub Actions. - Build, publish, and pull a container image from GHCR.
- Publish a container automatically using GitHub Actions.
- Understand cross-repository package consumption.
- Apply essential versioning, security, and troubleshooting practices.
2. Recommended 2-Hour Learning Flow
| Time | Topic | Outcome |
|---|---|---|
| 0–10 min | What GitHub Packages is | Understand the problem it solves |
| 10–25 min | Registries and package model | Know supported ecosystems and permission models |
| 25–40 min | Authentication and permissions | Understand PAT classic, GITHUB_TOKEN, package roles |
| 40–65 min | Hands-on GHCR | Build, push, verify, and pull an image |
| 65–90 min | GitHub Actions publishing | Automate build-test-publish |
| 90–105 min | Cross-repository access | Understand producer/consumer package access |
| 105–115 min | Security + versioning + troubleshooting | Learn production habits and common failures |
| 115–120 min | Knowledge check + cheat sheet | Reinforce essential commands and decisions |
Part 1 — Understand GitHub Packages
3. What Is GitHub Packages?
A package is a reusable software artifact that another developer, application, build, or deployment can consume.
Examples:
- Docker / OCI container image
- npm package
- Maven artifact
- Gradle-published Java library
- NuGet package
- Ruby gem
GitHub Packages provides registries for storing and distributing these artifacts.
Why do teams need a package registry?
Source code alone is not what applications deploy or consume.
A typical delivery path looks like this:
flowchart LR
A[Source Code] --> B[Build]
B --> C[Test]
C --> D[Package]
D --> E[Publish]
E --> F[GitHub Packages]
F --> G[Install / Pull]
G --> H[Application / Deployment]
Code language: CSS (css)
A registry sits between the producer and the consumer.
- Producer: creates and publishes a package.
- Registry: stores package versions.
- Consumer: installs or pulls a package.
Typical real-world example
A team builds a service called payments-api.
Instead of deploying directly from source code, CI builds a container image:
ghcr.io/acme/payments-api:1.4.2
That image is stored in GitHub Container Registry and later pulled by Kubernetes or another deployment platform.
4. GitHub Packages vs Releases vs Actions Artifacts
These three GitHub features solve different problems.
| Feature | Primary purpose | Best example |
|---|---|---|
| GitHub Packages | Reusable dependencies and deployable packages | ghcr.io/acme/api:1.2.0 |
| GitHub Releases | Release event, notes, downloadable assets | v1.2.0 release with binaries |
| GitHub Actions artifacts | Temporary workflow outputs | Test reports, build logs, intermediate files |
Simple rule
Use GitHub Packages when another build, developer, application, or deployment will consume the artifact as a package.
Use GitHub Releases when publishing release notes and downloadable release assets.
Use Actions artifacts for files mainly associated with a workflow run.
Part 2 — Registries and Permission Models
5. Supported GitHub Packages Registries
GitHub Packages supports six principal package ecosystems.
| Ecosystem | Endpoint | Typical client | Permission model |
|---|---|---|---|
| Container | ghcr.io | Docker / OCI | Granular user/org scoped |
| npm | npm.pkg.github.com | npm / Yarn | Granular user/org scoped |
| NuGet | nuget.pkg.github.com | dotnet / NuGet | Granular user/org scoped |
| RubyGems | rubygems.pkg.github.com | gem / Bundler | Granular user/org scoped |
| Maven | maven.pkg.github.com | Maven | Repository scoped |
| Gradle | Maven-compatible endpoint | Gradle | Repository scoped |
The old Docker registry endpoint:
docker.pkg.github.com
Code language: CSS (css)
is legacy. New designs should use:
ghcr.io
Code language: CSS (css)
6. The Most Important Architecture Concept: Granular vs Repository-Scoped
This distinction changes how access control works.
flowchart TD
A[GitHub Package] --> B{Permission model}
B -->|Granular| C[User or Organization Scope]
C --> D[Independent package permissions]
C --> E[Optional repository connection]
B -->|Repository scoped| F[Repository]
F --> G[Repository permissions]
F --> H[Repository visibility]
Granular packages
Supported by:
- Container
- npm
- NuGet
- RubyGems
These packages can have access rules independent of a repository.
Example:
A package can be owned by organization acme, while five different repositories receive read access.
Repository-scoped packages
Supported by:
- Maven
- Gradle
These packages inherit the repository’s permissions and visibility.
Remember this
Container/npm/NuGet/RubyGems = package-level access model.
Maven/Gradle = repository permission model.
This is one of the most common GitHub Packages design mistakes.
7. Package Visibility and Roles
Package roles
| Role | Typical capability |
|---|---|
| Read | View metadata and download/install |
| Write | Read + publish/upload |
| Admin | Manage package, access, versions, deletion |
Visibility
Depending on registry and account context, packages may be:
- Public
- Private
- Internal
For proprietary company packages, a safe starting point is:
Private/Internal -> explicitly grant access -> make public only after review
Code language: PHP (php)
Important GHCR exception
Public GHCR container images can be pulled anonymously.
Most other GitHub Packages package-client workflows still require authentication even when a package is public.
Part 3 — Authentication
8. PAT Classic vs GITHUB_TOKEN
Authentication is where many first-time GitHub Packages users get confused.
Local package-client access
For package clients, GitHub documents personal access token (classic) authentication.
Important scopes:
| Scope | Use |
|---|---|
read:packages | Download/install |
write:packages | Publish/upload |
delete:packages | Delete package/version |
Example local GHCR login:
export CR_PAT="YOUR_PAT_CLASSIC"
echo "$CR_PAT" | docker login ghcr.io \
-u YOUR_GITHUB_USERNAME \
--password-stdin
Code language: JavaScript (javascript)
Do not hardcode the token inside scripts committed to Git.
Inside GitHub Actions
Prefer GITHUB_TOKEN whenever the package access model supports the operation.
Read-only workflow:
permissions:
contents: read
packages: read
Publishing workflow:
permissions:
contents: read
packages: write
Why GITHUB_TOKEN is preferred in Actions
It is:
- Automatically generated
- Short lived
- Repository/workflow scoped
- Permission controllable
- Easier to audit than a shared long-lived PAT
Security rule
Local package client -> PAT classic when required
GitHub Actions -> GITHUB_TOKEN whenever possible
Part 4 — Hands-On: Publish a Container to GHCR
9. Lab 1 — Build and Publish Manually
Objective
Publish a versioned Docker image to GitHub Container Registry and pull it back successfully.
Prerequisites
You need:
- GitHub account
- GitHub repository
- Docker
- Permission to publish to the target namespace
- PAT classic with appropriate package scope
For examples below, replace:
acme
with your GitHub username or organization.
Step 1 — Create a simple application
Create index.html:
<h1>Hello from GitHub Packages</h1>
Code language: HTML, XML (xml)
Create Dockerfile:
FROM nginx:alpine
COPY ./index.html /usr/share/nginx/html/index.html
LABEL org.opencontainers.image.source="https://github.com/acme/hello-packages"
LABEL org.opencontainers.image.description="GitHub Packages Essentials demo"
Code language: JavaScript (javascript)
The source label helps GitHub associate the image with its source repository.
Step 2 — Authenticate to GHCR
export CR_PAT="YOUR_PAT_CLASSIC"
echo "$CR_PAT" | docker login ghcr.io \
-u YOUR_GITHUB_USERNAME \
--password-stdin
Code language: JavaScript (javascript)
Expected result:
Login Succeeded
Step 3 — Build the image
docker build -t hello-packages:1.0.0 .
Code language: CSS (css)
Verify locally:
docker images
Step 4 — Tag the image for GHCR
docker tag hello-packages:1.0.0 \
ghcr.io/acme/hello-packages:1.0.0
Image naming pattern:
ghcr.io/OWNER/IMAGE:TAG
Step 5 — Push the image
docker push ghcr.io/acme/hello-packages:1.0.0
Expected result:
The image layers are uploaded and GitHub creates or updates the package.
Step 6 — Verify in GitHub
Open the owner account or organization and navigate to Packages.
Confirm:
- Package exists
- Version
1.0.0exists - Source repository is correct
- Visibility is correct
- Package metadata is visible
Step 7 — Pull the image
docker pull ghcr.io/acme/hello-packages:1.0.0
Run it:
docker run --rm -p 8080:80 \
ghcr.io/acme/hello-packages:1.0.0
Open:
http://localhost:8080
Code language: JavaScript (javascript)
Expected result:
Hello from GitHub Packages
Code language: JavaScript (javascript)
10. Tag vs Digest
A container can be referenced using a tag:
ghcr.io/acme/hello-packages:1.0.0
or an immutable digest:
ghcr.io/acme/hello-packages@sha256:...
Why digest matters
Tags are human-friendly references. A digest identifies exact content.
For production deployments, digest pinning gives the strongest guarantee that the artifact being deployed is exactly the artifact that was tested.
Recommended release tags
A mature image may have:
2.4.1
2.4
2
sha-a1b2c3d
Code language: CSS (css)
Avoid using only:
latest
for production.
Part 5 — Automate Publishing with GitHub Actions
11. Why Move Publishing into CI?
Manual publishing is useful for learning, but production publishing should normally happen in CI.
Benefits:
- Reproducible build
- Automated testing
- Centralized credentials
- Audit trail
- Controlled permissions
- Easier provenance and security controls
Recommended lifecycle:
flowchart LR
A[Commit / Release] --> B[Checkout]
B --> C[Build]
C --> D[Test]
D --> E[Login]
E --> F[Publish]
F --> G[Verify]
Code language: CSS (css)
12. Lab 2 — Publish GHCR Image Using GitHub Actions
Create:
.github/workflows/publish.yml
Add:
name: Publish container
on:
release:
types: [published]
permissions:
contents: read
packages: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image
run: |
docker build \
-t ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} \
.
- name: Push image
run: |
docker push \
ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }}
Code language: HTTP (http)
What this workflow does
- Starts when a GitHub Release is published.
- Checks out the source code.
- Authenticates to GHCR using
GITHUB_TOKEN. - Builds the container.
- Tags it using the release tag.
- Publishes it to GHCR.
Why the permissions block matters
permissions:
contents: read
packages: write
The workflow receives only the access it needs.
Do not assume the default token permissions are sufficient.
13. Better Production Pattern: Test Before Publish
Publishing should depend on successful tests.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: ./scripts/test.sh
publish:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
<em># login, build, and publish steps</em>
Code language: HTML, XML (xml)
The key line is:
needs: test
Code language: HTTP (http)
If testing fails, publishing does not start.
Essential release rule
Build -> Test -> Package -> Publish
not:
Build -> Publish -> Hope tests pass
Part 6 — Cross-Repository Package Access
14. Producer and Consumer Repositories
Real organizations often separate the repository that creates a package from the repositories that consume it.
flowchart LR
A[Producer Repo] --> B[GitHub Actions]
B --> C[Organization Package]
C --> D[Consumer Repo A]
C --> E[Consumer Repo B]
C --> F[Consumer Repo C]
Code language: CSS (css)
Example:
shared-types repo
|
v
@acme/shared-types
|
+----+----+
| |
payments orders
For granular packages, GitHub lets you grant selected repositories access through package settings.
Typical cross-repository flow
- Publish the package from repository A.
- Open the package settings.
- Find Manage Actions access.
- Add repository B.
- Grant only the required role.
- In repository B, configure:
permissions:
contents: read
packages: read
- Consume the package using
GITHUB_TOKEN.
Critical concept
Token scope alone is not enough.
A workflow can have:
packages: read
Code language: HTTP (http)
and still fail if the repository itself has not been granted access to the package.
Think of authorization as two layers:
Workflow token permission
+
Package/repository access
=
Successful package operation
Part 7 — Understand the Other Registries
15. Essential Ecosystem Examples
You do not need to master every registry during this two-hour session. You should understand the configuration pattern.
npm
Package name:
{
"name": "@acme/shared-utils",
"version": "1.2.0"
}
Code language: JSON / JSON with Comments (json)
.npmrc:
@acme:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
Code language: JavaScript (javascript)
Publish:
npm publish
Install:
npm install @acme/shared-utils@1.2.0
Code language: CSS (css)
Maven
Endpoint pattern:
https://maven.pkg.github.com/OWNER/REPOSITORY
Code language: JavaScript (javascript)
Publish:
mvn --batch-mode deploy
Important: Maven packages are repository scoped.
Gradle
Publish:
./gradlew publish
Important: GitHub Gradle packages use Maven-format publishing and are repository scoped.
NuGet
Add source:
dotnet nuget add source \
--name github \
"https://nuget.pkg.github.com/acme/index.json"
Code language: JavaScript (javascript)
Publish:
dotnet nuget push PACKAGE.nupkg \
--source github \
--api-key YOUR_PAT_CLASSIC
Code language: CSS (css)
RubyGems
Registry pattern:
https://rubygems.pkg.github.com/NAMESPACE
Code language: JavaScript (javascript)
Build:
gem build package.gemspec
Code language: CSS (css)
Publish:
gem push \
--host https://rubygems.pkg.github.com/acme \
package.gem
Code language: JavaScript (javascript)
Part 8 — Essential Security and Release Practices
16. The 10 Rules Worth Remembering
- Prefer organization ownership for company packages.
- Prefer
GITHUB_TOKENinstead of long-lived PATs in Actions. - Use minimum token permissions.
- Publish production packages from trusted CI.
- Test before publishing.
- Do not reuse release version numbers.
- Use SemVer for libraries where appropriate.
- Use immutable container digests for production deployment.
- Do not store credentials in repository files.
- Grant consumers Read access, not Write/Admin access.
Good vs risky
| Risky | Better |
|---|---|
| Shared PAT | Individual auth or GITHUB_TOKEN |
| Admin token for downloads | Read-only access |
| Publish manually from laptop | Controlled CI publishing |
Deploy latest | Deploy version + digest |
| Rebuild for each environment | Build once, promote same artifact |
| Unlimited dev versions | Retention/cleanup policy |
| Package with no source link | Connect package to source repository |
17. Build Once, Deploy Many
A mature delivery process promotes the same artifact through environments.
flowchart LR
A[Commit] --> B[Build + Test]
B --> C[Immutable Package]
C --> D[Dev]
D --> E[Stage]
E --> F[Production]
Code language: CSS (css)
Do not rebuild independently for staging and production.
Why?
Because two builds from the same Git commit can still contain different dependency resolutions, timestamps, base image layers, or build-environment effects.
For containers, promote the same digest.
Good:
ghcr.io/acme/api@sha256:abc123...
Riskier:
ghcr.io/acme/api:latest
Part 9 — Troubleshooting
18. Fast Troubleshooting Matrix
| Problem | Likely cause | Check |
|---|---|---|
401 Unauthorized | Invalid/expired credential | Token, login, SSO authorization |
| Pull/install denied | Missing read access | Package ACL + packages: read |
| Push denied | Missing write access | Package role + packages: write |
| Works locally, fails in Actions | Workflow token too restricted | permissions: block |
| Cross-repo package not found | Consumer repo not granted access | Manage Actions access |
| Image tag not found | Wrong tag or owner | Package page and image reference |
exec format error | Architecture mismatch | Image platforms |
| Version already exists | Immutable version collision | Increment version |
| npm publishes to npmjs.org | Registry config incorrect | .npmrc / package config |
Troubleshooting order
When package access fails, check in this order:
1. Correct registry endpoint?
2. Correct package name/version?
3. Authentication working?
4. Token has required scope/permission?
5. User/repository has package access?
6. Workflow permissions correct?
7. Cross-repository access configured?
This sequence prevents random trial-and-error debugging.
Part 10 — Five-Minute Final Practice
19. Mini Exercise
Assume:
- Organization:
acme - Producer repository:
payments-api - Package:
ghcr.io/acme/payments-api - Consumer repository:
deployment-platform
Answer the following before checking the solution.
Questions
- Which registry stores the image?
- Is GHCR granular or repository scoped?
- What local token type should you use for Docker authentication?
- What workflow permission is required to publish?
- What workflow permission is required only to pull?
- What else must be configured when another repository consumes a private package?
- Which is safer for production:
:latestor@sha256:...? - Should production rebuild the image after staging approval?
Answers
- GitHub Container Registry /
ghcr.io. - Granular user/organization scoped.
- PAT classic with the required package scope.
packages: write.packages: read.- Grant the consumer repository package/Actions access.
- Immutable digest:
@sha256:.... - No. Promote the same tested artifact.
Part 11 — Essential Cheat Sheet
20. Registry Endpoints
| Registry | Endpoint |
|---|---|
| Container | ghcr.io/OWNER/IMAGE |
| npm | https://npm.pkg.github.com |
| Maven | https://maven.pkg.github.com/OWNER/REPOSITORY |
| Gradle | Maven-compatible GitHub Packages endpoint |
| NuGet | https://nuget.pkg.github.com/NAMESPACE/index.json |
| RubyGems | https://rubygems.pkg.github.com/NAMESPACE |
21. PAT Classic Scopes
read:packages
write:packages
delete:packages
Code language: CSS (css)
22. Workflow Permissions
Read:
permissions:
contents: read
packages: read
Publish:
permissions:
contents: read
packages: write
23. GHCR Commands
Login:
echo "$CR_PAT" | docker login ghcr.io \
-u "$GITHUB_USER" \
--password-stdin
Code language: PHP (php)
Build:
docker build -t ghcr.io/acme/api:1.0.0 .
Push:
docker push ghcr.io/acme/api:1.0.0
Pull:
docker pull ghcr.io/acme/api:1.0.0
Pull immutable digest:
docker pull ghcr.io/acme/api@sha256:REPLACE_WITH_DIGEST
Part 12 — What to Learn Next
24. Topics Intentionally Left for the Complete Guide
After completing this Essentials tutorial, move to the full reference guide for deeper study of:
- Package deletion and restoration
- REST API and
gh api - Webhooks
- GitHub Apps
- Billing and storage management
- Multi-architecture builds
- Automated cleanup
- Registry migration
- Legacy Docker registry migration
- Artifact attestations
- SBOM
- Dependency graph and Dependabot
- Linked artifacts
- Enterprise-specific behavior
- Organization governance
- Audit and usage reporting
These are important topics, but they are better studied after the package lifecycle, authentication model, permissions, GHCR workflow, and CI publishing process are understood.
Part 13 — Wrap-Up
25. Final Mental Model
If you remember only one flow from this tutorial, remember this:
flowchart LR
A[Source] --> B[Build]
B --> C[Test]
C --> D[Package]
D --> E[Publish with least privilege]
E --> F[GitHub Packages]
F --> G[Authorized Consumer]
G --> H[Deploy exact version/digest]
Code language: CSS (css)
And remember these four rules:
1. Know the registry permission model.
2. Use the smallest required permission.
3. Publish from trusted CI.
4. Promote immutable artifacts.
Code language: JavaScript (javascript)
If those four ideas are clear, you have the foundation needed to work safely and effectively with GitHub Packages.
26. Two-Hour Completion Checklist
Before considering the Essentials session complete, verify that the student can answer Yes to the following:
- [ ] I can explain what GitHub Packages is.
- [ ] I know when to use Packages vs Releases vs Actions artifacts.
- [ ] I know the six supported package ecosystems.
- [ ] I know which registries are granular and which are repository scoped.
- [ ] I understand Read / Write / Admin package roles.
- [ ] I understand PAT classic vs
GITHUB_TOKEN. - [ ] I can log in to GHCR.
- [ ] I can build, tag, push, and pull an image.
- [ ] I can publish an image using GitHub Actions.
- [ ] I understand cross-repository package access.
- [ ] I understand why production should use immutable versions/digests.
- [ ] I know the first checks to make when authentication or authorization fails.
27. Source
This Essentials tutorial is a condensed learning path derived from the GitHub Packages — Complete Reference Guide & Hands-On Tutorial, last verified September 2026. It intentionally prioritizes foundational concepts and hands-on skills suitable for a two-hour study/practice session rather than reproducing the complete advanced reference.
I’m Rajesh Kumar, a DevOps, SRE, DevSecOps, Cloud, and Platform Engineering expert passionate about sharing practical knowledge, real-world experiences, and industry best practices. I have worked at Cotocus and regularly write about technology, travel, investing, health, product reviews, and digital marketing through my various platforms.
I publish technical articles at DevOps School, travel stories at Holiday Landmark, stock market insights at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow, and SEO and digital marketing strategies at Wizbrand.
Find Trusted Cardiac Hospitals
Compare heart hospitals by city and services — all in one place.
Explore Hospitals