GitHub Packages (GHCR)

GitHub Packages is a complete package registry integrated with GitHub. Publish and consume Docker images, npm packages, Maven artifacts, NuGet packages, Ruby gems, and more—all with the same permissions and authentication as your code repositories.

Docker / GHCR npm Packages Maven Artifacts
What is GitHub Packages?

GitHub Packages is a package hosting service that allows you to publish and consume packages alongside your source code. It's fully integrated with GitHub, meaning you use the same authentication, permissions, and workflows for your packages as you do for your code. Packages are stored in your repository or organization, and access is controlled by the same teams and permissions.

The GitHub Container Registry (GHCR) is the container-focused part of GitHub Packages. It's a Docker registry that supports OCI images, with features like image tags, labels, and the ability to store images at the organization or user level. GHCR is optimized for containers and integrates seamlessly with GitHub Actions for building and pushing images.

GitHub Packages is free for public packages and includes generous storage and bandwidth for private packages. It eliminates the need for separate package registries like npmjs.com, Docker Hub, or Maven Central for private artifacts.
Supported Package Types

GitHub Packages supports a wide range of package ecosystems. Each package type has its own URL format and authentication method, but all use the same underlying permissions model.

Container Images (GHCR): Docker and OCI images. URL: ghcr.io/owner/image-name. Perfect for storing your application containers alongside your code.

npm Packages: JavaScript/TypeScript packages. URL: npm.pkg.github.com/owner/package-name. Great for private npm modules.

Maven Packages: Java/Kotlin artifacts. URL: maven.pkg.github.com/owner/repository. Supports both snapshots and releases.

NuGet Packages: .NET packages. URL: nuget.pkg.github.com/owner/index.json. For .NET libraries and tools.

RubyGems: Ruby packages. URL: rubygems.pkg.github.com/owner. For Ruby libraries and gems.

Apache Maven: Full Maven repository support with both releases and snapshots.

Gradle: Compatible with Maven repository format.

# Package registry URLs
Container Registry: ghcr.io
npm Registry: npm.pkg.github.com
Maven Registry: maven.pkg.github.com
NuGet Registry: nuget.pkg.github.com
RubyGems Registry: rubygems.pkg.github.com
GitHub Container Registry (GHCR)

GHCR is the Docker-compatible container registry built into GitHub. Unlike the older Docker package storage, GHCR stores images at the organization or user level, not tied to a specific repository. This gives you more flexibility in organizing your container images.

To authenticate with GHCR, you use a GitHub personal access token with the appropriate scopes (write:packages, read:packages). In GitHub Actions, you can use the GITHUB_TOKEN which automatically has permissions to push to packages in the same repository or organization.

GHCR supports all Docker client commands: docker pull, docker push, docker tag. It also supports OCI images, so you can use tools like Podman, Skopeo, or ORAS as well.

# Login to GHCR
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# Build and tag an image
docker build -t ghcr.io/owner/my-app:latest .

# Push to GHCR
docker push ghcr.io/owner/my-app:latest

# Pull an image
docker pull ghcr.io/owner/my-app:latest
Publishing npm Packages

GitHub Packages can host private npm packages, eliminating the need for a paid npm private registry. You can publish packages directly from your GitHub Actions workflows.

To publish, configure your .npmrc file to point to GitHub's npm registry. The URL format includes the owner name. Authentication uses a GitHub token with write:packages scope.

In your package.json, specify the package name with the scope: @owner/package-name. When you run npm publish, it will go to GitHub's registry. You can also consume private packages by configuring the same registry in your consuming project.

# .npmrc file
registry=https://npm.pkg.github.com/OWNER
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}

# package.json
{
  "name": "@myorg/my-private-package",
  "version": "1.0.0",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  }
}

# GitHub Actions workflow
- run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Publishing Maven Packages

GitHub Packages serves as a Maven repository, supporting both releases and snapshots. You can publish libraries, plugins, and other Java artifacts directly from your build.

Configure Maven's settings.xml or use environment variables to authenticate. The repository URL format is https://maven.pkg.github.com/owner/repository. For publishing, you need a token with write:packages scope.

In your pom.xml, configure the distribution management to point to GitHub. For consuming packages, add the repository to your pom.xml or settings.xml.

# settings.xml configuration
<servers>
  <server>
    <id>github</id>
    <username>OWNER</username>
    <password>${GITHUB_TOKEN}</password>
  </server>
</servers>

