Skip to content

Build Docker images #65

Build Docker images

Build Docker images #65

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:
LATEST_VERSION: ${{ vars.LATEST_PHP_VERSION || '8.4' }}
# PHP versions to build - keep in sync with build matrix
PHP_VERSIONS: '8.4 8.5'
# PIE version used in Dockerfiles - keep in sync with ARG PIE_VERSION
PIE_VERSION: '1.3.1'
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@v6
- name: Cache digests
id: cache-digests
uses: actions/cache@v5
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
# Expand PIE_VERSION variable (constant across all Dockerfiles)
image="${image//\$\{PIE_VERSION\}/${PIE_VERSION}}"
image="${image//\$PIE_VERSION/${PIE_VERSION}}"
# Expand PHP_VERSION variable for each version in the matrix
if [[ "${image}" == *'${PHP_VERSION}'* || "${image}" == *'$PHP_VERSION'* ]]; then
for php_version in ${PHP_VERSIONS}; do
expanded="${image//\$\{PHP_VERSION\}/${php_version}}"
expanded="${expanded//\$PHP_VERSION/${php_version}}"
base_images+=("${expanded}")
done
else
base_images+=("${image}")
fi
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@v5
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
php-version:
- '8.4'
- '8.5'
steps:
- name: Checkout repository
uses: actions/checkout@v6
- 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 Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Determine Image Name
id: config
# language=bash
run: |
flavor=$( \
echo "${{ matrix.dockerfile }}" | \
sed -E 's/.?Dockerfile//' | \
tr '/' '-' \
)
latest=${{ (matrix.stage == 'prod' && matrix.php-version == env.LATEST_VERSION && matrix.dockerfile == 'Dockerfile' ) && 'true' || 'false' }}
suffix="${{ matrix.stage == 'dev' && '-dev' || '' }}"
echo "latest=${latest}" >> $GITHUB_OUTPUT
echo "version=${{matrix.php-version}}${flavor:+-${flavor}}${suffix}" >> $GITHUB_OUTPUT
echo "image-name=${{ github.repository_owner }}/${{ vars.image-name || 'php' }}" >> $GITHUB_OUTPUT
- name: Set up Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ steps.config.outputs.image-name }}
labels: |
org.opencontainers.image.title=${{ steps.config.outputs.image-name }}
org.opencontainers.image.version=${{ steps.config.outputs.version }}
org.opencontainers.vendor="Matchory GmbH"
flavor: |
latest=${{ steps.config.outputs.latest }}
tags: |
type=sha,prefix=${{ steps.config.outputs.version }}-
type=raw,value=${{ steps.config.outputs.version }}
- name: Build and Push
id: build
uses: docker/build-push-action@v6
with:
context: .
file: ./${{ matrix.dockerfile }}
target: ${{ matrix.stage }}
build-args: |
PHP_VERSION=${{ matrix.php-version }}
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
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/${{ steps.config.outputs.image-name }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true