GitHub Packages
GitHub Packages is a package hosting service built directly into GitHub. It lets developers store and distribute software artifacts—npm modules, Maven artifacts, NuGet packages, Docker images, and more—alongside their source code. Instead of maintaining a separate artifact registry, teams can keep packages in the same repositories, organizations, or user accounts where they manage issues, pull requests, and CI workflows.
This tight integration creates a single source of truth for both code and binaries. When a package lives next to the repository that produced it, release traces are easier to follow, access controls are simpler to audit, and automated delivery pipelines require fewer external dependencies.
Supported Package Types
GitHub Packages supports multiple package ecosystems, making it practical for polyglot teams and multi-language projects:
- npm, Yarn, and pnpm for Node.js. Packages use scoped naming, such as
@owner/package-name. - Maven and Gradle for Java and JVM-based projects.
- NuGet for .NET applications and libraries.
- Docker and OCI-compliant container images, hosted at
ghcr.iounder GitHub Container Registry. - RubyGems for Ruby and Composer for PHP.
Because the service speaks the standard protocols of each package manager, developers can publish and consume artifacts using familiar CLI commands without learning a proprietary toolchain.
Authentication
Accessing GitHub Packages requires authentication. Two approaches cover most scenarios:
- Personal access tokens (PATs). Create a classic token with the appropriate scopes:
write:packagesfor publishing,read:packagesfor installing, anddelete:packagesfor removing versions. GITHUB_TOKEN. GitHub Actions automatically provides this short-lived token in workflow runs. By declaring thepackages: writepermission in a job, the workflow can authenticate and push artifacts without managing long-lived secrets.
For example, to configure npm for a scoped organization package, create an .npmrc file in the project root:
@my-org:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_TOKEN
Publishing Packages
Publishing an npm Package
In package.json, point the publish configuration to the GitHub npm registry:
{
"name": "@my-org/hello-utils",
"version": "1.0.0",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
Running npm publish then uploads the package to GitHub Packages under the specified scope.
Pushing Container Images
Container images are pushed to GitHub Container Registry at ghcr.io. After authenticating with docker login, build and push the image:
echo "$TOKEN" | docker login ghcr.io -u USERNAME --password-stdin
docker build -t ghcr.io/my-org/hello-app:1.0.0 .
docker push ghcr.io/my-org/hello-app:1.0.0
Continuous Integration with GitHub Actions
The most common pattern for GitHub Packages is automated publishing inside CI. The following workflow builds and pushes a Docker image whenever a version tag is pushed:
name: Publish Docker Image
on:
push:
tags: ["v*"]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
The permissions block is critical. Granting packages: write allows the built-in GITHUB_TOKEN to authenticate against the registry, eliminating the need to store a personal access token in repository secrets.
Installing and Consuming Packages
Consuming private packages requires the same authentication setup used for publishing. To install a scoped npm package from an organization:
npm install @my-org/hello-utils
To pull a private container image:
docker pull ghcr.io/my-org/hello-app:1.0.0
In both cases, the client must present valid credentials before the registry will serve the artifact.
Permissions and Visibility
GitHub Packages offers flexible access controls that align with repository permissions:
- Visibility levels. Public packages can be downloaded by anyone. Private packages are restricted to authorized users. GitHub Enterprise customers can also use an
internalvisibility level, making packages available to members of the enterprise but not the public internet. - Permission inheritance. By default, a package inherits the access settings of its source repository. Administrators can override this on the package settings page, adding individual users or teams and assigning read, write, or admin roles.
- Repository association. Packages can be linked to a specific repository. When the repository is deleted, administrators can choose whether to remove associated package versions as well.
Best Practices
To get the most out of GitHub Packages, consider the following guidelines:
- Prefer
GITHUB_TOKENin GitHub Actions over long-lived personal access tokens. Short-lived tokens reduce the surface area for credential leakage. - Use semantic versioning for tags and package versions. Clear version strings make rollback and dependency resolution more predictable.
- Clean up unused or obsolete package versions on a regular schedule. Storage and bandwidth quotas apply to private packages, so pruning old artifacts helps control costs.
- Keep package names consistent with repository names where possible. This makes it easier for team members to locate the source of a given artifact.
Conclusion
GitHub Packages unifies artifact management with code hosting and continuous integration on a single platform. By removing the need for a separate registry, it reduces operational overhead and keeps release metadata close to the code that produced it. Teams that adopt scoped naming, short-lived CI tokens, and disciplined version cleanup can quickly establish a reliable path from commit to published package.