Skip to content
GHCR: Complete Guide to GitHub Container Registry

GHCR: Complete Guide to GitHub Container Registry

Grayscale portrait of a young man looking left, wearing a striped lanyard, with an olive green dot pattern background.Artemiy Malyshau· Co-founder & CTO7 min read

Key takeaways

  • GHCR stores container images at ghcr.io with free unlimited pulls and tight GitHub Actions integration
  • Authentication uses GitHub PATs with package scopes or automatic GITHUB_TOKEN in workflows
  • GHCR has no rate limits unlike Docker Hub’s 200 pulls per 6 hours, making CI/CD more reliable
  • Registry scanning catches CVEs but misses business logic flaws in containerized application code
  • Gecko finds authorization bypasses and access control bugs that exist in your containers at runtime

You switched to GHCR because storage is free and you’re tired of Docker Hub’s pull limits breaking your builds. Then you spend an afternoon figuring out why docker push fails with permission denied even though you authenticated successfully, why your organization packages need SSO authorization on tokens, and which combination of read:packages, write:packages, and repo scopes you actually need. The authentication system connects to GitHub’s existing permissions in ways that create non-obvious failure modes.

TLDR:

  • GHCR stores container images at ghcr.io with free unlimited pulls and tight GitHub Actions integration
  • Authentication uses GitHub PATs with package scopes or automatic GITHUB_TOKEN in workflows
  • GHCR has no rate limits unlike Docker Hub’s 200 pulls per 6 hours, making CI/CD more reliable
  • Registry scanning catches CVEs but misses business logic flaws in containerized application code
  • Gecko finds authorization bypasses and access control bugs that exist in your containers at runtime

What Is GHCR and How Does GitHub Container Registry Work#

GHCR (GitHub Container Registry) is GitHub’s native container image storage service, accessible at ghcr.io. If you’re already using GitHub for source control, GHCR lets you store and distribute OCI-compliant container images in the same ecosystem where your code lives.

GHCR replaced the older Docker package support that lived under docker.pkg.github.com. The registry treats container images as first-class artifacts tied directly to repositories or organizations, not generic packages. You get better visibility, access controls inherited from your repository settings, and tighter coupling with GitHub Actions workflows.

When you push an image to ghcr.io, it’s stored as an OCI artifact that any Docker-compatible client can pull. The registry follows OCI specifications, so you can use standard Docker commands or tools like Podman. Images can be public or private, scoped to your user account or organization, and linked to specific repositories for automatic visibility and permission inheritance.

Understanding GHCR Pricing: Current Free Status and Future Billing Structure#

GHCR currently operates under a different billing model than other GitHub Packages offerings. Container image storage and bandwidth are free, making it one of the more accessible registry options if you’re already in the GitHub ecosystem.

This creates confusion because GitHub Packages (for npm, Maven, NuGet, and RubyGems) follows a tiered billing structure with included storage and transfer limits. GHCR doesn’t fall under those same billing rules. According to GitHub’s official documentation, the Container Registry remains free for now.

GitHub has committed to providing at least one month advance notice before introducing any pricing changes. If you’re considering GHCR for production workloads, this gives you time to react, but it’s worth factoring into long-term infrastructure planning.

One detail that matters for CI/CD: bandwidth used when pulling or pushing images within GitHub Actions workflows stays free regardless of future billing changes. Your automated pipelines won’t rack up transfer costs even if external bandwidth gets metered later.

GHCR vs Docker Hub: Comparing Container Registry Options#

Docker Hub remains the default registry for many teams, but GHCR changes the calculation if you’re already working in GitHub’s ecosystem. The core difference comes down to rate limits and integration friction.

Docker Hub’s free tier restricts you to 200 container pulls every six hours for anonymous users, which breaks CI/CD pipelines fast. GHCR gives you unlimited pulls for public images and no artificial throttling on private repositories tied to your GitHub account.

Access control works differently too. Docker Hub manages permissions separately from your source code, so you’re maintaining two sets of rules. GHCR inherits repository permissions directly from GitHub, meaning anyone who can read your repo can pull images scoped to it.

For GitHub Actions workflows, GHCR authentication happens automatically through the built-in GITHUB_TOKEN. Docker Hub requires separate credential management and secret storage.

