Beliebte Suchanfragen

Cloud Native



Agile Methoden



Publishing Docker images to GitHub Container Registry with GitHub Actions

4.3.2021 | 8 minutes of reading time

Tired of bumping into Docker Hub’s rate limiting? Why not give the GitHub Container Registry a try? Right now it’s in public beta but it already looks great, especially in combination with GitHub Actions.

GitHub Actions – blog series

Part 1: GitHub Actions CI pipeline: GitHub Packages, Codecov, release to Maven Central & GitHub
Part 2: Publishing Docker images to GitHub Container Registry with GitHub Actions
Part 3: Stop re-writing pipelines! Why GitHub Actions drive the future of CI/CD

What’s the problem with Docker Hub?

Recently I’ve been running into Docker Hub’s new rate limiting more and more often. So regardless which CI system I use, I find myself looking into a log file at something like this:

1Unable to find image 'hello...
2You have reached your pull rate limit. You may increase the limit by authenticating and upgrading:\nSee 'docker run --help'.\n"

or this

1ERROR: toomanyrequests: Too Many Requests.

So I thought of using an alternative container registry that doesn’t expose such a limit. Coincidentally I was updating my example project, which shows how to use GraalVM with Spring . I learned that Oracle moved their GraalVM Docker image from oracle/graalvm-ce (already producing a 404) from to a place at . The resulting image has the coordinates and is served by the GitHub Container Registry. Wow, I didn’t even know that GitHub had a registry! But in late 2020 they introduced it as part of their GitHub Packages offering (if you want to see the “normal” Packages in action, you can take a sneak peek here where I show how to publish Maven artifacts ).

Logo sources: GitHub & GitHub Actions & GitHub Packages logo , Docker logo

Soon I realized that more people started to migrate their Docker images to the new GitHub Container Registry :

GitHub Container Registry is currently in public beta and subject to change. During the beta, storage and bandwidth are free. To use GitHub Container Registry, you must enable the feature preview.

The GitHub Container Registry will ultimately supersede the already existing Packages Docker Registry .

All I wanted was: docker run hello-world

I hadn’t touched my beloved molecule showcase project for a while (wow, my blog about Continuous cloud infrastructure with Ansible, Molecule & TravisCI on AWS is already older than two years). So I finally needed to upgrade to the latest molecule module system with separate drivers not being shipped inside the core anymore. The upgrade really went super smoothly. Right until I hit the Docker Hub rate limiting problem in the final testinfra test case . 🙁 It was really all about a failing docker run hello-world!! What the hell?!

So I thought about publishing my own hello-world image to the GitHub Container Registry. Maybe even others would bump into the same problem. And this would give me the chance to get to know this new registry. 🙂 In the end all I wanted was something I could simply use like this:

1docker run

A simple Go-based executable

The original hello-world image from Docker Hub uses a small executable to print a text. I decided to leverage Go in order to create a reasonably small executable myself. Every piece of code I use throughout this post is also available on GitHub .

So let’s start with a ultra simple hello-world.go :

