In my job, I build a lot of samples that I share with customers to show them how things work. A lot of my customers are interested in Azure Container Apps, so I want to be able to provide them with samples with pre-built container images, without having to share the entire application source code as well (especially if I’ve got a bunch of basic microservices, that don’t really need to be included in the sample).

Enter GitHub Container Registry! (GHCR) I’ve seen a couple of sample repos where container images were being referenced from GHCR, but I didn’t know it worked or how to push images to it. Turns out, it was fairly straightforward.

In this post, I’ll talk about what GHCR is, and how we can push container images to it using GitHub Actions!

What is GitHub Container Registry?

GitHub Container Registry stores container images within your organization or personal account, and allows you to associate an image with a repository. It currently supports both the Docker Image Manifest V2, Schema 2 and Open Container Initiative (OCI) specifications.

In GitHub, we can build and push our docker images to GHCR within a GitHub Actions workflow file and make those images available either privately or publicly (I’m making my images public for my samples).

Let’s say I have the following Dockerfile:

FROM AS base
LABEL org.opencontainers.image.source=""

FROM AS build
COPY ["Store/Store.csproj", "Store/"]
RUN dotnet restore "Store/Store.csproj"
COPY . .
WORKDIR "/src/Store"
RUN dotnet build "Store.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Store.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Store.dll"]

This Dockerfile is just building a simple Blazor Server application (It’s a pretty generic Dockerfile for all ASP.NET Core applications).

Instead of pushing this to Docker Hub or Azure Container Registry, we’re going to set up a GitHub Actions workflow file to push this container image into GHCR.

Let’s take a look at how we can authenticate to GHCR.

Using the GITHUB_TOKEN to authenticate to GHCR

The recommended method for authenticating to GHCR is to use the GITHUB_TOKEN. GitHub provides you with a token that you can use to authenticate on behalf of GitHub Actions. At the start of each workflow run, GitHub will automatically create a unique GITHUB_TOKEN secret to use in the workflow, which you can use to authenticate.

When GHCR was in Beta, you could use a Personal Access Token (PAT) to authenticate. You’d need to be careful about the permissions that you gave the PAT token. With GITHUB_TOKEN, this comes with sufficient permissions needed to push container images to GHCR

Using a Personal Access Token to authenticate to GHCR

I did have some trouble using the GITHUB_TOKEN initially, so to get started, I used a PAT token. To create one, go to Settings/Developer settings, click on Personal access tokens/Tokens (classic) and then click on Generate new token. To push images to GHCR, you only need the following permissions:

  • read:packages
  • write:packages
  • delete:packages

Once you’ve created the PAT, you can store it as a repository secret inside your GitHub repository that contains your Dockerfile.

Within your GitHub Actions workflow file, you can then authenticate to GHCR using the following:

- name: 'Login to GitHub Container Registry'
        run: |
                    echo $CR_PAT | docker login -u <Your-GitHub-username> --password-stdin

Since it’s recommended to use GITHUB_TOKEN instead of PAT tokens, we’ll use that moving forward.

Creating a GitHub Actions workflow

Now that we’ve got a way to authenticate to GHCR, we can create a GitHub Actions workflow file to push our container image. Let’s take a look at the following:

name: Deploy Images to GHCR

      - main

        runs-on: ubuntu-latest
            working-directory: './Store'
          - name: 'Checkout GitHub Action'
            uses: actions/checkout@main
          - name: 'Login to GitHub Container Registry'
            uses: docker/login-action@v1
              username: ${{}}
              password: ${{secrets.GITHUB_TOKEN}}
          - name: 'Build Inventory Image'
            run: |
              docker build . --tag<your-GitHub-username>/store:latest
              docker push<your-GitHub-username>/store:latest              

The most important steps to highlight are authenticating to GHCR, and then pushing the container image.

To authenticate, we can use the docker/login-action, target as the registry, and use our username (passed in as ${{ }}) and our GITHUB_TOKEN as the password.

Once we’ve been authenticated, we can tag our image, using the format<your-github-username>/<image-name>:<image-version>.

Making our image publicly accessible

Now by default, when we publish a package the visibility will be private. You can keep your images private if you want, but for my samples I want them to be publicly available.

To make them available to our repository, we need to add a LABEL command to our Dockerfile. You should do this underneath the first FROM command like so:

FROM AS base
LABEL org.opencontainers.image.source="<your-github-username>/<your-repo-name>"

This will make our images visible on our repository’s main page, like so:

GitHub repository with the packages section highlighted with a red rectangle

Click on the package that you want to make public, then go to Package Settings. In the Danger Zone (cue Kenny Loggins), click on change visibility and choose Public like so:

GitHub Package settings for changing package visibility

Now that our package is public, we can pull it using docker pull like so:

docker pull


In this post, we talked about what GHCR is, how we can authenticate and push images to it using to it using GitHub Actions and then those images public.

Hopefully this has helped you with publishing container images to GitHub Container Registry. For private container images, I’ll still use Azure Container Registry, but authenticating and pushing images to GHCR for the purposes of samples looks like the way to go.

If you have any questions, feel free to reach out to me on twitter @willvelida

Until next time, Happy coding! 🤓🖥️