GitHub Container Registry: How to push Docker images to GitHub 🐋
Tags: docker, devops, github, webdev
Published: 2024-07-11 10:00:00

> _The GitHub Container Registry allows you to publish or host private and public Docker images. You can find out everything you need to know in this tutorial._

With its registry service **GitHub Packages**, GitHub provides a way for developers to host their own Docker images directly in GitHub. In my opinion, the name of GitHub is somewhat misleading: the umbrella term for the service is GitHub Packages. It contains a number of registries. These include the **Docker Registry**, which has been renamed the **Container Registry** in order to host all types of containers.

![GitHub Packages Structure](https://webdeasy.de/wp-content/uploads/2024/06/github-packages.png)

## Why not use Docker Hub? 🐋
The [Docker Hub](https://hub.docker.com/) is the first port of call when it comes to Docker images – no question about it. In the free version, however, only one private image is available to each account and the number of pulls (200 pulls per 6 hours) is also limited. And this is where the GitHub Container Registry comes into play! 🙂


## Push Docker Images into the GitHub Registry
To create a Docker image, we need a Docker image, which we have to create with a Dockerfile. You can create such a Dockerfile using the [docker init command](https://webdeasy.de/en/docker-init-command/), for example. Once the file is available, we can build the image:
```
docker build -t frontend .
```
![Screenshot from the Docker build process](https://webdeasy.de/wp-content/uploads/2024/06/docker-build.jpg)

In the next step, we need to log in to the GitHub Container Registry. To do this, we need a personal access token from GitHub.


### Generate Personal Access Token (PAT)
To generate a new PAT, navigate to **GitHub Settings > Developer seettings > Personal access tokens > Tokens (classic)** and create a new token. You must select **“write:packages”** and** “read:packages”** as the scope. You can adjust the duration of the token to your project or set it to unlimited.
![Create GitHub Personal Access Token (PAT)](https://webdeasy.de/wp-content/uploads/2024/06/pat-1.jpg)
You should save the token, e.g. in your [password manager](https://webdeasy.de/en/password-manager/). If you lose the token, you will have to regenerate it.

Now open a terminal on your computer and set your PAT in a variable (replace `<YOUR_PAT>` with your token):
```
# Set PAT into a variable
export GH_PAT=<YOUR_PAT>
```
To log in to the GitHub Container Registry (ghcr.io), use the following command (replace `<USERNAME>` with your GitHub username).
```
# Login to the GitHub Container Registry using your PAT
echo $GH_PAT | docker login ghcr.io -u <USERNAME> --password-stdin
```
If the PAT and your user name were correct, you should receive the message “Login Succeeded”.
![Successful Docker login to the GitHub Container Registry (ghcr.io)](https://webdeasy.de/wp-content/uploads/2024/06/docker-login-ghcr.jpg)
Next, we tag the image so that it also ends up in the correct registry. Replace `<USERNAME>` with your GitHub username, `<IMAGE_NAME>` with the desired name of the image and `<TAG>` with the desired tag (default: `latest`)
```
# Tag the builded docker image
docker tag frontend ghcr.io/<USERNAME>/<IMAGE_NAME>:<TAG>
```
Now we can push the image into the registry. Here you have to replace the same variables as when tagging:
```
docker push ghcr.io/<USERNAME>/<IMAGE_NAME>:<TAG>
```
If you have done everything correctly, you should receive such an output or no error message 😉
![GitHub Docker Regsitry: Tagging and pushing Docker images](https://webdeasy.de/wp-content/uploads/2024/06/docker-ghcr-push.jpg)
_Congratulations! You have successfully pushed your first Docker image to the GitHub registry!_ 🎉
![GitHub Packages](https://webdeasy.de/wp-content/uploads/2024/06/github-packages.jpg)
On this page you now have further options, such as changing the visibility or deleting the image.


## Pulling GitHub Docker Images
Of course, you can now also use your Docker images from the registry. The path is the same as for pushing:
```
docker pull ghcr.io/<USERNAME>/<IMAGE_NAME>:<TAG>
```


## GitHub Actions Workflow for Docker Images in the GitHub Registry
Pushing Docker images manually is daft! That’s why there are [CI/CD pipelines](https://webdeasy.de/en/what-is-ci-cd/) that we can implement with [GitHub Actions](https://webdeasy.de/en/github-actions-tutorial-en/), for example. For certain events, e.g. a new commit, we can automatically build the Docker image and publish it in the registry. In the following example, a Docker image is built and published when the frontend branch is pushed. Building and publishing is already done after the “Build and push Docker image” step. However, I have left the last two steps in for the sake of completeness, as this is a workflow of one of my projects and you may also have a use case for it 😉

In your GitHub repository, you can simply create a file under `.github/workflows/cd.yml`.

```yaml
name: Continuous Deployment - Frontend

on:
  push:
    branches: ['frontend']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      attestations: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Log in to the Container registry
        uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        id: push
        uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

      - name: Install SSH client
        run: sudo apt-get update && sudo apt-get install -y openssh-client

      - name: Deploy to VM
        env:
          SSH_HOST: ${{ vars.SSH_HOST }}
          SSH_USER: ${{ secrets.SSH_USER }}
          SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
          IMAGE_TAG: ${{ steps.meta.outputs.tags }}
        run: |
          echo "${{ env.SERVER_SSH_KEY }}" > key.pem
          chmod 600 key.pem
          ssh -i key.pem -o StrictHostKeyChecking=no ${{ env.SSH_USER }}@${{ env.SSH_HOST }} << 'EOF'
            docker login ${{ env.REGISTRY }} -u ${{ github.actor }} -p ${{ secrets.MY_PERSONAL_PAT }}
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
            cd ~
            docker compose down frontend
            docker compose up -d frontend
          EOF
          # Clean up the private key.pem
          rm key.pem
```

And don’t forget: Create the used secrets and variables in the repository settings. In this case:
- **Secrets:**
  - MY_PERSONAL_PAT
  - SERVER_SSH_KEY
  - SSH_USER
- **Variables:**
  - SSH_HOST
![GitHub Repository: Secrets and variables](https://webdeasy.de/wp-content/uploads/2024/06/github-repo-settings-action-secrets.jpg)


## GitHub Container Registry: Conclusion
That’s it! Building and publishing Docker images in the GitHub Docker Registry or GitHub Container Registry is very similar to Docker Hub. Only the login via the GitHub PAT is a little more complicated, but also offers advantages in terms of authorisation management, etc. Will you be using the GitHub Packages Registry in the future?