Recently I decided to try GitHub Container Registry (GHCR) out as a possible alternative to Docker Hub and Azure Container Registry (ACR) for certain apps. While I knew it wouldn’t expose all the features of a registry like ACR, many times that’s not needed and it was compelling to have source, build (via Actions), and packages / images all self-contained and linked within GitHub.

While Docker images were supported when GitHub packages launched, GHCR adds better access control and use within a GitHub organization, among other things. Currently it’s in Beta but coming along nicely.

Getting Started

Currently when using within an organization, GHCR needs to be enabled first.

For this example I started with a GitHub action I created for Deploying .NET Apps to Raspberry Pi with GitHub Actions and Docker which pushed to Docker Hub.

Configuring Access

  • A Personal access token (PAT) needs to be setup with read:packages, repo and write:packages OAuth scopes so the action can push to the registry. When doing this in an organization this would normally be done logged in as some service account (a separate browser profile is handy).
  • That PAT should be added as a repo secret to the repo of the GitHub action pushing the image to GHCR.
  • The account the PAT was created under should have appropriate access to the repo – i.e. GH service account user is assigned to repo with Write access directly or via a team.

Sample GitHub Action

This action starts by pulling the repo and setting up some environment variables for image labels and tags.

name: Cat Siren Push
    - 'siren/**'
    - '.github/workflows/push-siren.yml'
    runs-on: ubuntu-latest
    - name: Checkout
      uses: actions/checkout@v2
      name: Set Environment Variables
      run: |
        echo "::set-env name=BUILD_VER::1.0.$GITHUB_RUN_NUMBER"
        echo "::set-env name=IMG::${IMG}"
        echo "::set-env${{ github.repository_owner }}/${IMG}"
        echo "::set-env name=BUILD_DATE::$(date +'%Y-%m-%d %H:%M:%S')"
        echo "::set-env name=GIT_SHA::$(echo ${{ github.sha }} | cut -c1-7)"
        echo "::set-env name=GIT_REF::$(git symbolic-ref -q --short HEAD || git describe --tags --exact-match)"

Next Docker Buildx is setup and the registry is logged into using the same user and PAT created earlier.

      name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v1
      name: Login to Container Registry
      uses: docker/login-action@v1
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

Next the image is built and pushed using the docker/build-push-action.

      name: Docker build and push
      uses: docker/build-push-action@v2
        context: ./siren
        file: ./siren/Dockerfile
        labels: |
          org.opencontainers.image.authors=${{ github.repository_owner }}
          org.opencontainers.image.created=${{ env.BUILD_DATE }}
          org.opencontainers.image.description=Created from commit ${{ env.GIT_SHA }} and ref ${{ env.GIT_REF }}
${{ env.GIT_REF }}
          org.opencontainers.image.revision=${{ github.sha }}
          org.opencontainers.image.source=${{ github.repository }}
          org.opencontainers.image.version=${{ env.BUILD_VER }}
        tags: |
          ${{ env.IMAGE }}:latest
          ${{ env.IMAGE }}:${{ env.GIT_REF }}
          ${{ env.IMAGE }}:${{ env.GIT_SHA }}
          ${{ env.IMAGE }}:${{ env.BUILD_VER }}
        push: true
        secrets: |
            GIT_AUTH_TOKEN=${{ secrets.DOCKER_PASSWORD }}

The last time I did this I used v1 of this build and push action which required less work with properties like add_git_labels, tag_with_ref, tag_with_sha etc. but I suppose that was rather opinionated and less flexible. It also mixed login with build and push. The new version requires more explicit tag and label use, removes the repository property, and login is done beforehand.

See also the Open Container Initiative’s standard labels and note that org.opencontainers.image.source will help link the image to the repo and org.opencontainers.image.description will get picked up by GitHub and shown as the package description.

See the complete change diff for more, which also includes changes for a debug version of this image as well as modifications to a docker pull helper script.

Testing it Out

Kicking off the workflow, the logs look good. By the way, props to the GitHub team on the improved Actions logs.

After the Initial Push

At least currently, and within an organization, the image / package needs to be associated to the repo manually in Package Settings. I thought this might happen more automagically, at least within the context of a GitHub action in the same repo. I think there are some plans to help make this “just work” but there are access considerations.

Note that within an org, if the action pushed to the registry using a service account and you’re logged in as your personal account, you won’t see Package Settings to begin with as the package / image will only be visible to that service account initially. Logging in as that user, the Package Settings can be accessed and the package associated with the repo.

Speaking of repo association, the Docker package initially did not show on the repo Packages sidebar but that was quickly addressed by GitHub (at least while logged in currently).

Browsing the Packages

First I browsed the packages at the org / user level.

Images also show up in the Packages sidebar on the Code tab of the repo, although at least one seems missing for me currently.

Next I checked out the package detail. I like how the tags for the same image are grouped by digest unlike say ACR which lists them all flat. The Readme being brought in is a nice touch too.

Pulling an Image

To pull the image I created a different PAT with just read:packages in my personal account and logged in with that before pulling the image.

Some Other Minor Issues in Beta

Some issues I’ve reported so far during my Beta include:

Most of these are minor with the exception of the credential helper which has been a headache anyway.

Initial Thoughts

Overall I’ve been happy with GitHub Container Registry so far, especially considering it is early Beta. Currently I’m using it for internal images in an on-premise Docker swarm as well as for some personal projects. For some mission critical production apps I’d probably still opt for something like Azure Container Registry (ACR) for features like geo replication, retention, advanced security and scale, easier repo and image management, etc.


The example in this post is part of this Raspberry Pi Cat Motion Siren project and available in this GitHub repo, specifically push-siren.yml and