Skip to content

Docker: build and publish container images #180

Docker: build and publish container images

Docker: build and publish container images #180

name: 'Docker: build and publish container images'
on:
push:
branches:
- master
paths-ignore:
- 'doc/**'
- 'examples/**'
- 'Formula/**'
- 'tools/get-version'
- 'windows/**'
- '**.md'
schedule:
- cron: '22 2 */6 * *' # every 6 days to avoid gha cache being evicted
pull_request:
paths-ignore:
- 'doc/**'
- 'examples/**'
- 'Formula/**'
- 'tools/get-version'
- 'windows/**'
- '**.md'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
DOCKERHUB_REPO: eturnal/eturnal
PKGREL_FILE: tools/ctrrel
jobs:
################################################################################
#' check whether to compile from master branch or from tagged version
detect-change:
name: Check ctr version change
runs-on: ubuntu-latest
outputs:
update: ${{ steps.check_version_changed.outputs.update }}
steps:
-
name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 2
-
name: Compare ctr package-release vsn between commits
id: check_version_changed
run: |
TAG_PKGREL=$(awk 'END{print}' ${{ env.PKGREL_FILE }})
git checkout HEAD^
TAG_PKGREL_BASELINE=$(awk 'END{print}' ${{ env.PKGREL_FILE }})
if [ "$TAG_PKGREL" != "$TAG_PKGREL_BASELINE" ]
then echo "update=true" >> $GITHUB_OUTPUT
else echo "update=false" >> $GITHUB_OUTPUT
fi
################################################################################
#' build musl-libc based binary tarballs for x64/arm64
build-musl-binary-archives:
name: ${{ matrix.arch }} - build musl-libc based binary archives
runs-on: ubuntu-latest
strategy:
matrix:
arch: [x64, arm64]
fail-fast: false
needs: [detect-change]
steps:
-
name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: Cache toolchain directory
uses: actions/cache@v4
with:
path: ~/build/
key: ${{runner.os}}-ct-ng-1.25.0-${{ matrix.arch }}-musl
-
name: On push master | extract git version
if: needs.detect-change.outputs.update == 'false'
run: echo "TAG_VERSION=$(echo "$(./tools/get-version)" | sed -e 's|+|-|')" >> $GITHUB_ENV
-
name: On release | extract release tag
if: needs.detect-change.outputs.update == 'true'
run: echo "TAG_VERSION=$(awk 'END{gsub("-", " "); print $1}' ${{ env.PKGREL_FILE }})" >> $GITHUB_ENV
-
name: On release | check out release to be published/updated
uses: actions/checkout@v4
if: needs.detect-change.outputs.update == 'true'
with:
ref: ${{ env.TAG_VERSION }}
-
name: Install prerequisites, obtain erlang/otp version & target arch ...
run: |
sudo apt-get -qq update
# https://github.com/crosstool-ng/crosstool-ng/blob/master/testing/docker/ubuntu22.04/Dockerfile
sudo apt-get -qq install makeself build-essential \
gcc g++ gperf bison flex texinfo help2man make libncurses5-dev \
python3-dev autoconf automake libtool libtool-bin gawk wget bzip2 \
xz-utils unzip patch libstdc++6 rsync git meson ninja-build \
binfmt-support qemu-user-static
echo "OTP_VSN=$(awk '/^otp_vsn=/ {{gsub(/[^0-9.]/, ""); print}}' tools/make-binaries)" >> $GITHUB_ENV
echo "ARCH=$(echo ${{ matrix.arch }} | sed -e 's|x64|x86_64|;s|arm64|aarch64|')" >> $GITHUB_ENV
-
name: Install erlang/OTP
uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VSN }}
version-type: strict
-
name: Build ${{ matrix.arch }} musl-libc based binary archives
run: CHECK_DEPS=false tools/make-binaries ${{ env.ARCH }}-linux-musl
-
name: Start container for rebar3 test suites ...
run: |
rebar3_path="$HOME/build/bootstrap/bin"
otp_path="$HOME/build/eturnal/${{ env.ARCH }}-linux-musl/bin"
alpine_path='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
arch=$(echo ${{ matrix.arch }} | sed -e 's|x64|amd64|')
docker run --init -d --name test-suites \
--platform linux/$arch \
-v $HOME/build:$HOME/build \
-v $PWD:/eturnal \
--workdir /eturnal \
-e PATH=$rebar3_path:$otp_path:$alpine_path \
docker.io/alpine:latest \
sleep 600
- # add build-tools & git, if rebar.config uses e.g. "github" as source
name: add build tools to the container ...
run: docker exec test-suites apk add build-base git yaml-dev
-
name: rebar3 xref ...
run: docker exec test-suites rebar3 xref
-
name: rebar3 eunit ...
run: docker exec test-suites rebar3 eunit
-
name: rebar3 ct ...
run: docker exec test-suites rebar3 ct
-
name: Stop rebar3 test container ...
run: docker stop test-suites
-
name: Upload artifact | ${{ matrix.arch }} musl-libc based binary tarball
uses: actions/upload-artifact@v4
with:
name: eturnal-${{ env.TAG_VERSION }}-linux-musl-${{ matrix.arch }}.tar.gz
path: eturnal-*-linux-musl-${{ matrix.arch }}.tar.gz
if-no-files-found: error
retention-days: 15
################################################################################
#' build actual images for x64/arm64 with built binary tarballs
build-ctr-binary-based:
name: ${{ matrix.arch }} - ${{ matrix.method }}-based variant
runs-on: ubuntu-latest
strategy:
matrix:
arch: [x64, arm64]
method: [package]
fail-fast: false
needs: [detect-change, build-musl-binary-archives]
steps:
-
name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: On push master | extract git version
if: needs.detect-change.outputs.update == 'false'
run: echo "TAG_VERSION=$(echo "$(./tools/get-version)" | sed -e 's|+|-|')" >> $GITHUB_ENV
-
name: On release | extract release tag
if: needs.detect-change.outputs.update == 'true'
run: echo "TAG_VERSION=$(awk 'END{gsub("-", " "); print $1}' ${{ env.PKGREL_FILE }})" >> $GITHUB_ENV
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Extract build & environment variables
run: |
echo "DOCKERFILE_PATH=$(dirname $(find . -name Dockerfile))" >> $GITHUB_ENV
echo "ARCH=$(echo ${{ matrix.arch }} | sed -e 's|x64|amd64|')" >> $GITHUB_ENV
-
name: Download artifact | ${{ matrix.arch }} musl-libc based binary tarball
uses: actions/download-artifact@v4
with:
name: eturnal-${{ env.TAG_VERSION }}-linux-musl-${{ matrix.arch }}.tar.gz
-
name: Log in to ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
-
name: Standalone | build & push by digest
id: build-so
uses: docker/build-push-action@v5
with:
build-args: |
METHOD=${{ matrix.method }}
VERSION=${{ env.TAG_VERSION }}
REPOSITORY=https://github.com/${{ github.repository }}.git
VARIANT=standalone
context: .
file: ${{ env.DOCKERFILE_PATH }}/Dockerfile
platforms: linux/${{ env.ARCH }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
-
name: Simple smoke test
run: |
set -x
docker run -d --name eturnal \
--platform linux/${{ env.ARCH }} \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-so.outputs.digest }} \
sleep 600
docker exec eturnal eturnalctl daemon
docker exec eturnal eturnalctl ping
docker exec eturnal eturnalctl info
docker logs eturnal
docker stop eturnal
-
name: Standalone | export digest | ${{ matrix.arch }}
run: |
mkdir -p /tmp/digests-so
digest="${{ steps.build-so.outputs.digest }}"
touch "/tmp/digests-so/${digest#sha256:}"
-
name: Standalone | upload digest | ${{ matrix.arch }}
uses: actions/upload-artifact@v4
with:
name: digests-standalone-${{ matrix.arch }}
path: /tmp/digests-so/*
if-no-files-found: error
retention-days: 5
-
name: ACME | build & push by digest
id: build-as
uses: docker/build-push-action@v5
with:
build-args: |
METHOD=${{ matrix.method }}
VERSION=${{ env.TAG_VERSION }}
REPOSITORY=https://github.com/${{ github.repository }}.git
VARIANT=acme
context: .
file: ${{ env.DOCKERFILE_PATH }}/Dockerfile
platforms: linux/${{ env.ARCH }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
-
name: ACME | export digest | ${{ matrix.arch }}
run: |
mkdir -p /tmp/digests-as
digest="${{ steps.build-as.outputs.digest }}"
touch "/tmp/digests-as/${digest#sha256:}"
-
name: ACME | upload digest | ${{ matrix.arch }}
uses: actions/upload-artifact@v4
with:
name: digests-acme-${{ matrix.arch }}
path: /tmp/digests-as/*
if-no-files-found: error
retention-days: 5
################################################################################
#' build Erlang/OTP and rebar3 for directly compilation.
build-erlang-rebar:
name: Build Erlang/OTP and Rebar3
runs-on: ubuntu-latest
needs: [detect-change]
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
-
name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: On push master | extract git version and define build mode
if: needs.detect-change.outputs.update == 'false'
run: |
echo "OTP_VSN=$(awk '/^otp_vsn=/ {{gsub(/[^0-9.]/, ""); print}}' tools/make-binaries)" >> $GITHUB_ENV
echo "REBAR_VSN=$(awk '/^rebar_vsn=/ {{gsub(/[^0-9.]/, ""); print}}' tools/make-binaries)" >> $GITHUB_ENV
-
name: On release | extract git version and define build mode
if: needs.detect-change.outputs.update == 'true'
run: |
echo "OTP_VSN=$(wget -O - https://raw.githubusercontent.com/${{ github.repository }}/$eturnal_vsn/tools/make-binaries \
| awk '/^otp_vsn=/ {{gsub(/[^0-9.]/, ""); print}}')" >> $GITHUB_ENV
echo "REBAR_VSN=$(wget -O - https://raw.githubusercontent.com/${{ github.repository }}/$eturnal_vsn/tools/make-binaries \
| awk '/^rebar_vsn=/ {{gsub(/[^0-9.]/, ""); print}}')" >> $GITHUB_ENV
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host
-
name: Build Erlang/OTP ${{ env.OTP_VSN }} base image
uses: docker/build-push-action@v5
with:
build-args: |
OTP_VSN=${{ env.OTP_VSN }}
REBAR_VSN=${{ env.REBAR_VSN }}
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: tools/Dockerfile.erlang
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/386,linux/arm/v7,linux/ppc64le
push: true
tags: localhost:5000/erlang:otp-${{ env.OTP_VSN }}
################################################################################
#' build container images with local source files
build-ctr:
name: ${{ matrix.arch }} - ${{ matrix.method }}-based variant
runs-on: ubuntu-latest
strategy:
matrix:
arch: [386, arm/v7, ppc64le]
method: [build]
fail-fast: false
needs: [detect-change, build-erlang-rebar]
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
-
name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: On push master | extract git version and define build mode
if: needs.detect-change.outputs.update == 'false'
run: |
echo "VSN=$(git describe --tag)" >> $GITHUB_ENV
echo "TAG_VERSION=$(echo "$(./tools/get-version)" | sed -e 's|+|-|')" >> $GITHUB_ENV
echo "SRC=local" >> $GITHUB_ENV
echo "OTP_VSN=$(awk '/^otp_vsn=/ {{gsub(/[^0-9.]/, ""); print}}' tools/make-binaries)" >> $GITHUB_ENV
echo "REBAR_VSN=$(awk '/^rebar_vsn=/ {{gsub(/[^0-9.]/, ""); print}}' tools/make-binaries)" >> $GITHUB_ENV
echo "ARCH=$(echo ${{ matrix.arch }} | sed -e 's|/|-|')" >> $GITHUB_ENV
-
name: On release | extract git version and define build mode
if: needs.detect-change.outputs.update == 'true'
run: |
eturnal_vsn=$(awk 'END{gsub("-", " "); print $1}' ${{ env.PKGREL_FILE }})
echo "VSN=$eturnal_vsn" >> $GITHUB_ENV
echo "TAG_VERSION=$(awk 'END{gsub("-", " "); print $1}' ${{ env.PKGREL_FILE }})" >> $GITHUB_ENV
# define erlang/OTP version
echo "OTP_VSN=$(wget -O - https://raw.githubusercontent.com/${{ github.repository }}/$eturnal_vsn/tools/make-binaries \
| awk '/^otp_vsn=/ {{gsub(/[^0-9.]/, ""); print}}')" >> $GITHUB_ENV
echo "REBAR_VSN=$(wget -O - https://raw.githubusercontent.com/${{ github.repository }}/$eturnal_vsn/tools/make-binaries \
| awk '/^rebar_vsn=/ {{gsub(/[^0-9.]/, ""); print}}')" >> $GITHUB_ENV
# check whether to build from archive or git, because git repo is HEAD here
# hence, we would not build the specifc release when updating the ctr image
pkgrel=$(awk 'END{gsub("-", " "); print $2}' ${{ env.PKGREL_FILE }})
if [ "$pkgrel" != 'r0' ]
then echo "SRC=web" >> $GITHUB_ENV
else echo "SRC=local" >> $GITHUB_ENV
fi
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host
-
name: Build Erlang/OTP ${{ env.OTP_VSN }} base image
uses: docker/build-push-action@v5
with:
build-args: |
OTP_VSN=${{ env.OTP_VSN }}
REBAR_VSN=${{ env.REBAR_VSN }}
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: tools/Dockerfile.erlang
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/386,linux/arm/v7,linux/ppc64le
push: true
tags: localhost:5000/erlang:otp-${{ env.OTP_VSN }}
-
name: Extract build & environment variables
run: |
echo "DOCKERFILE_PATH=$(dirname $(find . -name Dockerfile))" >> $GITHUB_ENV
# Check if rebar3 common test should be performed
# fix for slow architectures was introduces after version 1.10.1
if dpkg --compare-versions ${{ env.TAG_VERSION }} le "1.10.1"
then echo "REBAR_CT=false" >> $GITHUB_ENV
else echo "REBAR_CT=true" >> $GITHUB_ENV
fi
-
name: Log in to ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
-
name: Standalone | build & push by digest
id: build-so
uses: docker/build-push-action@v5
with:
build-args: |
SOURCE=${{ env.SRC }}
VERSION=${{ env.VSN }}
BUILD_IMAGE=localhost:5000/erlang:otp-${{ env.OTP_VSN }}
REPOSITORY=https://github.com/${{ github.repository }}.git
REBAR_CT=${{ env.REBAR_CT }}
VARIANT=standalone
context: .
file: ${{ env.DOCKERFILE_PATH }}/Dockerfile
platforms: linux/${{ matrix.arch }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
-
name: Simple smoke test
run: |
set -x
docker run -d --name eturnal \
--platform linux/${{ matrix.ARCH }} \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-so.outputs.digest }} \
sleep 600
docker exec eturnal eturnalctl daemon
docker exec eturnal eturnalctl ping
docker exec eturnal eturnalctl info
docker logs eturnal
docker stop eturnal
-
name: Standalone | export digest | ${{ matrix.arch }}
run: |
mkdir -p /tmp/digests-so
digest="${{ steps.build-so.outputs.digest }}"
touch "/tmp/digests-so/${digest#sha256:}"
-
name: Standalone | upload digest | ${{ matrix.arch }}
uses: actions/upload-artifact@v4
with:
name: digests-standalone-${{ env.ARCH }}
path: /tmp/digests-so/*
if-no-files-found: error
retention-days: 5
-
name: ACME | build & push by digest
id: build-as
uses: docker/build-push-action@v5
with:
build-args: |
SOURCE=${{ env.SRC }}
VERSION=${{ env.VSN }}
BUILD_IMAGE=localhost:5000/erlang:otp-${{ env.OTP_VSN }}
REPOSITORY=https://github.com/${{ github.repository }}.git
REBAR_CT=${{ env.REBAR_CT }}
VARIANT=acme
context: .
file: ${{ env.DOCKERFILE_PATH }}/Dockerfile
platforms: linux/${{ matrix.arch }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
-
name: ACME | export digest | ${{ matrix.arch }}
run: |
mkdir -p /tmp/digests-as
digest="${{ steps.build-as.outputs.digest }}"
touch "/tmp/digests-as/${digest#sha256:}"
-
name: ACME | upload digest | ${{ matrix.arch }}
uses: actions/upload-artifact@v4
with:
name: digests-acme-${{ env.ARCH }}
path: /tmp/digests-as/*
if-no-files-found: error
retention-days: 5
################################################################################
## merge single images to only advertise one image plus tag
publish:
name: ${{ matrix.registry }} - publish manifest ${{ matrix.variant }} variant
runs-on: ubuntu-latest
strategy:
matrix:
registry: [ghcr.io, docker.io]
variant: [standalone, acme]
fail-fast: false
if: github.event_name != 'pull_request' && github.event_name != 'schedule'
needs: [detect-change, build-musl-binary-archives, build-ctr-binary-based, build-ctr]
steps:
-
name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: On push master | extract git version and define build variants
if: needs.detect-change.outputs.update == 'false'
run: |
echo "TAG_VERSION=$(echo "$(./tools/get-version)" | sed -e 's|+|-|')" >> $GITHUB_ENV
-
name: On release | extract release tags to be published/updated
if: needs.detect-change.outputs.update == 'true'
run: |
echo "TAG_PKGREL=$(awk 'END{print}' ${{ env.PKGREL_FILE }})" >> $GITHUB_ENV
echo "TAG_VERSION=$(awk 'END{gsub("-", " "); print $1}' ${{ env.PKGREL_FILE }})" >> $GITHUB_ENV
echo "TAG_MINOR=$(awk 'END{gsub("\\.", " "); print $1"."$2}' ${{ env.PKGREL_FILE }})" >> $GITHUB_ENV
echo "TAG_MAJOR=$(awk 'END{gsub("\\.", " "); print $1}' ${{ env.PKGREL_FILE }})" >> $GITHUB_ENV
-
name: Log in to ${{ matrix.registry }}
uses: docker/login-action@v3
if: |
( matrix.registry == 'docker.io'
&& github.repository_owner == 'processone' )
|| matrix.registry == 'ghcr.io'
with:
registry: ${{ matrix.registry }}
username: ${{ (matrix.registry == 'docker.io'
&& secrets.DOCKERHUB_USERNAME)
|| github.repository_owner }}
password: ${{ (matrix.registry == 'docker.io'
&& secrets.DOCKERHUB_TOKEN)
|| secrets.GITHUB_TOKEN }}
-
name: Download digests | ${{ matrix.variant }}
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-${{ matrix.variant }}-*
merge-multiple: true
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Define tag for latest ...
run: |
if [ ${{ matrix.variant }} = 'standalone' ]
then echo "TAG_LATEST=latest" >> $GITHUB_ENV
else echo "TAG_LATEST=${{ matrix.variant }}" >> $GITHUB_ENV
fi
-
name: For variant == 'acme' add a special tag suffix
if: matrix.variant == 'acme'
run: echo "TAG_ACME=-acme" >> $GITHUB_ENV
-
name: On push master | Docker meta
id: meta
if: needs.detect-change.outputs.update == 'false'
uses: docker/metadata-action@v5
with:
images: ${{ matrix.registry }}/${{ (matrix.registry == 'docker.io'
&& env.DOCKERHUB_REPO)
|| env.IMAGE_NAME }}
tags: |
edge${{ env.TAG_ACME }}
-
name: On release | Docker meta
id: meta-rel
if: needs.detect-change.outputs.update == 'true'
uses: docker/metadata-action@v5
with:
images: ${{ matrix.registry }}/${{ (matrix.registry == 'docker.io'
&& env.DOCKERHUB_REPO)
|| env.IMAGE_NAME }}
tags: |
${{ env.TAG_LATEST }}
${{ env.TAG_PKGREL }}${{ env.TAG_ACME }}
${{ env.TAG_VERSION }}${{ env.TAG_ACME }}
${{ env.TAG_MINOR }}${{ env.TAG_ACME }}
${{ env.TAG_MAJOR }}${{ env.TAG_ACME }}
-
name: Create manifest list and push
if: |
( matrix.registry == 'docker.io'
&& github.repository_owner == 'processone' )
|| matrix.registry == 'ghcr.io'
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -r '"-t " + (.tags | join(" -t "))' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
-
name: Inspect image
if: matrix.registry == 'ghcr.io'
run: |
docker buildx imagetools inspect ${{ matrix.registry }}/${{ env.IMAGE_NAME }}:${{ steps.meta-rel.outputs.version }} ||
docker buildx imagetools inspect ${{ matrix.registry }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}