¯\_(ツ)_/¯

thunder@home:~$

This is my home blog, mostly to share some useful info or code snippets
~ 9 mins

Heya!

While I was working on my little Docker container with image scanner tools, I wanted to automate build and push that container into DockerHub and GitHub registry.

First of all, did you know, that GitHub now has its own Docker registry? And you can push your containers in there?

How much is the fish and Pricing table

Free plan includes 500MB of storage and Data Transfer 1Gb/mo. More info on pricing: packages#pricing

More about GitHub Container Registry

More info about that you can find on this page: about-github-container-registry

So, main idea is to trigger Container build if I push some Tag with version. My flow will checkout repository, build the container, tag it and push it to my DockerHub account and my GitHub account into Container Registry.

Lets create our flow!

Creating .github/workflows/deploy.yaml

Create .github/workflows/deploy.yaml file in repository’s root.

Initially we want to trigger build on Tag push event, Tag should start with v, e.g. v1.0.0.

name: Deploy Docker Image

# Run workflow on tags starting with v (eg. v2, v1.2.0)
on:
  push:
    tags:
      - v*
env:
  IMAGE_NAME: docker-scanner

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v1

On Tag push it will checkout this repository. I also set IMAGE_NAME as Environment variable and will use it later on Push activities.

Login into multiple registries

Before this you need to create secrets with Tokens to be able to access DockerHub and GitHub registries.

Token for DockerHub

Navigate to settings/security on DockerHub and click New Access Token button. Enter description and click Create button. DockerHub will generate one.

Remember token!

This token will be shown only once, so remember it! You won’t be able to see it again.

Now, navigate to GitHub, open desired Repository settings, choose Secrets on the left menu and click on New repository secret button. Enter name of the secret which we will use in Workflow step for login. Lets define its name as DH_REGISTRY and put our DockerHub token as Value. Also, I’ve put my DockerHub user name as secret as well. So, create another secret with name DH_USER and your DockerHub user name as Value.

We’re done with DockerHub. Now lets setup GitHub Container Registry token.

Token for GitHub

In general, GitHub Actions provide token for authentication (available voa GITHUB_TOKEN), but? for now, it does not support Container Registry auth, so we need to create separate token.

Navigate to your user Settings on GitHub, then Developer settings and Personal access tokens. Now, click on Generate new token, enter some description on Note field and tick repo checkbox (will autotick all sub checkboxes) and write:packages checkbox (will autotick read:packages as well). Click Generate token button at the bottom.

Or just click this link, which will set up all required checkboxes :)

Remember token!

This token will be shown only once, so remember it! You won’t be able to see it again.

Now, lets create a secret in your repository with this token to provide possibility to push image to GitHub Container Registry. Navigate to GitHub, open desired Repository settings, choose Secrets on the left menu and click on New repository secret button. Enter name of the secret which we will use in Workflow step for login. Lets define its name as GH_REGISTRY and put our GitHub token as Value.

We’re done with all secrets and lets continue with Workflow.

Workflow steps

Once we created all required secrets, lets add Login steps into our Workflow.

- name: Login to GitHub Container Registry
  uses: docker/login-action@v1
  with:
    registry: ghcr.io
    username: ${{ github.repository_owner }}
    password: ${{ secrets.GH_REGISTRY }}

- name: Login to DockerHub Container Registry
  uses: docker/login-action@v1
  with:
    username: ${{ secrets.DH_USER }}
    password: ${{ secrets.DH_REGISTRY }}

As you can see, we use secrets object which keep all our repository’s secrets.

GitHub Actions Secrets

More on Secrets in GitHub actions you can find on this page: encrypted-secrets

Those two steps log us in into GitHub Registry (first) and DockerHub Registry (second).

Easy, isn’t it?

Build and Push image

Last step or our Workflow is building and pushing image. We can use precreated Action – docker/build-push-action.

- name: Build and Push Docker Image
  uses: docker/build-push-action@v2
  with:
    push: true # Will only build if this is not here
    tags: |
      ${{ secrets.DH_USER }}/${{ env.IMAGE_NAME }}:latest
      ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest

push: true is mandatory, otherwise this action will just build container.

But, what if we want to tag containers with specific version which we defined as tag?

Getting version for container from tag

We can get referenced tag by using github.ref field exported into Actions, but it will return proper reference – ref/tags/ourtag. Also, same value available as Environment variable – GITHUB_REF.

