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.io under 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:

  1. Personal access tokens (PATs). Create a classic token with the appropriate scopes: write:packages for publishing, read:packages for installing, and delete:packages for removing versions.
  2. GITHUB_TOKEN. GitHub Actions automatically provides this short-lived token in workflow runs. By declaring the packages: write permission 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 internal visibility 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_TOKEN in 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.