Table of Contents
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 push
ing!