# pom.xml distribution management
<distributionManagement>
  <repository>
    <id>github</id>
    <url>https://maven.pkg.github.com/owner/repo</url>
  </repository>
</distributionManagement>
GitHub Actions Integration

GitHub Packages and Actions work seamlessly together. The GITHUB_TOKEN automatically has permissions to push to packages in the same repository. This makes publishing packages in your CI/CD pipeline incredibly simple.

For container images, you can build and push using the Docker CLI or dedicated actions like docker/build-push-action. For npm, Maven, or other package types, the token works with the respective package managers.

You can also use environment-specific packages by using different tags or version schemes. For example, push :staging and :production tags for container images, or use version suffixes like -SNAPSHOT for Maven.

# Complete workflow to build and push Docker image
name: Build and Push to GHCR
on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      packages: write
    steps:
      - uses: actions/checkout@v4
      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
Access Control and Permissions

One of GitHub Packages' biggest advantages is its integrated permissions model. Package permissions are inherited from the repository or organization. You can set visibility to public, private, or inherited from the repository.

For organization packages, you can control who can read, write, or administer packages. This is configured in the organization settings under Packages. You can also set fine-grained permissions for individual packages.

When using the GITHUB_TOKEN in Actions, permissions are automatically scoped. You may need to explicitly grant packages: write permission in your workflow file for publishing. For reading packages from other repositories, you need to configure a personal access token with appropriate scopes.

# Grant package write permission in workflow
jobs:
  build:
    permissions:
      contents: read
      packages: write # Required for publishing
Best Practices for GitHub Packages

Use semantic versioning. Follow semver for all packages—it helps consumers understand compatibility. Use major, minor, and patch versions consistently.

Tag container images meaningfully. Use specific version tags like 1.2.3 alongside latest. This allows reproducible deployments.

Clean up old packages. GitHub Packages has retention policies. You can configure automatic deletion of older versions to manage storage usage.

Use OIDC for authentication. Instead of personal access tokens, use OpenID Connect to authenticate to GitHub Packages from Actions. It's more secure and requires no token management.

Document your packages. Include a README in your package. GitHub shows it on the package page, helping users understand how to use your package.

Use organization packages. For shared libraries, publish to organization-level packages rather than user-level. This makes permissions easier to manage.

GitHub Packages is the natural home for your artifacts. It keeps everything—code, documentation, and packages—in one place with consistent permissions and workflows.
Frequently Asked Questions
Is GitHub Packages free?
Yes! GitHub Packages is free for public packages. For private packages, it's included in all paid GitHub plans with generous storage and bandwidth limits. You can check your account's included usage in the GitHub billing settings.
What's the difference between GHCR and the old Docker registry?
GHCR (ghcr.io) is the modern container registry. It supports OCI images, stores images at the organization/user level (not tied to a repository), and has better performance. The old Docker registry (docker.pkg.github.com) is being deprecated in favor of GHCR.
Can I use GitHub Packages with CI/CD tools other than GitHub Actions?
Yes! GitHub Packages works with any CI/CD tool. You just need to authenticate using a GitHub personal access token with the appropriate package scopes. The same commands work in Jenkins, GitLab CI, CircleCI, or local development.
How do I authenticate to consume private packages?
For npm, configure your .npmrc with the token. For Docker, use `docker login ghcr.io` with your token. For Maven, configure settings.xml. In GitHub Actions, the GITHUB_TOKEN works automatically for packages in the same repository.
Can I migrate existing packages from other registries?
Yes! You can pull packages from Docker Hub or other registries, retag them, and push to GHCR. For npm and Maven, you can publish existing packages using the standard publish commands with the GitHub registry configured.
What's the storage limit for GitHub Packages?
Storage limits depend on your GitHub plan. Free accounts have 500 MB for private packages. Pro accounts have 2 GB. Team accounts have 2 GB. Enterprise accounts have 50 GB. Public packages are unlimited and free.
Can I delete packages or specific versions?
Yes! Go to the package page on GitHub, click "Package settings," and you can delete specific versions or entire packages. You can also set up retention policies to automatically delete older versions.
How do I make my package publicly visible?
In the package settings, you can change the visibility from "Private" to "Public." For container images, this makes the image publicly pullable. For npm/Maven packages, it makes them publicly installable.
Previous: Self-Hosted Runners Next: IaC with GitHub

GitHub Packages is your complete package registry. Publish once, consume anywhere, and keep everything in one place with the same permissions as your code.