This repository contains a set of helper scripts for working with containers in GitHub Actions.
You can use the container-image.yml
workflow to create a new container image.
This workflow makes sure that the CI machine is setup for multi-arch, such that
you can build a docker container for multiple architectures. It also logs in to
the GitHub container registry allowing you to upload an image right away. To use
it, add a job to your GitHub workflow calling this workflow:
# ...
jobs:
# ...
docker:
uses: "tweedegolf/actions-helpers/.github/workflows/container-image.yml@main"
with:
push: ${{ github.ref == 'refs/heads/main' }}
platforms: "linux/amd64,linux/arm64"
tags: ghcr.io/tweedegolf/example:latest
# ...
This workflow has several parameters that allow customizing the behavior:
Option | Required | Default |
---|---|---|
tags |
yes | |
List of image tags (newline separated) for the resulting container image | ||
push |
no | false |
If true, the image will be pushed to the registry | ||
platforms |
no | "" |
Comma separated list of platforms to build the image for, i.e. linux/amd64,linux/arm64 . If left empty, will only build for the native platform.
| ||
context |
no | . |
Context directory for the container image build | ||
build-args |
no | "" |
List of build arguments (newline separated) to be inserted in the container image build | ||
file |
no | Dockerfile |
Name of the dockerfile to build | ||
artifact |
no |
|
Download artifact with this name |
Below, you will find a working example where we either just build (and not push) a container image on a pull request, and we fully build and push a container on the main branch, these two require separate permissions. We'll also add a build matrix to allow multiple images to be generated:
-
.github/workflows/docker.yml
name: Docker on: workflow_call: jobs: docker: strategy: matrix: include: - version: trixie latest: false alt: testing - version: bookworm latest: true alt: stable - version: bullseye latest: false alt: oldstable uses: "tweedegolf/actions-helpers/.github/workflows/container-image.yml@main" with: push: ${{ github.ref == 'refs/heads/main' }} platforms: "linux/amd64,linux/arm64" build-args: | DEBIAN_VERSION=${{matrix.version}} tags: | ghcr.io/tweedegolf/debian:${{matrix.version}} ghcr.io/tweedegolf/debian:${{matrix.alt}} ${{ matrix.latest && 'ghcr.io/tweedegolf/debian:latest' || '' }}
This file will be re-used by the two workflows below. As such we only trigger it on
workflow_call
. Note how we use a matrix to build multiple images. Of course for this workflow to run we'll also need a Dockerfile to be built, but that has been omitted in this example. -
.github/workflows/build-push.yml
name: Build and push permissions: contents: read packages: write on: push: branches: - main schedule: - cron: '30 2 * * SUN' jobs: build-and-push: uses: ./.github/workflows/docker.yml
This build and push workflow for the main branch also runs on a schedule every week to keep the image up to date. Note how we require package write permission in this workflow.
-
.github/workflows/check.yml
name: Checks permissions: contents: read on: pull_request: jobs: build: uses: ./.github/workflows/docker.yml secrets: inherit
The checks workflow will just need read permissions for the repository with no write permissions required. We only run it for a pull request.
The GitHub container registry does not automatically remove old tags or untagged
images from itself. To do this, we can setup an actions workflow that does this
for us automatically. There are two steps to this process. First, we remove any
tags that are no longer needed by adding any number of jobs refering to the
container-tag-cleanup.yml
workflow. Finally we finish by adding a single
container-untagged-cleanup.yml
workflow. Of course, if your image creation
workflow never creates any new tags but instead always reuses the existing ones
there is no need to include any of the tag cleanup workflows. Let's look at an
example:
name: Container Registry Cleanup
permissions:
contents: read
packages: write
on:
workflow_dispatch:
schedule:
- cron: '30 2 * * MON'
jobs:
old-nightly-cleanup:
uses: "tweedegolf/actions-helpers/.github/workflows/container-tag-cleanup.yml@main"
with:
package: debian
filter: "^nightly-\\d{2}-\\d{2}-\\d{4}$"
keep_n: 5
untagged-cleanup:
needs: [old-nightly-cleanup]
uses: "tweedegolf/actions-helpers/.github/workflows/container-untagged-cleanup.yml@main"
with:
package: debian
In this example, we run a cleanup job that removes old nightly tagged images first, and then removes any remaining untagged images after that. Note how this action is only run on a schedule once a week, there is no need to run it after every main branch update or every pull request unless lots of container builds are happening on your repository. You might have additional jobs that run the tag cleanup workflow if there are multiple patterns you wish to match, but we always make sure that the untagged cleanup runs after all other jobs have finished to make sure we capture any additional untagged images from the other cleanup jobs.
This workflow has several parameters that allow customizing the behavior:
Option | Required | Default |
---|---|---|
package |
yes | |
Name of the container registry package | ||
filter |
no | "" |
A regular expression matching some specific tags for the container | ||
keep_filter |
no | "" |
A regular expression matching any tags that should not be removed | ||
keep_n |
no | -1 |
How many of the most recently created and matched tags to keep (if non-negative). | ||
older_than |
no | -1 |
Only remove untagged container images at least as old as this (in days), only works if larger than zero. |
This workflow has several parameters that allow customizing the behavior:
Option | Required | Default |
---|---|---|
package |
yes | |
Name of the container registry package | ||
older_than |
no | -1 |
Only remove untagged container images at least as old as this (in days), only works if larger than zero. | ||
untagged_timestamp_tolerance |
no | 10000 |
Do not remove untagged container images if they have a created timestamp this close (in milliseconds) to a tagged container image. We do this because multi-arch docker containers will be pushed as separate untagged images to the container registry and GitHub cannot recognize them as part of a tagged image. Setting this option large enough prevents accidental deletion of one of these images. |