GitHub Actions Variables

More info on github context

More info on Environment Variables

More info on Workflow commands

There are many ways to get tag name only. Here is some of them, as an example I’ll put all ways in same step:

Option 1

- name: get tag and pass to other steps
  run: |
    # Strip git ref prefix from version
    TAG_1=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')

    # Strip "v" prefix from tag name
    [[ "${{ github.ref }}" == "refs/tags/"* ]] && TAG_1=$(echo $TAG_1 | sed -e 's/^v//')

    # Register this as Environment Variable
    echo "TAG_1=${TAG_1}" >> $GITHUB_ENV
- name: use tag
  run: |
    echo ${{ env.TAG_1 }}

Option 2

- name: get tag and pass to other steps
  run: |
    TAG_2=${GITHUB_REF#refs/tags/}

    echo "TAG_2=${TAG_2}" >> $GITHUB_ENV
- name: use tag
  run: |
    echo ${{ env.TAG_2 }}

Environment Files -- $GITHUB_ENV

More about Environment Files you can find on this page: workflow-commands-for-github-actions#environment-files

Option 3

set-env is deprecated

This Workflow function is deprecated. To make it work, you need to define ACTIONS_ALLOW_UNSECURE_COMMANDS, but you should not do that, consider using other options.

- name: get tag and pass to other steps
  env:
    ACTIONS_ALLOW_UNSECURE_COMMANDS: true
  run: |
    echo ::set-env name=TAG_3::${GITHUB_REF#refs/tags/}
- name: use tag
  run: |
    echo ${{ env.TAG_3 }}

Option 4

Must set Step ID

This option require setting Step ID. In our example, we set id to version. This ID will be used later to get our output.

- name: get tag and pass to other steps
  id: version
  run: |
    echo ::set-output name=TAG_4::${GITHUB_REF#refs/tags/}
- name: use tag
  run: |
    echo ${{ steps.version.outputs.TAG_4 }}

Preferred option is 1 or 2.

Now we know how to get tag and pass it to other steps.

Adding Tag to our Container

Lets add our tag to built container.

Here is both steps – Get tag and Build and Push container:

- name: Get the version from tags for DockerHub
  run: |
    # Strip git ref prefix from version
    IMAGE_VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')

    # Strip "v" prefix from tag name
    [[ "${{ github.ref }}" == "refs/tags/"* ]] && IMAGE_VERSION=$(echo $IMAGE_VERSION | sed -e 's/^v//')

    # GitHub Registry:
    ## Set Owner name
    GH_OWNER="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')"

    # Put proper values as Environment variable
    echo "IMAGE_VERSION=${IMAGE_VERSION}" >> $GITHUB_ENV
    echo "DH_IMAGE_NAME=${{ secrets.DH_USER }}/${{ env.IMAGE_NAME }}" >> $GITHUB_ENV
    echo "GH_IMAGE_NAME=ghcr.io/${GH_OWNER}/${{ env.IMAGE_NAME }}" >> $GITHUB_ENV

- name: Build and Push Docker Image
  uses: docker/build-push-action@v2
  with:
    push: true # Will only build if this is not here
    tags: |
      ${{ env.DH_IMAGE_NAME }}:${{ env.IMAGE_VERSION }}
      ${{ env.DH_IMAGE_NAME }}:latest
      ${{ env.GH_IMAGE_NAME }}:${{ env.IMAGE_VERSION }}
      ${{ env.GH_IMAGE_NAME }}:latest

IMAGE_NAME variable

If you remember, we defined this variable at the top of our workflow as a global environment variable.

That’s it!

Linking our container in GitHub Container Registry with repository

This part is specific to GitHub Container Registry. When you push container, it will be added to your profile as a Package. They are not linked to any Repository at this point. And this package will not be filled with any information, like Readme or Description.

Lets link this Package to our Source repository, attach README.md and set small Description.

This could be done easily by providing correct image Labels.

Connecting a repository to a container image on the command line

Official documentation on this, you can find here: connecting-a-repository-to-a-container-image-on-the-command-line

More info on OpenContainers specification, you can find here: annotations.md#pre-defined-annotation-keys

Now, lets add some more data gathering and Labels to our containers:

- name: Get the version from tags for DockerHub
  run: |
    # Strip git ref prefix from version
    IMAGE_VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')

    # Strip "v" prefix from tag name
    [[ "${{ github.ref }}" == "refs/tags/"* ]] && IMAGE_VERSION=$(echo $IMAGE_VERSION | sed -e 's/^v//')

    # GitHub Registry:
    ## Set Owner name
    GH_OWNER="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')"

    # Put proper values as Environment variable
    echo "IMAGE_VERSION=${IMAGE_VERSION}" >> $GITHUB_ENV
    echo "DH_IMAGE_NAME=${{ secrets.DH_USER }}/${{ env.IMAGE_NAME }}" >> $GITHUB_ENV
    echo "GH_IMAGE_NAME=ghcr.io/${GH_OWNER}/${{ env.IMAGE_NAME }}" >> $GITHUB_ENV
    echo "BUILD_DATE=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_ENV
    echo "GIT_SHA=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_ENV
    echo "GIT_REF=$(git symbolic-ref -q --short HEAD || git describe --tags --exact-match)" >> $GITHUB_ENV
- name: Build and Push Docker Image
  uses: docker/build-push-action@v2
  with:
    push: true # Will only build if this is not here
    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 }}
      org.opencontainers.image.ref.name=${{ env.GIT_REF }}
      org.opencontainers.image.revision=${{ github.sha }}
      org.opencontainers.image.source=https://github.com/${{ github.repository }}
      org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
    tags: |
      ${{ env.DH_IMAGE_NAME }}:${{ env.IMAGE_VERSION }}
      ${{ env.DH_IMAGE_NAME }}:latest
      ${{ env.GH_IMAGE_NAME }}:${{ env.IMAGE_VERSION }}
      ${{ env.GH_IMAGE_NAME }}:latest

For the repository linking see org.opencontainers.image.source=https://github.com/${{ github.repository }} label.

Short Description – org.opencontainers.image.description label. Value should be less than 512 chars.

You don’t have to specify org.opencontainers.image.documentation label, by default, GHCR will use README.md from source repository.

So, that is it. Easy, isn’t it?

Full workflow to build and push container into multiple registries

Here is my full workflow to build and push container into DockerHub and GitHub registries:

name: Deploy Docker Image

# Run workflow on tags starting with v (eg. v2, v1.2.0)
on:
  push:
    tags:
      - v*
env:
  IMAGE_NAME: docker-scanner

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v1

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GH_REGISTRY }}

      - name: Login to DockerHub Container Registry
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DH_USER }}
          password: ${{ secrets.DH_REGISTRY }}

      - name: Get the version from tags for DockerHub
        id: version
        run: |
          # Strip git ref prefix from version
          IMAGE_VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')

          # Strip "v" prefix from tag name
          [[ "${{ github.ref }}" == "refs/tags/"* ]] && IMAGE_VERSION=$(echo $IMAGE_VERSION | sed -e 's/^v//')

          # GitHub Registry:
          ## Set Owner name
          GH_OWNER="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')"

          # Put proper values as Environment variable
          echo "IMAGE_VERSION=${IMAGE_VERSION}" >> $GITHUB_ENV
          echo "DH_IMAGE_NAME=${{ secrets.DH_USER }}/${{ env.IMAGE_NAME }}" >> $GITHUB_ENV
          echo "GH_IMAGE_NAME=ghcr.io/${GH_OWNER}/${{ env.IMAGE_NAME }}" >> $GITHUB_ENV

          echo "BUILD_DATE=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_ENV
          echo "GIT_SHA=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_ENV
          echo "GIT_REF=$(git symbolic-ref -q --short HEAD || git describe --tags --exact-match)" >> $GITHUB_ENV

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v2
        with:
          push: true # Will only build if this is not here
          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 }}
            org.opencontainers.image.ref.name=${{ env.GIT_REF }}
            org.opencontainers.image.revision=${{ github.sha }}
            org.opencontainers.image.source=https://github.com/${{ github.repository }}
            org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
          tags: |
            ${{ env.DH_IMAGE_NAME }}:${{ env.IMAGE_VERSION }}
            ${{ env.DH_IMAGE_NAME }}:latest
            ${{ env.GH_IMAGE_NAME }}:${{ env.IMAGE_VERSION }}
            ${{ env.GH_IMAGE_NAME }}:latest

Happy build and pushing!

Thank You For Reading