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.
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 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
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
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 }}
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 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
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
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 your complete package registry. Publish once, consume anywhere, and keep everything in one place with the same permissions as your code.