1package main
3import "fmt"
5func main() {
6    fmt.Println("Hello from Docker on GitHub Container Registry!\nThis message shows that your installation appears to be working correctly.")

Building and running a Go program is easy. You only need to have the Go compiler installed on your machine. On my Mac I used homebrew and did a brew install go. Now we can build the program with

1go build hello-world.go

This will produce a hello-world executable that we can run with ./hello world.

A multi-stage build for our GO program

As we only need to have the Go compiler present to build the binary, we should implement a Docker multi-stage build. The official Go image is quite huge:

1$ docker images
2golang     latest     861b1afd1d13   7 days ago     862MB

Therefore we should leverage a multi-stage build inside of our Dockerfile :

1# We need a golang build environment first
2FROM golang:1.16.0-alpine3.13
4WORKDIR /go/src/app
5ADD hello-world.go /go/src/app
7RUN go build hello-world.go
9# We use a Docker multi-stage build here in order that we only take the compiled go executable
10FROM alpine:3.13
12COPY --from=0 "/go/src/app/hello-world" hello-world
14ENTRYPOINT ./hello-world

The second “run” image is based on the same alpine image as the builder image containing the Go compiler. So let’s now simply build and run our image:

1$ docker build . --tag hello-world
2$ docker run hello-world
3Hello from Docker on GitHub Container Registry!
4This message shows that your installation appears to be working correctly.

The resulting image is around 7.55MB which should be small enough for our use cases.

List of steps how to publish to GitHub Container Registry with GitHub Actions

In order to publish a container image on GitHub Container Registry using GitHub Actions, we have to do the following steps:

1. Activate improved container support
2. Create a personal access token (PAT) and a repository secret
3. Create GitHub Actions workflow and login to GitHub Container Registry using the PAT
4. Publish (push) Container image to GitHub Container Registry & link it to our repository
5. Optional: Make your image publicly accessible

1. Activating improved container support

This step is only needed while the GitHub Container Registry is in beta phase. In order to use the new Container Registry feature, we need to activate it in our account first . Therefore head to your GitHub account’s settings menu und click on Feature preview:

In my account there was only one feature I could choose from: the improved container support feature we need to enable to have GitHub Container Registry ready:

2. Creating a personal access token (PAT) and a repository secret

Right now (in beta) we can’t use the GITHUB_TOKEN in GitHub Actions to authenticate to the GitHub Container Registry . So we need to create a personal access token (PAT). But keep in mind what the docs state :

PATs can grant broad access to your account. We recommend selecting only the necessary read, write, or delete package scope when creating a PAT to authenticate to the container registry.

Create a PAT in Settings/Developer settings Personal access tokens and click on Generate new token. Here you need to select read:packages, write:packages and delete:packages scopes like this:

Having created the PAT, we can move on to create a new repository secret inside our GitHub repository that contains our Go program and Dockerfile. To create a repository secret, head to your repository’s settings tab and click on Secrets. There you should be able to create a new repository secret:

3. Creating GitHub Actions workflow and logging into GitHub Container Registry using the PAT

With both PAT and repository secret set up, we can now create a new (or choose an existing) GitHub Actions workflow. After the usual checkout action, we should set our secret as an environment variable. The example project’s workflow file .github/workflow/publish.yml looks like this:

1name: publish
3on: [push]
6  publish-hello-world-image:
7    runs-on: ubuntu-latest
9    steps:
10    - uses: actions/checkout@v2
12    - name: Build the hello-world Docker image
13      run: |
14        echo $CR_PAT | docker login -u YourAccountOrGHOrgaNameHere --password-stdin
15      env:
16        CR_PAT: ${{ secrets.CR_PAT }}

Be sure to use your account or GitHub organization name instead of YourAccountOrGHOrgaNameHere. This should successfully do the login to the GitHub Container Registry.

4. Publishing (Pushing) Container image to GitHub Container Registry & linking it to our repository

So we’re already approaching the final steps. Now we should have everything in place to push our container image to the GitHub Container Registry. Therefore we need to tag our image correctly while building it. The tag’s pattern must adhere to Inside the example project’s .github/workflow/publish.yml it looks like this:

1docker build . --tag
2        docker run
3        docker push

This is already enough to push our image!

As the image is published as a GitHub account global package, it isn’t linked with our repository and thus won’t be displayed there, nor will it use the as description. In order to automatically link our image to our GitHub repository, we need to add a specific LABEL into our Dockerfile . It should contain the URL to our repository like this:

1LABEL org.opencontainers.image.source=""

Having added this label to our Dockerfile, the image package automatically gets linked to our repository:

Also, the image becomes visible on our repository’s main page:

5. Optional: Making your image publicly accessible

By default , our container image is private on the GitHub Container Registry . To make it publicly accessible, we need to move to our user account or GitHub organization page. For my account, this is :

Click on the container image published (which looks the same as a normal GitHub package) and then go to Package Settings. In the Danger Zone, click on change visibility and choose Public:

Now we should finally be able to pull and run our image! Simply run docker run

1$ docker run
2Unable to find image '' locally
3latest: Pulling from jonashackt/hello-world
4ba3557a56b15: Already exists
58d624a13b642: Pull complete
6Digest: sha256:c88996d21c33ed08a76decc2b53e109bbc601d0fa1e444f24682250c5d406aa1
7Status: Downloaded newer image for
8Hello from Docker on GitHub Container Registry!
9This message shows that your installation appears to be working correctly.

Give the GitHub Container Registry a try!

I didn’t really want to create a hello-world image myself in the first place. But hey! Working with the GitHub Container Registry is fun and you should consider it for your next project. In this article we have seen how to create a small example program and build it using a Docker multi-stage build. The activation of the new feature and the creation of a personal access token (PAT) are the prerequisites to using the Container Registry right now in beta phase. I guess both won’t be necessary anymore soon.

Using GitHub Actions, we only need to log in using our PAT which we can store in a repository secret. The LABEL org.opencontainers.image.source links our image to our repository after we pushed it using a GitHub Actions workflow of choice. Finally we define our image as publicly accessible if we want it to be. That’s it already! Now it’s time to move your images from Docker Hub to GitHub Container Registry I guess. 🙂 And if you ever need a hello-world image, you know where to find it …

share post




More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.


Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.