Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 62 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ jobs:
outputs:
variants: ${{ steps.matrix.outputs.variants }}
platforms: ${{ steps.matrix.outputs.platforms }}
build_matrix: ${{ steps.matrix.outputs.build_matrix }}
build_variants: ${{ steps.matrix.outputs.build_variants }}
targets: ${{ steps.matrix.outputs.targets }}
flavors: ${{ steps.matrix.outputs.flavors }}
metadata: ${{ steps.matrix.outputs.metadata }}
Expand Down Expand Up @@ -154,12 +156,67 @@ jobs:
VARIANTS="$(jq -c --arg re "$SUFFIX_REGEX" '.group.default.targets|map(sub($re; ""))|unique' <<< "${METADATA}")"
FLAVORS="$(jq -c 'map(split("-")[-2])|unique' <<< "${VARIANTS}")"
PLATFORMS="$(jq -c 'first(.target[]) | .platforms' <<< "${METADATA}")"

# In case an arch is missing for a base image, filter it out and warn, but continue.
BUILD_MATRIX='[]'
while read -r variant; do
TARGET_DEF="$(jq -c --arg v "$variant" 'first(.target | to_entries[] | select(.key | startswith($v + "-")) | .value)' <<< "${METADATA}")"
WANTED="$(jq -c '.platforms' <<< "${TARGET_DEF}")"
BASE_IMAGE="$(jq -r '.contexts["php-base"] | sub("^docker-image://"; "")' <<< "${TARGET_DEF}")"
case "${BASE_IMAGE}" in
*/*) BASE_REF="${BASE_IMAGE}" ;;
*) BASE_REF="docker.io/library/${BASE_IMAGE}" ;;
esac

AVAILABLE='[]'
for attempt in 1 2 3; do
if RAW_MANIFEST="$(skopeo inspect --raw "docker://${BASE_REF}")"; then
# Collect "os/arch" and "os/arch/variant" so bake's `linux/arm64`
# matches the upstream `linux/arm64/v8` manifest entry.
AVAILABLE="$(jq -c '
[ (.manifests // [])[]
| .platform // empty
| select(.architecture != "unknown")
| (.os + "/" + .architecture) as $p
| if (.variant // "") != "" then ($p + "/" + .variant), $p else $p end
] | unique' <<< "${RAW_MANIFEST}")"
break
fi
echo "Attempt ${attempt}/3 to inspect '${BASE_REF}' failed."
sleep "$((attempt * 5))"
done

if [ "$(jq 'length' <<< "${AVAILABLE}")" -eq 0 ]; then
echo "::warning::Could not resolve platforms for '${BASE_REF}'; keeping all platforms for '${variant}'."
AVAILABLE="${WANTED}"
fi

SUPPORTED="$(jq -c --argjson avail "${AVAILABLE}" '[.[] | select(. as $x | $avail | index($x))]' <<< "${WANTED}")"
SKIPPED="$(jq -r --argjson avail "${AVAILABLE}" '[.[] | select(. as $x | $avail | index($x) | not)] | join(", ")' <<< "${WANTED}")"
if [ -n "${SKIPPED}" ]; then
echo "::warning::Upstream image '${BASE_REF}' has no manifest for: ${SKIPPED}. Skipping those platforms for '${variant}'."
fi
if [ "$(jq 'length' <<< "${SUPPORTED}")" -eq 0 ]; then
echo "::warning::Upstream image '${BASE_REF}' provides none of the requested platforms; skipping variant '${variant}' entirely."
continue
fi
echo "Platforms for '${variant}': $(jq -r 'join(", ")' <<< "${SUPPORTED}")"

BUILD_MATRIX="$(jq -c --arg v "$variant" --argjson p "${SUPPORTED}" '. + [$p[] | {variant: $v, platform: .}]' <<< "${BUILD_MATRIX}")"
done < <(jq -r '.[]' <<< "${VARIANTS}")

# `merge` must only fan out over variants that actually produced digests,
# otherwise it would look for artifacts that were never uploaded.
BUILD_VARIANTS="$(jq -c '[.[].variant] | unique' <<< "${BUILD_MATRIX}")"

{
echo metadata="$METADATA"
echo targets="$TARGETS"
echo flavors="$FLAVORS"
echo variants="$VARIANTS"
echo platforms="$PLATFORMS"
echo build_matrix="$BUILD_MATRIX"
echo build_variants="$BUILD_VARIANTS"
} >> "${GITHUB_OUTPUT}"
env:
SHA: ${{ github.sha }}
Expand All @@ -170,15 +227,15 @@ jobs:
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
needs:
- prepare
if: ${{ needs.prepare.outputs.php_versions != '' }}
if: ${{ needs.prepare.outputs.php_versions != '' && needs.prepare.outputs.build_matrix != '[]' }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
# Variants whose upstream `php-base` image lacks a platform are excluded by the `prepare` job.
include: ${{ fromJson(needs.prepare.outputs.build_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -270,7 +327,7 @@ jobs:

merge:
runs-on: ubuntu-24.04
if: github.event_name != 'pull_request'
if: ${{ github.event_name != 'pull_request' && needs.prepare.outputs.build_variants != '[]' }}
needs:
- prepare
- build
Expand All @@ -280,7 +337,7 @@ jobs:
strategy:
fail-fast: false
matrix:
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
variant: ${{ fromJson(needs.prepare.outputs.build_variants) }}
target: ${{ fromJson(needs.prepare.outputs.targets) }}
steps:
- name: Download digests
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
- Bake target naming convention is `php-<ver-with-dashes>-<flavor>-<os>-<target>` and `<target>` may itself contain `-` (for example `otel-ffmpeg`). Always strip the target suffix using the metadata-derived list with a longest-first regex; never use positional `split("-")` indexes on the full target name.
- Per-platform digest artifacts use a `--` (double-hyphen) delimiter: `digest--<variant>--<platform>`. The merge job's `actions/download-artifact` `pattern` (`digest--<variant>--*`) relies on this so variant boundaries are unambiguous. Do not introduce a variant or platform name containing `--`.
- When adding a new target: update the `Dockerfile` stage and `docker-bake.hcl` `TARGETS`. The `release.yml` workflow picks it up automatically. Then update `security.yml` scan matrix and docs (`docs/usage.md`, `docs/images.md`).
- `docker-bake.hcl` declares the *desired* platforms (`linux/amd64`, `linux/386`, `linux/arm/v7`, `linux/arm64`), but upstream `php:<version>-<flavor>-<os>` images do not always publish all of them: an arch disappears from the manifest list when its Debian base image is missing for that build (see [docker-library/php#1683](https://github.com/docker-library/php/issues/1683) — e.g. `php:8.5.10-zts-trixie` has no `linux/386` while `php:8.5.10-cli-trixie` does). The `prepare` job therefore inspects each variant's `php-base` manifest with `skopeo inspect --raw`, intersects it with the bake platforms and emits `build_matrix` (an explicit `{variant, platform}` include-matrix). Missing platforms are skipped with a `::warning::` instead of failing the run with `no match for platform in manifest`. Keep the `build` job on `matrix.include` — do NOT go back to a full `variant x platform` cross product.
- `merge` stays a `variant x target` matrix (a manifest list is platform-agnostic and is assembled from whatever digests `build` uploaded), but its variant axis comes from `build_variants` (unique variants of `build_matrix`), not `variants`. A variant with zero available platforms is skipped everywhere instead of producing a merge job that fails on missing digest artifacts.

## Trivy CRITICALs on published images (stale base packages)
- Symptom: `task trivy:remote` (or `security.yml`) reports `fixed` CRITICALs (e.g. `ffmpeg`/`libav*`, `libgnutls30t64`) on some PHP lines (commonly older ones like 8.2/8.3) but not others (8.4/8.5).
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ ghcr.io/toshy/php:<version>-<flavor>-<os>(-<target>)
- Contains the following PHP versions: `8.2`, `8.3`, `8.4`, `8.5`.
- Contains the following flavors: `cli`, `fpm`, `apache`, `zts`.
- Contains the following OS: `trixie` (deprecated: `bookworm`).
- Contains the following [targets](images.md#targets): `base`, `ffmpeg`, `otel`, `otel-ffmpeg`, `otel-grpc,`, `otel-grpc-ffmpeg`.
- Contains the following [targets](images.md#targets): `base`, `ffmpeg`, `otel`, `otel-ffmpeg`, `otel-grpc`, `otel-grpc-ffmpeg`.

!!!question

Expand Down