Skip to content

Build Docker images #37

Build Docker images

Build Docker images #37

Workflow file for this run

name: Build Docker images
concurrency:
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
on:
push:
branches:
- main
schedule:
# Runs every Monday at 03:00 UTC
- cron: '0 3 * * 1'
env:
REGISTRY: ghcr.io
permissions:
contents: read
packages: write
id-token: write
attestations: write
jobs:
check-updates:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.check.outputs.changed }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Cache digests
id: cache-digests
uses: actions/cache@v4
with:
path: latest-digests.txt
key: digest-cache-${{ runner.os }}-${{ hashFiles('*Dockerfile') }}
restore-keys: |
digest-cache-${{ runner.os }}-
- name: Extract Base Images from Dockerfiles
id: extract
# language=bash
run: |
shopt -s globstar nullglob
base_images=()
for file in ./*Dockerfile; do
while IFS= read -r image; do
# only include real image names, ignoring internal stages
if [[ "${image}" == *"/"* || "${image}" == *":"* ]]; then
base_images+=("${image}")
fi
done < <( \
grep -e '^FROM ' "${file}" | \
awk '{print $2}' | \
sort -u \
)
done
# Deduplicate and format for github actions
IFS=" " read -r -a unique_images <<< "$( \
echo "${base_images[@]}" | \
tr ' ' '\n' | \
sort -u | \
tr '\n' ' ' \
)"
echo "base images detected: ${unique_images[*]}"
# Save images as space-separated list for later use
echo "base_images=${unique_images[*]}" >> "${GITHUB_ENV}"
- name: Check upstream image digests
id: check
# language=bash
run: |
# Check if the base images have changed
# If not a scheduled run, always consider the images changed
if [[ "${GITHUB_EVENT_NAME}" != "schedule" ]]; then
echo changed=true >> "${GITHUB_OUTPUT}"
exit 0
fi
digests=()
for image in $(echo "${base_images}" | tr ' ' '\n'); do
# Fetch image manifest without pulling the full image, so we can extract the digests
manifest=$(docker manifest inspect "${image}")
if [[ -z "${manifest}" ]]; then
echo "Failed to fetch manifest for ${image}"
exit 1
fi
# Extract digests for linux/amd64 and linux/arm64
amd64_digest=$( \
echo "${manifest}" | \
jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest' | \
head -n1 \
)
arm64_digest=$( \
echo "${manifest}" | \
jq -r '.manifests[] | select(.platform.architecture=="arm64" and .platform.os=="linux") | .digest' | \
head -n1 \
)
if [[ -z "${amd64_digest}" || -z "${arm64_digest}" ]]; then
echo "Warning: Missing digest for one of the architectures in ${image}"
exit 1
fi
digests+=("${image} (linux/amd64): ${amd64_digest}")
digests+=("${image} (linux/arm64): ${arm64_digest}")
done
echo "Latest digests:"
echo "${digests[*]}"
echo "${digests}" > latest-digests.txt
if [[ -f cached-digests.txt && "$(diff -q latest-digests.txt cached-digests.txt)" == "" ]]; then
echo "No changes in base images."
echo changed=false >> "${GITHUB_OUTPUT}"
else
echo "Base images changed."
echo changed=true >> "${GITHUB_OUTPUT}"
# Save for next run
cp latest-digests.txt cached-digests.txt
fi
- name: Save digests cache
if: ${{ fromJSON(steps.check.outputs.changed) }}
uses: actions/cache@v4
with:
path: latest-digests.txt
key: digest-cache-${{ runner.os }}-${{ hashFiles('*Dockerfile') }}
restore-keys: |
digest-cache-${{ runner.os }}-
build:
name: Build Docker images
runs-on: ubuntu-latest
needs: check-updates
if: ${{ fromJSON(needs.check-updates.outputs.changed) }}
strategy:
fail-fast: false
matrix:
stage:
- dev
- prod
dockerfile:
- Dockerfile
- alpine.Dockerfile
- frankenphp.Dockerfile
- frankenphp-alpine.Dockerfile
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU (for ARM builds)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine Image Name
id: image_name
# language=bash
run: |
echo "IMAGE_NAME=${{ github.repository_owner }}/${{ vars.IMAGE_NAME || 'php' }}" >> $GITHUB_ENV
base_version=$( \
echo "${{ matrix.dockerfile }}" | \
sed -E 's/.?Dockerfile//' | \
tr '/' '-' \
)
suffix="${{ matrix.stage == 'dev' && 'dev' || '' }}"
echo "VERSION=${base_version:-latest}${suffix:+-}${suffix}" >> $GITHUB_ENV
- name: Set up Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_NAME }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
labels: |
org.opencontainers.image.title=${{ env.IMAGE_NAME }}
org.opencontainers.image.version=${{ env.VERSION }}
org.opencontainers.vendor="Matchory GmbH"
tags: |
type=sha
type=sha,prefix=${{ env.VERSION }}-
type=raw,value=${{ env.VERSION }}
- name: Build and Push
id: build
uses: docker/build-push-action@v6
with:
context: .
file: ./${{ matrix.dockerfile }}
target: ${{ matrix.stage }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: mode=max
sbom: true
- name: Generate artifact attestation for Docker Hub
uses: actions/attest-build-provenance@v2
with:
subject-name: index.docker.io/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true