docker image #22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |