GitHub Container Registry Packages and Image Identity

Publish a demonstration image to GHCR with scoped workflow access, connect package permissions to a repository and distinguish mutable tags from digests.

Lesson 30 of 30Intermediate10 min read

A GitHub release can describe a version while a container registry supplies the image a deployment runs. GHCR is GitHub's container registry, and its packages have access settings that deserve their own review. This lesson provides a complete manually dispatched publishing demonstration and an offline image-reference exercise. The workflow is teaching material only: it has not been dispatched and no package is published by the site build. Test it in a designated practice repository when you are ready to create a demonstration package, and review the package's visibility and access afterward.

Distinguish repository and package access

A container package is associated with a namespace and can be connected to a repository, but its visibility and permissions are not simply the same thing as the repository's settings. Inspect the package's current access configuration. A public source repository does not justify assuming a newly created image is public, and a repository token does not automatically have rights to every unrelated package in the organization.

For workflow publishing, use the documented GITHUB_TOKEN path where applicable and request packages: write for the publishing job. The package must also allow the workflow's repository access, particularly when updating an existing package created through another path. A personal access token may be needed for some non-workflow operations under the current registry contract, but choose the documented token type and minimal scopes rather than copying a broad account token into every workflow. Do not log credentials or include them in an image layer.

Name and connect the package deliberately

GHCR image paths use a registry host and namespace followed by a package name. Lowercase the reference where the registry's naming contract requires it; the demonstration derives a lowercase repository name and appends lesson-demo to make its teaching purpose clear. Do not overwrite an established production image while learning. Choose a tag that identifies the source revision, and record the package path so consumers know which artifact belongs to the intended project.

The org.opencontainers.image.source label records the source repository URL and helps connect image metadata to its origin. It does not cryptographically prove the image was built from that repository. Preserve the source revision, build logs and any provenance or signing evidence required by your release process. A useful package review checks both metadata and actual access: which workflow can push, who can pull and whether anonymous access is intended. Keep those decisions separate from the readable image name.

Treat tags and digests as different references

A tag such as sha-<commit> is a convention, not an inherent immutability guarantee. An authorized publisher can potentially move a tag unless a policy prevents it. A digest identifies particular image content, which makes it useful when a deployment must consume the verified image rather than whichever content a tag points to later. After publishing, inspect and record the registry-reported digest under your release process.

Digest pinning alone does not establish that the image is safe or from a trusted build. Verify the source, build inputs and applicable provenance before adopting that digest. Likewise, pinning a base image to a reviewed digest improves input identity but still needs updates when the base changes. The small demonstration uses a versioned BusyBox tag to keep the example readable and explicitly does not claim reproducible or permanently fixed base bytes. A production workflow should use its reviewed base-input policy.

Promote verified output with bounded credentials

The workflow builds one image from a complete inline Dockerfile, logs in with password-stdin and pushes only its explicit package reference. The manual trigger avoids publishing on every untrusted contribution. For a real project, replace the inline demonstration with reviewed source and meaningful tests, then promote the exact verified artifact. Do not automatically execute fork-controlled build instructions with a publishing token or production deployment credentials.

Plan for failed pushes, access changes and package retention. A registry login can succeed while the package update is denied, because authentication and authorization are different checks. Inspect both workflow permissions and package access rather than increasing all token scopes immediately. Record the resulting digest and verify a pull in an appropriately authorized environment. If deleting old packages or changing visibility, review the consumers that depend on them first; those are operational changes beyond merely building a local image.

Sources and prerequisites

GitHub Container Registry Packages and Image Identity Summary

  • Review package visibility and repository access independently.
  • Use scoped publishing credentials and explicit image references.
  • Tags can move; record verified digests and preserve the build's trust evidence.

GitHub Container Registry Packages and Image Identity Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A complete manually dispatched demonstration publisher

Save this file as .github/workflows/ghcr-demo.yml only in a practice repository when you intend to publish a new lesson-demo package. It requires a GitHub.com hosted Ubuntu runner with Docker. The final reference below is illustrative; real owner, repository and commit values differ. This tutorial does not execute the publishing workflow.

Docker also prints build and push progress, including the image digest. The fence shows the final reference shape, not a fabricated live push result. Review the created package's access and record its actual digest before using it elsewhere. The inline image simply prints container lesson when run; it is not a production application.

