Pushing an image to ghcr.io can be a ten-line workflow. The trouble is that a few settings are outside the workflow file, and the error messages often don't mention them. Here are six checks that cover the most common surprises, so you spend your time on the image and not on the log.
This guide is for maintainers who publish Docker images from a GitHub repository to ghcr.io.
1. The job doesn't have permission to write packages
If the push fails with permission_denied: write_package, the job probably lacks permission. Depending on your repository or organization settings, the default token permissions may be read-only.
Check: Give the publish job its own permissions block:
permissions:
contents: read
packages: write
Then log in with the built-in GITHUB_TOKEN. For a package in the same repository, you don't need a personal access token.
2. The image name has uppercase letters
Registry names must be lowercase. Your GitHub owner name may not be.
Check: Write the full image name in lowercase, or lowercase it in the workflow before you use it.
3. An old package is linked to something else
If a package with the same name already exists, it can be linked to another repository or owner. The login step then succeeds, but GitHub still denies the push.
Check: Open the package settings. See which repository it is linked to. Link it to the right one, or use a new name.
4. Your organization limits package creation
Organizations can control who may create packages. In that case, a member's push can fail even when the workflow file is correct.
Check: If the owner is an organization, ask an admin about the package settings before you debug the workflow.
5. You only push latest
Each time you push, the latest tag is reassigned to the new image. If a release breaks something, a user who pulled latest can't tell which build they have or get back to the one that worked.
Check: Push a version tag for every release, like 1.4.0, next to latest. Tell users to pin the version tag. docker/metadata-action can make both tags from your Git tag, so pushing v1.4.0 gives you 1.4.0 and latest without typing either one.
6. Users can't pull because the package is private
A new package can be private by default. Pulls without a login then fail with unauthorized, which looks like a bug in your image.
Check: Open the package page and look at the visibility. Decide on purpose. Then test from a clean shell with no login:
docker pull ghcr.io/your-owner/your-image:1.4.0
The short version
- Add
packages: writeto the publish job. - Use a lowercase image name.
- Check which repository the package is linked to.
- Check the organization package settings.
- Push a version tag, not only
latest. - Check the visibility and test a pull with no login.
The full checklist
I made a longer version of this as a paid kit: GHCR Publish Checklist ($7). It has three parts:
- A 7-step checklist that you can copy into a release issue
- A CSV of the build, test, tag, and pull commands
- A GitHub Actions workflow template for ghcr.io
It is for maintainers who publish Docker images from GitHub to ghcr.io. It is not for teams that use another registry or a managed release platform.
The free checks above cover the errors that come up most often in public threads. The official GitHub documentation is the source of truth, and the settings and actions can change, so check them if a step doesn't match what you see.
Top comments (0)