Skip to content

docker image

docker image #22

Workflow file for this run

name: docker image
# Builds the Debian and Alpine images for amd64 + arm64 and publishes them as
# multi-arch tags on ghcr.io. Tag pushes publish; a manual run only builds and
# verifies unless "push" is ticked, so the Dockerfiles can be exercised without
# touching the registry.
#
# The published tag set follows the nginx official images -- version, minor and
# major series, a floating alias and a distro-suffixed variant of each, times
# two image variants (default and perl):
#
# 3.2.0 3.2 3 latest 3.2.0-trixie 3.2-trixie ... trixie
# 3.2.0-alpine ... 3.2.0-alpine3.24 ... alpine3.24
# 3.2.0-perl ... perl 3.2.0-trixie-perl ... trixie-perl
# 3.2.0-alpine-perl ... 3.2.0-alpine3.24-perl ... alpine3.24-perl
#
# .github/scripts/image-tags.sh owns that list; the rolling tags are withheld
# from pre-releases.
#
# The perl variant is NOT a second compile -- see the Dockerfile header. It is a
# --target on the same build, so it costs one extra runtime layer.
#
# NOTE: a GHCR package is private on first push. It has to be flipped to public
# once, by hand, under https://github.com/<owner>?tab=packages -> tengine ->
# Package settings -> Change visibility. Nothing in this workflow can do that.
on:
workflow_dispatch:
inputs:
push:
description: 'Push the images to ghcr.io'
type: boolean
default: false
push:
tags:
# Keep in sync with package.yml: 3.x tags are bare version numbers,
# 2.x used a "tengine-" prefix.
- '[0-9]+.[0-9]+.[0-9]+*'
- 'tengine-*'
permissions:
contents: read
packages: write
jobs:
# One place that decides the image name, the version and which tags move.
meta:
runs-on: ubuntu-24.04
outputs:
image: ${{ steps.calc.outputs.image }}
version: ${{ steps.calc.outputs.version }}
push: ${{ steps.calc.outputs.push }}
latest: ${{ steps.calc.outputs.latest }}
steps:
- uses: actions/checkout@v7
- name: compute image name and tags
id: calc
env:
DISPATCH_PUSH: ${{ inputs.push }}
run: |
set -eu
# GHCR rejects upper case in repository names, and the owner may well
# be spelled "Alibaba".
owner=$(printf '%s' "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "image=ghcr.io/$owner/tengine" >> "$GITHUB_OUTPUT"
if [ "${{ github.ref_type }}" = tag ]; then
# 3.x tags are bare (3.2.0-rc1 -> 3.2.0-rc1); 2.x carried a prefix
# (tengine-2.2.2 -> 2.2.2), which is stripped when present.
version=${GITHUB_REF_NAME#tengine-}
push=true
# A pre-release must never become what "docker pull tengine" gets.
case "$(printf '%s' "$version" | tr '[:upper:]' '[:lower:]')" in
*-rc*|*rc[0-9]*|*-beta*|*-alpha*|*-pre*) latest=false ;;
*) latest=true ;;
esac
else
# Manual run: a throwaway tag that can never collide with a release.
ver=$(sed -n 's/^#define TENGINE_VERSION *"\(.*\)"/\1/p' src/core/nginx.h)
version="$ver-dev-$(git rev-parse --short=7 HEAD)"
push=${DISPATCH_PUSH:-false}
latest=false
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "push=$push" >> "$GITHUB_OUTPUT"
echo "latest=$latest" >> "$GITHUB_OUTPUT"
printf 'image=ghcr.io/%s/tengine\nversion=%s\npush=%s\nlatest=%s\n' \
"$owner" "$version" "$push" "$latest"
build:
needs: meta
# arm64 builds on native runners: emulating a full Tongsuo + xquic + Tengine
# compile under QEMU would take 5-10x longer and risk the job timeout.
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
# Tongsuo is compiled twice (once for xquic, once by Tengine's configure),
# plus xquic, LuaJIT and Tengine itself.
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
flavor: [debian, alpine]
arch: [amd64, arm64]
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v4
- name: log in to ghcr.io
if: needs.meta.outputs.push == 'true'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Publishing path: push by digest only. The per-architecture images stay
# untagged and the manifest job assembles them into the real tags, so no
# arch-specific tag ever shows up in the registry.
#
# The perl variant is built in this same job rather than as its own matrix
# entry on purpose. It is a --target on the very same Dockerfile, so every
# builder layer is already in this runner's buildx state: the second build
# only has to run the handful of runtime instructions. A separate job
# would instead re-import the whole layer set from the GHA cache -- and
# two jobs writing the same cache scope would race.
- name: build and push by digest (default)
if: needs.meta.outputs.push == 'true'
id: push
uses: docker/build-push-action@v7
with:
context: .
file: ${{ matrix.flavor == 'alpine' && 'Dockerfile.alpine' || 'Dockerfile' }}
target: default
build-args: TENGINE_VERSION=${{ needs.meta.outputs.version }}
cache-from: type=gha,scope=${{ matrix.flavor }}-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.flavor }}-${{ matrix.arch }}
outputs: type=image,name=${{ needs.meta.outputs.image }},push-by-digest=true,name-canonical=true,push=true
- name: build and push by digest (perl)
if: needs.meta.outputs.push == 'true'
id: push_perl
uses: docker/build-push-action@v7
with:
context: .
file: ${{ matrix.flavor == 'alpine' && 'Dockerfile.alpine' || 'Dockerfile' }}
target: perl
build-args: TENGINE_VERSION=${{ needs.meta.outputs.version }}
cache-from: type=gha,scope=${{ matrix.flavor }}-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.flavor }}-${{ matrix.arch }}
outputs: type=image,name=${{ needs.meta.outputs.image }},push-by-digest=true,name-canonical=true,push=true
# One artifact per (flavour, arch) holding both variants' digests. The
# file name carries the variant AND the arch because the manifest job
# downloads with merge-multiple, which flattens every artifact into one
# directory -- same-named files would overwrite each other.
- name: record digests
if: needs.meta.outputs.push == 'true'
run: |
set -eu
mkdir -p /tmp/digests
echo "${{ steps.push.outputs.digest }}" > "/tmp/digests/default-${{ matrix.arch }}"
echo "${{ steps.push_perl.outputs.digest }}" > "/tmp/digests/perl-${{ matrix.arch }}"
- uses: actions/upload-artifact@v7
if: needs.meta.outputs.push == 'true'
with:
name: digest-${{ matrix.flavor }}-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
# Build-only path: load the images into the local daemon and exercise them
# right here, since there will be no published tag to verify later.
- name: build locally (default)
if: needs.meta.outputs.push != 'true'
uses: docker/build-push-action@v7
with:
context: .
file: ${{ matrix.flavor == 'alpine' && 'Dockerfile.alpine' || 'Dockerfile' }}
target: default
build-args: TENGINE_VERSION=${{ needs.meta.outputs.version }}
cache-from: type=gha,scope=${{ matrix.flavor }}-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.flavor }}-${{ matrix.arch }}
load: true
tags: tengine-local:test
- name: build locally (perl)
if: needs.meta.outputs.push != 'true'
uses: docker/build-push-action@v7
with:
context: .
file: ${{ matrix.flavor == 'alpine' && 'Dockerfile.alpine' || 'Dockerfile' }}
target: perl
build-args: TENGINE_VERSION=${{ needs.meta.outputs.version }}
cache-from: type=gha,scope=${{ matrix.flavor }}-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.flavor }}-${{ matrix.arch }}
load: true
tags: tengine-local:test-perl
- name: verify the local images
if: needs.meta.outputs.push != 'true'
run: |
./.github/scripts/verify-image.sh tengine-local:test
./.github/scripts/verify-image.sh tengine-local:test-perl --variant perl
# buildx assembles the per-architecture digests into one multi-arch tag.
manifest:
needs: [meta, build]
if: needs.meta.outputs.push == 'true'
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
flavor: [debian, alpine]
variant: [default, perl]
steps:
# image-tags.sh reads the base images back out of the Dockerfiles.
- uses: actions/checkout@v7
# Pulls in both variants' digests for this flavour; the tag step picks
# out the ones belonging to matrix.variant by file-name prefix.
- uses: actions/download-artifact@v8
with:
pattern: digest-${{ matrix.flavor }}-*
path: digests
merge-multiple: true
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: create the multi-arch tags
env:
IMAGE: ${{ needs.meta.outputs.image }}
VERSION: ${{ needs.meta.outputs.version }}
FLAVOR: ${{ matrix.flavor }}
VARIANT: ${{ matrix.variant }}
MOVE_LATEST: ${{ needs.meta.outputs.latest }}
run: |
set -eu
# Debian is the default flavour and owns the bare tags; Alpine gets
# the -alpine suffix, matching how the nginx images are published.
# The script also derives the minor/major series tags and the
# distro-suffixed ones (-trixie, -alpine3.24) from the Dockerfiles.
# "default" is spelled as no variant at all, so its tags stay bare.
if [ "$VARIANT" = default ]; then
tags=$(./.github/scripts/image-tags.sh "$FLAVOR" "$VERSION" "$MOVE_LATEST")
else
tags=$(./.github/scripts/image-tags.sh "$FLAVOR" "$VERSION" "$MOVE_LATEST" "$VARIANT")
fi
set --
for t in $tags; do
set -- "$@" --tag "$IMAGE:$t"
done
# Only this variant's digests -- the artifact holds both.
found=0
for f in digests/"$VARIANT"-*; do
[ -e "$f" ] || continue
set -- "$@" "$IMAGE@$(cat "$f")"
found=$((found + 1))
done
[ "$found" -gt 0 ] || { echo "no digests for variant $VARIANT" >&2; exit 1; }
echo "docker buildx imagetools create $*"
docker buildx imagetools create "$@"
for t in $tags; do
docker buildx imagetools inspect "$IMAGE:$t"
done
# Pull the published tag back and make sure the promised feature set is
# actually in there.
verify:
needs: [meta, manifest]
if: needs.meta.outputs.push == 'true'
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
strategy:
fail-fast: false
matrix:
flavor: [debian, alpine]
arch: [amd64, arm64]
variant: [default, perl]
steps:
- uses: actions/checkout@v7
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: pull and verify
env:
IMAGE: ${{ needs.meta.outputs.image }}
VERSION: ${{ needs.meta.outputs.version }}
VARIANT: ${{ matrix.variant }}
run: |
set -eu
# Reassemble the version-pinned tag this variant was published under:
# 3.2.0, 3.2.0-alpine, 3.2.0-perl, 3.2.0-alpine-perl.
ref="$IMAGE:$VERSION"
[ "${{ matrix.flavor }}" = alpine ] && ref="$ref-alpine"
[ "$VARIANT" = default ] || ref="$ref-$VARIANT"
docker pull "$ref"
if [ "$VARIANT" = default ]; then
./.github/scripts/verify-image.sh "$ref"
else
./.github/scripts/verify-image.sh "$ref" --variant "$VARIANT"
fi