YAML
name: Publish GHCR lesson demo
on: workflow_dispatch
permissions: {}
jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      packages: write
    steps:
      - name: Build and publish the demonstration image
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          image="ghcr.io/${GITHUB_REPOSITORY,,}-lesson-demo"
          tag="sha-$GITHUB_SHA"
          mkdir -p image-context
          printf 'container lesson\n' > image-context/message.txt
          cat > image-context/Dockerfile <<'DOCKER'
          FROM busybox:1.37.0
          COPY message.txt /message.txt
          CMD ["cat", "/message.txt"]
          DOCKER
          docker build --label "org.opencontainers.image.source=https://github.com/$GITHUB_REPOSITORY" -t "$image:$tag" image-context
          printf '%s' "$GH_TOKEN" | docker login ghcr.io --username "$GITHUB_ACTOR" --password-stdin
          docker push "$image:$tag"
          printf 'published: %s:%s\n' "$image" "$tag"
Output
published: ghcr.io/demo-org/demo-repo-lesson-demo:sha-<commit-id>

GitHub Container Registry Packages and Image Identity in Real Projects

Pin a container image by digest

Run this complete offline Node.js exercise. It models a mutable tag moving between two fabricated valid-shaped digest references while a saved digest reference stays fixed. The values are teaching placeholders and do not identify real registry images.

JavaScript
const image = 'ghcr.io/demo-org/demo-repo-lesson-demo';
const firstDigest = 'sha256:' + '1'.repeat(64);
const secondDigest = 'sha256:' + '2'.repeat(64);
const tags = new Map([['latest', firstDigest]]);
const pinned = image + '@' + tags.get('latest');
tags.set('latest', secondDigest);
const current = image + '@' + tags.get('latest');
console.log('tag moved:', current !== pinned);
console.log('saved reference unchanged:', pinned.endsWith(firstDigest));
console.log('source trust established by this exercise:', false);
Output
tag moved: true
saved reference unchanged: true
source trust established by this exercise: false

The saved digest reference identifies the original modeled content even after latest moves. The exercise establishes no provenance or real registry existence. In a deployment, obtain a real verified digest, apply your source and build trust policy, and pull that explicit reference with the required access.

GitHub Container Registry Packages and Image Identity FAQ

Why can my workflow log in but fail to push a package?

Authentication can succeed while the token lacks authorization for the selected package. Check the publishing job's packages permission, the package's repository access and whether the existing package was created under a different ownership or linkage path. Read the actual denied operation before changing scopes. A successful login proves that credentials were accepted; it does not guarantee permission to update every image name in the namespace.

Is an image in a public repository automatically public?

Do not assume that. Container packages have their own visibility and permission settings, and the workflow should inspect the created package's intended access. Decide whether anonymous pulls or only approved consumers are appropriate. Check the current GitHub Packages documentation and test a pull from the intended consumer context. Repository source visibility and package distribution policy are related project decisions but remain separately observable settings.

Does a commit-shaped image tag guarantee immutable content?

The text of a tag is a naming convention. Unless an effective policy prevents reassignment, an authorized publisher can move it to different content. Record and consume a verified digest when the exact image matters. Also preserve source and build evidence, because digest identity alone does not prove the image is safe or correctly produced. The practice model illustrates movement without claiming that any real package was changed.

Should I publish directly from every fork pull request?

Avoid granting publishing authority to a job executing untrusted contribution-controlled build instructions. Validate contributions in a suitable isolated path, then publish reviewed source or promote a verified artifact under a trusted workflow. The demonstration uses a manual trigger and fixed inline content to keep its boundary visible. A production workflow needs meaningful tests, reviewed build inputs, explicit package permissions and a clear release decision.

GitHub Container Registry Packages and Image Identity Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choicemedium

After the practice model moves latest, what still identifies its original modeled content?

GitHub Container Registry Packages and Image Identity Quiz

2 questions to check what you learned.

  1. Which job capability does the example request for GHCR publishing?

    • Only issues: read
    • Repository administration by default
    • packages: write
  2. What does the source label alone establish?

    • Declared source metadata, which still needs provenance review
    • Cryptographic proof of every build input
    • Automatic public access to the package