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:
1 2 3 4 5 6 7 8 |
#73 exporting to image #73 sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00 #73 pushing layers 1.3s done #73 ERROR: unexpected status: 403 Forbidden ------ > exporting to image: ------ error: failed to solve: rpc error: code = Unknown desc = unexpected status: 403 Forbidden |
Second off, the name of this registry is ghcr.io
and images must be prefixed with ghcr.io/
. DockerHub hosted images too have a prefix – docker.io/
– 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 ghcr.io/rakheshster/stubby-unbound
(ghcr.io/OWNER/IMAGE
).
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!
1 |
LABEL org.opencontainers.image.source=https://github.com/OWNER/REPO |
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 echo
ing it to the docker login
command thus:
1 |
echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin |
but you could put it in a file instead and cat
that:
1 |
cat ~/PAT.txt | docker login ghcr.io -u USERNAME --password-stdin |
or just paste it into the password prompt if that’s easier:
1 2 |
docker login ghcr.io -u USERNAME # this will prompt for the password |
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 ghcr.io/
prefix I mentioned earlier. And if you forget to login, no biggie… it will error! :)
I usually build and push thus:
1 |
docker buildx build --platform $ARCH -t ghcr.io/OWNER/IMAGE_NAME:VERSION -t ghcr.io/IMAGE_NAME:latest --progress=plain --push $(pwd) |
But you could also push an already built image:
1 2 |
docker push ghcr.io/OWNER/IMAGE_NAME:VERSION docker push ghcr.io/OWNER/IMAGE_NAME:latest |
If you didn’t tag the image when building it, do a docker images
to find its ID, and tag it thus:
1 2 |
docker tag ID ghcr.io/OWNER/IMAGE_NAME:VERSION docker tag ID ghcr.io/OWNER/IMAGE_NAME:latest |
Then push it as before.
Pulling images from GHCR is similar to from DockerHub, just prefix ghcr.io/
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:
1 2 3 4 5 |
- name: Login to Docker Hub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} |
The equivalent for GHCR:
1 2 3 4 5 6 |
- name: Login to GitHub Container Registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GHCR_TOKEN }} |
Pushing is even more straightforward. As before, there’s nothing additional to do apart from prefixing the ghcr.io/
. 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
- name: Login to DockerHub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.CR_PAT }} - name: Build and push uses: docker/build-push-action@v2 with: context: . file: ./Dockerfile platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x push: true tags: | user/app:latest user/app:1.0.0 ghcr.io/user/app:latest ghcr.io/user/app:1.0.0 |
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.