Skip to content

GitHub Container Registry (GHCR)

GHCR is a thin auth wrapper over a Distribution-spec registry, but its access model is unusual: package visibility is independent of the source repository's visibility, and ownership lives on a user or org namespace rather than a single account. Most GHCR connection problems trace back to these two facts.

Auth options

MethodWhen to useHarborGuard fields
Classic PAT with read:packagesSimplest path, broad accessUsername = GitHub login, Password = PAT
Fine-grained PATRestricted to specific repositories or organizationsSame as above
GitHub App installation tokenAutomated / org-managed access without a personal accountUsername = x-access-token, Password = installation token
GITHUB_TOKEN (Actions only)Workflow scanning the same repo's packageNot applicable — issued at job runtime

GHCR does not accept the GitHub UI password — only tokens.

Add the registry

  1. Registries → Connect Registry.
  2. Registry URLghcr.io (no namespace; HarborGuard discovers what the token can see).
  3. The wizard shows GitHub GHCR Detected.
  4. Cloud: enter your GitHub login + PAT.
  5. Save.

GHCR's /v2/_catalog returns only what the authenticated principal can read, so the catalog reflects exactly what your PAT was issued for.

Required PAT scopes

For a classic PAT:

  • read:packages — required, sufficient for public + private packages the user can see.
  • repo — required only if any of the packages are linked to a private repository (GHCR currently inherits the repo's read permission).

For a fine-grained PAT:

  • Repository access: select the repos whose packages you want to scan.
  • Permissions → Repository → Packages: Read-only.
  • Permissions → Account → Packages: Read-only (if scanning user-namespace packages outside any repo).

For a GitHub App:

  • Repository permissions → Packages: Read-only + Metadata: Read-only.
  • Install on the relevant repositories or org.

Push-event sync

GHCR doesn't have a registry-side webhook. Instead, GitHub fires a package repository event on push.

  1. On the source repository (or org level), add a webhook for the Package event.
  2. URL: https://<your-harborguard-host>/api/webhooks/github.
  3. Set the registry's schedule to on_push.

For a multi-repo org, prefer an organization-level webhook so you don't manage one per repo.

Common pitfalls

  • User vs org namespaces. A package owned by user alice lives at ghcr.io/alice/<image>. The same image moved to org acme lives at ghcr.io/acme/<image> — same registry, different ACL. Forking does not move packages.
  • Package visibility. A package can be public even when its source repo is private (and vice versa). The package's own visibility setting governs /v2/ access.
  • PAT expiry. Classic PATs default to no expiry but can be rotated by org SSO policy. SSO-protected orgs require the PAT be authorized via Configure SSO after creation; otherwise it 404s on every package.
  • Restricted scopes on fine-grained PATs. The default fine-grained PAT has no package permission. The most common failure mode is a PAT that authenticates against the GitHub API but 401s on ghcr.io.
  • docker.pkg.github.com. That's the deprecated GitHub Packages predecessor. HarborGuard does not target it; migrate to GHCR.

Troubleshooting

SymptomLikely cause
401 on test, PAT was created seconds agoSSO authorization not granted on the PAT
Catalog is empty for an org with many packagesPAT scoped to user-only resources; needs org-level package read
Specific image 404sPackage visibility is private and PAT lacks repo scope on the linked repo
Push events ignoredWebhook is on the wrong repo (not the one that publishes the package)
Sensor mode works once, fails after token rotationGitHub App installation token expired (1 hour) — refresh logic missing on the sensor

On this page