Getting started with GitHub Container Registry

Recently I updated one of my images on DockerHub and the upload kept timing out. Not sure if it was a temporary thing because it eventually succeeded after a few hours. Later I made one more update and encountered the same issue. This got me wondering if there was some new DockerHub free user restriction (didn’t seem to be, but who knows) and also prompted me to check out GitHub Container Registry. I don’t mind paying for DockerHub, and I might in the end, but no harm in checking out alternatives – especially as I use GitHub for managing the source code anyways.

The documentation for GitHub Container Registry is a bit all over the place in my opinion. By which it does not straight away tell you what to do, so you need to read through a bit… and even then I felt it wasn’t complete. But hey, that’s probably just me being impatient or not reading well. Nevertheless I thought it would be a good idea to have a short write up here as a reference to myself and anyone else.

First off, GitHub Container Registry – which I am going to shorten as GHCR from now – needs to be enabled. Follow the steps in this article to do that. I completely missed this the first time and couldn’t figure out why my Docker pushes to GHCR were erroring. This is what I was getting by the way:

Second off, the name of this registry is and images must be prefixed with DockerHub hosted images too have a prefix – – just that it’s implied. If you miss the prefix the image gets pushed to DockerHub instead.

To give an example: my image on DockerHub is called rakheshster/stubby-unbound (OWNER/IMAGE); the same on GHCR is called (

Since Sept 2020 GHCR supports the Docker Image Manifest V2, Schema 2 image format, which means you can push multi-architecture images to it. Nice!

Next – images you push to GHCR are private by default so you have an additional step of making them public. Instructions for personal accounts are here; scroll down the same to find instructions for organizations. That page hightlights another thing – I wasn’t sure where the images I push are to be found. Initially I thought they’d be visible under the Packages section of my source repo (coz when you try to add a Package it does say you can add Docker images, and some documents mention that GHCR replaces Packages)… but no, GHCR images are present in the top level of your account, under Packages. This is on the same level as your Repositories. For a screenshot check out step 3 of this article.

Note that GHCR is still in beta and currently free for both public and private images. This is subject to change when it exists the beta period, but according to this blog post it sounds like public images will continue to be free even after the beta period. That’s one good reason to switch to GHCR. This is what GitHub used to do in the past for its repositories too, and of course they’ve got the $$$s to support the pull/ push bandwidth and larger storage requirements thanks to Microsoft.

It is possible to link the GHCR Docker image to the source repo. Follow the steps in this link. It’s even possible to do this automatically via a LABEL in the Dockerfile. Nice!

So how do you push images to GHCR? Easy.

First you need to create a Personal Access Token (PAT) for yourself. (It’s similar to DockerHub – I have tokens for each of the devices I authenticate from). See this link on how to create the same. Looks like with the prior Packages incarnation of GHCR you could also use a GITHUB_TOKEN within GitHub Actions but that’s not currently supported. During the beta period at least, PAT is the sole way to authenticate. Step 1 of this article tells you what permissions to assign the PAT.  That article suggests storing it in an environment variable and echoing it to the docker login command thus:

but you could put it in a file instead and cat that:

or just paste it into the password prompt if that’s easier:

And that’s it really. Once you login as above, you can push your image as you always do… just be mindful to add the prefix I mentioned earlier. And if you forget to login, no biggie… it will error! :)

I usually build and push thus:

But you could also push an already built image:

If you didn’t tag the image when building it, do a docker images to find its ID, and tag it thus:

Then push it as before.

Pulling images from GHCR is similar to from DockerHub, just prefix to the image name.

If you use GitHub actions things are straight-forward there too. I have the following to login to DockerHub for instance:

The equivalent for GHCR:

Pushing is even more straightforward. As before, there’s nothing additional to do apart from prefixing the Here’s an example from crazy-max’s docker/build-push-action@v2 (highly recommended! It is now an official Docker action) from his examples section:

Simple. Login to each, build and push with multiple tags covering both registries. I’ll start doing the same for my Docker images I think, as I use GitHub Actions anyways and this is an easy next step.