Skip to content

ci: adds dedicated release token #13

ci: adds dedicated release token

ci: adds dedicated release token #13

Workflow file for this run

# Builds the multi-arch image and publishes to GHCR (+ DockerHub if configured).
#
# Triggered by git tag creation, which publishes to the same docker tag.
# Each platform is built on its own runner in parallel and pushed by digest;
# a final merge job stitches the digests into one manifest list per registry,
# then attaches provenance + SBOM attestations.
#
# Platforms: amd64 (native), arm64 (native arm runner), and arm/v7, 386,
# ppc64le, s390x (glibc, cross-compiled on amd64). No emulation is used, so
# QEMU is not needed.
name: 🐳 Docker
on:
workflow_dispatch:
inputs:
tag:
description: 'Docker tag to publish. Leave empty to update latest'
required: false
type: string
push:
branches:
- main
tags:
- '*'
concurrency:
group: docker-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
IMAGE_NAME: adguardian
DOCKER_USER: lissy93
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
jobs:
# Resolve the tag + target registries once, shared by every build leg + merge
prepare:
name: 🧭 Prepare
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
tag: ${{ steps.meta.outputs.tag }}
ghcr_image: ${{ steps.meta.outputs.ghcr_image }}
publish_images: ${{ steps.meta.outputs.publish_images }}
dockerhub_enabled: ${{ steps.meta.outputs.dockerhub_enabled }}
steps:
- name: Compute image metadata
id: meta
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
MANUAL_TAG: ${{ inputs.tag }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
if [[ -n "$MANUAL_TAG" ]]; then TAG="$MANUAL_TAG"; else TAG="latest"; fi
elif [[ "$GITHUB_REF" == refs/tags/* ]]; then
TAG="${GITHUB_REF#refs/tags/}"
else
TAG="latest"
fi
TAG="$(echo "$TAG" | sed -E 's/[^A-Za-z0-9_.-]+/-/g; s/^[.-]+//' | cut -c1-128)"
if [[ -z "$TAG" ]]; then
echo "::error::Computed Docker tag is empty"
exit 1
fi
GHCR_IMAGE="${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}"
PUBLISH_IMAGES="$GHCR_IMAGE"
DOCKERHUB_ENABLED="false"
if [[ -n "$DOCKERHUB_PASSWORD" ]]; then
PUBLISH_IMAGES="${PUBLISH_IMAGES},${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}"
DOCKERHUB_ENABLED="true"
fi
{
echo "tag=$TAG"
echo "ghcr_image=$GHCR_IMAGE"
echo "publish_images=$PUBLISH_IMAGES"
echo "dockerhub_enabled=$DOCKERHUB_ENABLED"
} >> "$GITHUB_OUTPUT"
# Build each platform on its own runner, in parallel, pushing by digest
build:
name: πŸ—οΈ Build ${{ matrix.platform }}
needs: prepare
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
# 64-bit: ultra-light scratch/musl image (Dockerfile), built natively
- platform: linux/amd64
runner: ubuntu-latest
dockerfile: ./Dockerfile
- platform: linux/arm64
runner: ubuntu-24.04-arm
dockerfile: ./Dockerfile
# glibc/Debian image (Dockerfile.full), cross-compiled on amd64
- platform: linux/arm/v7
runner: ubuntu-latest
dockerfile: ./Dockerfile.full
- platform: linux/386
runner: ubuntu-latest
dockerfile: ./Dockerfile.full
- platform: linux/ppc64le
runner: ubuntu-latest
dockerfile: ./Dockerfile.full
- platform: linux/s390x
runner: ubuntu-latest
dockerfile: ./Dockerfile.full
steps:
- name: πŸ›ŽοΈ Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: 🏷️ Prepare platform name
env:
PLATFORM: ${{ matrix.platform }}
run: echo "PLATFORM_PAIR=${PLATFORM//\//-}" >> "$GITHUB_ENV"
- name: 🧱 Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: πŸ”‘ Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: πŸ”‘ Login to DockerHub
if: needs.prepare.outputs.dockerhub_enabled == 'true'
uses: docker/login-action@v4
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ env.DOCKER_USER }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: πŸ—οΈ Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ${{ matrix.dockerfile }}
platforms: ${{ matrix.platform }}
provenance: false
sbom: false
cache-from: type=gha,scope=${{ env.PLATFORM_PAIR }}
cache-to: type=gha,mode=max,scope=${{ env.PLATFORM_PAIR }}
outputs: type=image,"name=${{ needs.prepare.outputs.publish_images }}",push-by-digest=true,name-canonical=true,push=true
- name: πŸ“€ Export digest
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
set -euo pipefail
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"
- name: ⬆️ Upload digest
uses: actions/upload-artifact@v7
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Combine the per-platform digests into one multi-arch manifest per registry
merge:
name: 🧩 Merge manifest
needs: [prepare, build]
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: πŸ“₯ Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: 🧱 Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: πŸ”‘ Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: πŸ”‘ Login to DockerHub
if: needs.prepare.outputs.dockerhub_enabled == 'true'
uses: docker/login-action@v4
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ env.DOCKER_USER }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: 🧩 Create combined manifest list
shell: bash
env:
TAG: ${{ needs.prepare.outputs.tag }}
PUBLISH_IMAGES: ${{ needs.prepare.outputs.publish_images }}
run: |
set -euo pipefail
cd /tmp/digests
shopt -s nullglob
DIGEST_FILES=(*)
if [ ${#DIGEST_FILES[@]} -eq 0 ]; then
echo "::error::No digests found to merge"
exit 1
fi
IFS=',' read -ra IMAGES <<< "$PUBLISH_IMAGES"
for IMG in "${IMAGES[@]}"; do
REFS=()
for f in "${DIGEST_FILES[@]}"; do
REFS+=("${IMG}@sha256:${f}")
done
docker buildx imagetools create -t "${IMG}:${TAG}" "${REFS[@]}"
done
- name: πŸ”Ž Inspect and capture GHCR digest
id: digest
shell: bash
env:
GHCR_IMAGE: ${{ needs.prepare.outputs.ghcr_image }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
GHCR_DIGEST=$(docker buildx imagetools inspect "${GHCR_IMAGE}:${TAG}" --format '{{.Manifest.Digest}}')
echo "ghcr_digest=${GHCR_DIGEST}" >> "$GITHUB_OUTPUT"
docker buildx imagetools inspect "${GHCR_IMAGE}:${TAG}"
- name: πŸͺͺ Attest build provenance
uses: actions/attest-build-provenance@v4
continue-on-error: true
with:
subject-name: ${{ needs.prepare.outputs.ghcr_image }}
subject-digest: ${{ steps.digest.outputs.ghcr_digest }}
push-to-registry: true
- name: πŸ“‹ Generate SBOM
uses: anchore/sbom-action@v0.24.0
continue-on-error: true
with:
image: ${{ needs.prepare.outputs.ghcr_image }}@${{ steps.digest.outputs.ghcr_digest }}
format: spdx-json
output-file: sbom.spdx.json
- name: πŸͺͺ Attest SBOM
uses: actions/attest@v4
continue-on-error: true
with:
subject-name: ${{ needs.prepare.outputs.ghcr_image }}
subject-digest: ${{ steps.digest.outputs.ghcr_digest }}
predicate-type: https://spdx.dev/Document
predicate-path: sbom.spdx.json
push-to-registry: true