FeatureGHCR (GitHub Container Registry)Docker Hub
Rate Limits (Public Images)No rate limits on public or private image pulls200 pulls per 6 hours for anonymous users; 200 pulls per 6 hours for free authenticated accounts
Storage & Bandwidth PricingCurrently free with no storage or bandwidth charges; GitHub committed to 1-month advance notice before any pricing changesFree tier includes unlimited public repositories; private repositories limited by plan tier
Authentication in GitHub ActionsAutomatic authentication via built-in GITHUB_TOKEN; no separate credential management neededRequires manual secret storage and separate credential management for workflow authentication
Access Control ModelInherits permissions directly from GitHub repository settings; unified access management with source codeSeparate permission system independent of source code repositories; requires dual maintenance
Image Namespace Structureghcr.io/NAMESPACE/IMAGE where namespace matches GitHub username or organizationdocker.io/NAMESPACE/IMAGE with separate Docker Hub account namespace
Public Image DiscoveryNo centralized browse interface; discovery through GitHub package search or repository linksCentralized search and browse interface with filtering by categories and popularity
CI/CD Pipeline ImpactUnlimited pulls prevent pipeline throttling; tight GitHub Actions integration reduces configuration overheadRate limits frequently break CI/CD pipelines on free tier; requires paid plans or pull-through caching strategies

Authenticating to GHCR: Personal Access Tokens and GitHub Actions Integration#

Authentication to ghcr.io relies on GitHub’s native credential system. Local development requires a Personal Access Token with appropriate package scopes, while GitHub Actions workflows use the automatically provided GITHUB_TOKEN.

Generate a PAT from Developer Settings > Personal Access Tokens. Reading public and private images requires the read:packages scope. Pushing containers needs write:packages. When images tie to private repositories, add the repo scope for proper permission inheritance.

Authenticate Docker or Podman with your token:

<code class="language-css">echo YOUR_PAT | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
</code>

The --password-stdin flag prevents tokens from appearing in shell history. Podman uses identical syntax.

For GitHub Actions, skip manual PAT creation. The GITHUB_TOKEN secret handles GHCR authentication automatically:

<code class="language-yaml">- name: Log in to GHCR
  run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
</code>

This workflow-scoped token expires when the job completes, limiting credential exposure.

Pushing and Pulling Container Images from GHCR#

Image names on GHCR follow the structure ghcr.io/NAMESPACE/IMAGE_NAME:TAG, where the namespace matches your GitHub username or organization. Tag and push your local image:

<code class="language-bash">docker tag myapp:latest ghcr.io/youruser/myapp:v1.0.0
docker push ghcr.io/youruser/myapp:v1.0.0
</code>

Pulling public images requires no authentication:

<code class="language-python">docker pull ghcr.io/open-webui/open-webui:main
</code>

Private images need your PAT or GITHUB_TOKEN first. GHCR supports multi-architecture manifests via Docker buildx:

<code class="language-bash">docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/youruser/myapp:latest --push .
</code>

Image visibility defaults to private. You can switch to public in package settings without exposing your repository if it’s private, though testing for hidden attack vectors remains important.

See it in action.

Integrating GHCR with GitHub Actions for CI/CD Pipelines#

GitHub Actions workflows authenticate to GHCR using the built-in GITHUB_TOKEN, which eliminates separate credential management. The workflow context injects repository names and commit metadata for automatic tagging.

A typical workflow grants package write permissions and logs in before building:

<code class="language-yaml">name: Build and Push to GHCR
on:
  push:
    branches: [main]
<p>jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build and push
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
</code>

The permissions block is required for push operations. Omitting it causes authentication failures despite valid tokens. For deployments spanning multiple registries like Docker Hub or AWS ECR, store additional credentials as repository secrets and execute sequential login steps before tagging images with registry-specific prefixes.

Container Registry Security Risks and Vulnerability Management#

Container registries store more than just images. They inherit every vulnerability, hardcoded secret, and misconfiguration baked into the layers you push. 87% of container images contain high or critical security vulnerabilities, which creates immediate exposure when pulling from GHCR without scanning.

Base image vulnerabilities compound across your stack. An outdated Alpine or Ubuntu layer propagates the same CVEs to every dependent image. Exposed secrets like API keys or database credentials embedded in layers remain accessible even after deletion, since registry layers are immutable and cached across pulls.

Supply chain attacks target registries directly. Typosquatting, compromised upstream images, and malicious tags inserted into public namespaces turn trusted sources into infection vectors. GHCR’s public image discoverability increases attack surface if you’re pulling third-party containers without verification.

Registry scanning catches known CVEs but misses business logic flaws in the applications running inside containers.

Searching and Finding Public Images on GHCR#

GHCR doesn’t offer a centralized search interface like Docker Hub’s browse page. You can’t land on ghcr.io and browse popular images or filter by categories. Discovery happens through GitHub’s package search or direct repository links.

Search for public GHCR images from GitHub’s main search bar using the type:package filter. Add registry constraints like ghcr.io open-webui to narrow results. Repository README files often include ghcr.io pull commands, making the source repo your best discovery path.

Package visibility settings live in repository or organization package settings, not at push time. Switching an image from private to public doesn’t expose the linked repository if it remains private. You control package and code visibility independently.

Verify authenticity by checking the package’s linked repository and organization. GHCR displays the source repository for each image, letting you confirm the maintainer matches expectations before pulling, similar to how authorization bypass vulnerabilities require verification of authentication flows.

Common GHCR Troubleshooting: Authentication, Permissions, and Rate Limits#

Authentication failures usually trace back to token scope. If docker login ghcr.io succeeds but pushes fail with “denied: permission_denied”, your PAT is missing write:packages. Pulling private images linked to private repos needs the repo scope added.

Organization accounts with SSO require token authorization. After generating your PAT, click “Configure SSO” next to the token and authorize it for your organization. Without this step, you’ll authenticate successfully but hit permission errors on org-scoped packages.

Permission denied errors during push often mean the package namespace doesn’t match your authentication context. Pushing to ghcr.io/orgname/image requires membership in orgname with package write access. Verify the namespace matches your username or an organization you belong to.

GHCR doesn’t impose Docker Hub-style rate limits on public image pulls, but GitHub Actions workflow failures can stem from insufficient job permissions. Add packages: write to the job’s permissions block when pushing images.

Securing Container Images: Beyond Registry Scanning with Business Logic Testing#

Registry scanning tools catch CVEs in base images and dependencies, but they can’t analyze what happens when your containerized application runs. A clean scan on your GHCR image tells you nothing about authorization bypasses, privilege escalation paths, or broken access control in the application code inside that container.

Container scanning operates at the dependency layer, checking Alpine versions, library CVEs, and known vulnerabilities in packages. Static analysis struggles with business logic vulnerabilities in your application code. Business logic flaws live in your application code. They come from how services interact across trust boundaries, how authentication flows between microservices, and whether permission checks actually exist where they should.

Gecko finds semantic vulnerabilities that scanning misses by analyzing how containerized applications actually behave and tracing execution paths across service boundaries to identify authorization gaps that only appear at runtime.

Final Thoughts on GHCR Security and Implementation#

GHCR solves the Docker Hub rate limit problem and integrates cleanly with your GitHub workflows, but container security needs more than vulnerability scanning. Your business logic flaws live in how services trust each other and handle permissions, not in outdated base images. Gecko traces those execution paths across your containerized stack to catch semantic vulnerabilities that registry tools miss.

Grayscale portrait of a young man looking left, wearing a striped lanyard, with an olive green dot pattern background.

Artemiy Malyshau

Co-founder & CTO

Artemiy served in an elite unit of the Austrian Cyber Forces, defending national infrastructure He was then the first employee at a government-backed cybersecurity research group, where he led security projects for Interpol and national governments. At Gecko he builds the platform trusted to sit inside Fortune 500 codebases, and holds it to the standard those governments taught him.

Frequently asked questions

Related content

The latest news, technologies, and resources from our team.

Subscribe to the Gecko Security newsletter

Occasional updates, new content, and insights. No spam; unsubscribe anytime.

We use your email only to send you our newsletter. See our privacy policy for how we handle your data. You can unsubscribe at any time.