From 60f2b441c0293f996fe2936b697b1a7211c91290 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 22 Oct 2026 02:19:12 +0000 Subject: [PATCH 1/8] Release from a tag push and publish floating tags only for the newest release --- .../workflows/action_publish-images-beta.yml | 2 + .../action_publish-images-production.yml | 109 +++++++++++++++--- .github/workflows/service_build-images.yml | 25 ++-- AGENTS.md | 3 +- .../docs/1.getting-started/7.upgrade-guide.md | 2 +- scripts/assemble-docker-tags.sh | 63 ++++++---- scripts/tests/run.sh | 49 ++++++++ 7 files changed, 198 insertions(+), 55 deletions(-) diff --git a/.github/workflows/action_publish-images-beta.yml b/.github/workflows/action_publish-images-beta.yml index 79fb16ff..5f0d158f 100644 --- a/.github/workflows/action_publish-images-beta.yml +++ b/.github/workflows/action_publish-images-beta.yml @@ -73,6 +73,8 @@ jobs: registry-repositories: "docker.io/serversideup/php,ghcr.io/serversideup/php" tag-prefix: "beta" release-type: "testing" + # Only the prerelease that fired this gets release tags. Rebuilds refresh the beta floating tags. + release-tag: ${{ github.event_name == 'release' && needs.release.outputs.tag || '' }} secrets: inherit test: diff --git a/.github/workflows/action_publish-images-production.yml b/.github/workflows/action_publish-images-production.yml index e411c535..53607d45 100644 --- a/.github/workflows/action_publish-images-production.yml +++ b/.github/workflows/action_publish-images-production.yml @@ -1,19 +1,23 @@ name: Docker Publish (Production Images) run-name: >- Production images (${{ - github.event.release.tag_name || + (github.ref_type == 'tag' && github.ref_name) || (github.event_name == 'schedule' && 'weekly rebuild') || 'manual rebuild' }}) +# Releasing: write the notes as a draft release (optional), then push the version tag from the +# branch to release. The images are built, tested, and published before the release is, because +# publishing a release locks its tag. If the build fails, delete the tag, fix it, and push it again. on: workflow_dispatch: - release: - types: [released] + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' schedule: - cron: '0 8 * * 2' -# A release event and the weekly rebuild must not publish the same tags at the same time. +# A release and the weekly rebuild must not publish the same tags at the same time. concurrency: group: production-images @@ -27,25 +31,72 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: - contents: read + contents: write outputs: - tag: ${{ steps.release.outputs.tag }} + ref: ${{ steps.release.outputs.ref }} + release-tag: ${{ steps.release.outputs.release-tag }} + floating-tags: ${{ steps.release.outputs.floating-tags }} + publish-release: ${{ steps.release.outputs.publish-release }} steps: - # A release event builds the release that fired it. Scheduled and manual runs rebuild the latest release. - name: Find the release to build id: release env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} - RELEASE_TAG: ${{ github.event.release.tag_name }} + PUSHED_TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }} run: | - TAG="${RELEASE_TAG:-$(gh release view --json tagName --jq .tagName)}" - if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then - echo "::error title=No release found::Could not find a published release to build." - exit 1 + # GitHub's "Latest" label can't decide this: a pushed release isn't published yet. + published_releases() { + gh release list --exclude-drafts --exclude-pre-releases --limit 100 --json tagName --jq '.[].tagName' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' + } + + if [ -z "$PUSHED_TAG" ]; then + TAG=$(published_releases | sort -V | tail -n1) + if [ -z "$TAG" ]; then + echo "::error title=No release found::Could not find a published release to build." + exit 1 + fi + echo "Rebuilding release $TAG" + { + echo "ref=$TAG" + echo "release-tag=" + echo "floating-tags=true" + echo "publish-release=false" + } >> "$GITHUB_OUTPUT" + exit 0 + fi + + SORTED=$( (published_releases; echo "$PUSHED_TAG") | sort -uV) + NEWEST=$(echo "$SORTED" | tail -n1) + PREVIOUS=$(echo "$SORTED" | grep -B1 -xF "$PUSHED_TAG" | grep -vxF "$PUSHED_TAG" || true) + + # Publishing a release in the GitHub UI also pushes its tag. Its images still get built. + if published_releases | grep -xF "$PUSHED_TAG" > /dev/null; then + echo "::warning title=Release already published::$PUSHED_TAG was published before its images were built, so its tag is locked even if this build fails. Next time, push the tag instead." + PUBLISH_RELEASE=false + elif gh release view "$PUSHED_TAG" > /dev/null 2>&1; then + echo "Using the draft release notes for $PUSHED_TAG" + PUBLISH_RELEASE=true + else + gh release create "$PUSHED_TAG" --draft --verify-tag --title "$PUSHED_TAG" --generate-notes ${PREVIOUS:+--notes-start-tag "$PREVIOUS"} + echo "Created a draft release for $PUSHED_TAG with generated notes" + PUBLISH_RELEASE=true + fi + + if [ "$NEWEST" = "$PUSHED_TAG" ]; then + echo "Releasing $PUSHED_TAG as the newest release" + FLOATING=true + else + echo "Releasing $PUSHED_TAG with release tags only, because $NEWEST is newer" + FLOATING=false fi - echo "Building release $TAG" - echo "tag=$TAG" >> "$GITHUB_OUTPUT" + { + echo "ref=$PUSHED_TAG" + echo "release-tag=$PUSHED_TAG" + echo "floating-tags=$FLOATING" + echo "publish-release=$PUBLISH_RELEASE" + } >> "$GITHUB_OUTPUT" setup: needs: release @@ -53,7 +104,7 @@ jobs: permissions: contents: read with: - ref: ${{ needs.release.outputs.tag }} + ref: ${{ needs.release.outputs.ref }} build: needs: [release, setup] @@ -69,10 +120,12 @@ jobs: with: variation: ${{ matrix.variation }} matrix: ${{ needs.setup.outputs.matrix }} - ref: ${{ needs.release.outputs.tag }} + ref: ${{ needs.release.outputs.ref }} registry-repositories: "docker.io/serversideup/php,ghcr.io/serversideup/php" tag-prefix: '' release-type: "latest" + release-tag: ${{ needs.release.outputs.release-tag }} + floating-tags: ${{ needs.release.outputs.floating-tags == 'true' }} secrets: inherit test: @@ -83,7 +136,7 @@ jobs: contents: read id-token: write with: - ref: ${{ needs.release.outputs.tag }} + ref: ${{ needs.release.outputs.ref }} # Only images that passed the image tests reach a public registry. publish: @@ -95,9 +148,27 @@ jobs: packages: write id-token: write with: - ref: ${{ needs.release.outputs.tag }} + ref: ${{ needs.release.outputs.ref }} secrets: inherit + # Publishing the release locks its tag, so it waits until every image is live. + finalize: + needs: [release, publish] + if: needs.release.outputs.publish-release == 'true' + name: publish release + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: write + steps: + - name: Publish the draft release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + VERSION: ${{ needs.release.outputs.release-tag }} + LATEST: ${{ needs.release.outputs.floating-tags }} + run: gh release edit "$VERSION" --draft=false --latest="$LATEST" + report: needs: [release, setup, build, test, publish] if: always() && needs.setup.result == 'success' @@ -106,4 +177,4 @@ jobs: contents: read with: matrix: ${{ needs.setup.outputs.matrix }} - ref: ${{ needs.release.outputs.tag }} + ref: ${{ needs.release.outputs.ref }} diff --git a/.github/workflows/service_build-images.yml b/.github/workflows/service_build-images.yml index fa382312..eb6612df 100644 --- a/.github/workflows/service_build-images.yml +++ b/.github/workflows/service_build-images.yml @@ -41,6 +41,14 @@ on: type: string default: ${{ github.ref }} description: 'The git ref to checkout (branch, tag, or commit SHA)' + release-tag: + type: string + default: '' + description: 'The release being published (example: v5.1.0). Adds release tags like 8.4-fpm-nginx-v5.1.0. Empty for rebuilds and pull requests.' + floating-tags: + type: boolean + default: true + description: 'Publish floating tags like 8.4-fpm-nginx. False for a release older than the newest one, so a backport never moves them.' jobs: images: @@ -86,20 +94,19 @@ jobs: DOCKER_TAG_PREFIX: ${{ inputs.tag-prefix }} DOCKER_REGISTRY_REPOSITORIES: ${{ inputs.registry-repositories }} RELEASE_TYPE: ${{ inputs.release-type }} - GITHUB_RELEASE_TAG: ${{ github.ref_name }} - GITHUB_REF_TYPE: ${{ github.ref_type }} + GITHUB_RELEASE_TAG: ${{ inputs.release-tag }} + PUBLISH_FLOATING_TAGS: ${{ inputs.floating-tags }} - name: Set REPOSITORY_BUILD_VERSION id: set_version + env: + RELEASE_TAG: ${{ inputs.release-tag }} + REF: ${{ inputs.ref }} run: | - if [[ "${{ github.ref_type }}" == "tag" || "${{ inputs.ref }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - # Building from a tag (release or scheduled task) + if [[ -n "$RELEASE_TAG" || "$REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + # Building a release, or rebuilding one on a schedule echo "๐Ÿš€ Setting REPOSITORY_BUILD_VERSION to Tag + Run ID" - if [[ "${{ github.ref_type }}" == "tag" ]]; then - VERSION="${{ github.ref_name }}" - else - VERSION="${{ inputs.ref }}" - fi + VERSION="${RELEASE_TAG:-$REF}" echo "REPOSITORY_BUILD_VERSION=${VERSION}-${{ github.run_id }}" >> "$GITHUB_ENV" else # Regular git build (PR, branch push, etc). Read the SHA from the checkout so diff --git a/AGENTS.md b/AGENTS.md index 3a94d132..bb24dad6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,10 +97,11 @@ When modifying the version pipeline, the base config (`php-versions-base-config. - Builds run via GitHub Actions using **Depot** (`depot/build-push-action`) for multi-arch (`linux/amd64` + `linux/arm64/v8`). Depot builds both architectures natively on its own builders; the GitHub runner only orchestrates, so runner size and architecture do not affect build speed. - Publishing is three phases, each a reusable workflow the callers (`action_publish-images-*.yml`) chain: `service_setup-matrix.yml` generates the matrix once; `service_build-images.yml` builds one variation and saves every image to the Depot Registry (nothing public yet), recording the Depot build ID and digest; `service_test-images.yml` pulls the saved images on native amd64 and arm64 Depot runners (Depot sponsors the project, so they are free here and have no concurrency cap), one job per PHP version and base OS so the variations that share base layers share one pull, and runs `scripts/test-image.sh` against each; `service_publish-images.yml` promotes the tested builds with `depot push`, which copies the saved image to every Docker Hub and GHCR tag server-side (no image bytes touch the runner), one job per variation, and fails if the published digest is not the tested one. GHCR allows 2,000 requests a minute per token and `depot push` does not retry a 429, so the publish job pushes only two images at a time and retries a failed push with a growing pause; raising `MAX_PARALLEL` gets every variation's job rate limited at once. `service_report.yml` renders the run table last. Jobs render as `build cli / 8.4.25-bookworm`, `test / 8.4.25-bookworm arm64`, `publish / cli`. - A broken image can never reach a public tag: what users pull is byte-for-byte what the tests ran. Publishing is all or nothing: if any image fails to build or fails its test, no image from that run is published, including on the weekly rebuilds. Saved images live in the Depot Registry until the project's retention setting removes them. +- Stable releases start from pushing the version tag (`git push origin v5.1.0`), not from publishing a release on GitHub. "Docker Publish (Production Images)" builds, tests, and publishes the tag's images, then publishes the draft release (or one with generated notes). Publishing locks the tag, so a failed build leaves it unlocked: delete the tag, fix the problem, and push it again. Floating tags (`8.4-fpm-nginx`, `latest`) move only when the version is newer than every published stable release. An older version, such as a backport tagged on a `release/*` branch, gets release tags only and `--latest=false`. To build a specific PHP patch, pin it in `php-versions-base-config.yml` and commit it, so the tag alone reproduces the build. Beta releases still start from a GitHub prerelease. - The build matrix is generated from the PHP version pipeline described above. - Image tags follow the pattern: `serversideup/php:{version}-{variation}` (Debian default) or `serversideup/php:{version}-{variation}-{os}` (Alpine/specific OS). - Depot authentication: the project ID lives in `depot.json` (not a secret). Same-repo runs authenticate through a Depot OIDC trust relationship (`id-token: write`). Pull requests from forks have no OIDC token, so Depot falls back to its open-source pull request flow (https://depot.dev/blog/github-actions-oss-fork-builds): the full matrix builds on ephemeral builders without the project cache, nothing is saved, and each build job loads its amd64 image onto the runner and runs `scripts/test-image.sh` there instead, so the build check still means a tested image. A maintainer publishes a fork's images to `serversideup/php-dev`, and gets the arm64 tests, by running the "Docker Publish (PR Images)" workflow manually with the PR number. -- The CI helper script `scripts/build-summary.sh` is checked by `scripts/tests/run.sh`, a single file of plain assertions over sample images. Run it after changing the script and add an assertion when you add behavior. Keep jq programs explicit with parentheses: `a + b as $x | ...` parses differently across jq versions, and runners ship an older jq than most laptops. +- The CI helper scripts `scripts/build-summary.sh` and `scripts/assemble-docker-tags.sh` are checked by `scripts/tests/run.sh`, a single file of plain assertions over sample images and tags. Run it after changing either script and add an assertion when you add behavior. Keep jq programs explicit with parentheses: `a + b as $x | ...` parses differently across jq versions, and runners ship an older jq than most laptops. - The project is enrolled in Anthropic's OSS Scanner (`projects/serversideup-php` in anthropics/oss-scanner), which builds `.oss-scanner/Dockerfile` from `main` and audits it offline. That Dockerfile copies `src/` over each published web server image with the same `COPY` lines as `src/variations/*/Dockerfile`, and `.oss-scanner/run-variation` starts each one the way `docker run` would. Nothing fails when these drift, so update them in the same change as: - a variation's `COPY` layout or post-copy permission step: the matching overlay and runtime stages in `.oss-scanner/Dockerfile` - a new or removed web server variation: its stages in `.oss-scanner/Dockerfile`, and its default command in `run-variation` if it is not `/init` diff --git a/docs/content/docs/1.getting-started/7.upgrade-guide.md b/docs/content/docs/1.getting-started/7.upgrade-guide.md index 7172b453..819409f8 100644 --- a/docs/content/docs/1.getting-started/7.upgrade-guide.md +++ b/docs/content/docs/1.getting-started/7.upgrade-guide.md @@ -18,7 +18,7 @@ Regardless if you are choosing to use floating tags or pinned releases, it is hi ## How our releases work -All source code merges into the `main` branch. A pre-release is created for beta testing. After internal and community testing, we [publish a release on GitHub](https://github.com/serversideup/docker-php/releases) with a detailed changelog. Publishing a release triggers a production build that pushes a new set of tags to Docker Hub and GitHub Packages. +All source code merges into the `main` branch. A pre-release is created for beta testing. After internal and community testing, GitHub Actions builds and tests every image, pushes a new set of tags to Docker Hub and GitHub Packages, and then publishes the [release on GitHub](https://github.com/serversideup/docker-php/releases) with a detailed changelog. Production images are also rebuilt **automatically every Tuesday at 08:00 UTC** so floating tags pick up the latest upstream PHP and operating system security patches even when we haven't cut a release that week. diff --git a/scripts/assemble-docker-tags.sh b/scripts/assemble-docker-tags.sh index 6fa93242..ff9675ef 100755 --- a/scripts/assemble-docker-tags.sh +++ b/scripts/assemble-docker-tags.sh @@ -1,6 +1,6 @@ #!/bin/bash ################################################### -# Usage: assemble-docker-tags.sh --variation --os --patch-version [--stable-release --github-release-tag ] +# Usage: assemble-docker-tags.sh --variation --os --patch-version [--stable-release --github-release-tag --release-tags-only] ################################################### # This scripts dives deep into the advanced logic of assembling Docker tags for GitHub Actions. # If $CI is "true", it outputs the tags to GITHUB_ENV for use in subsequent steps. @@ -32,6 +32,9 @@ DEFAULT_IMAGE_VARIATION="${DEFAULT_IMAGE_VARIATION:-$(yq e -r '.php_variations[] IFS=',' read -ra DOCKER_REGISTRY_REPOSITORIES <<< "${DOCKER_REGISTRY_REPOSITORIES:-"docker.io/serversideup/php,ghcr.io/serversideup/php"}" DOCKER_TAG_PREFIX="${DOCKER_TAG_PREFIX:-""}" RELEASE_TYPE="${RELEASE_TYPE:-"testing"}" +GITHUB_RELEASE_TAG="${GITHUB_RELEASE_TAG:-""}" +# False for a release older than the newest one (a backport), so it never moves floating tags like 8.4-fpm-nginx +PUBLISH_FLOATING_TAGS="${PUBLISH_FLOATING_TAGS:-"true"}" ########################## # Functions @@ -81,9 +84,21 @@ check_vars() { return 0 } +append_docker_tag() { + tag_name=$1 + + if [[ -z "$DOCKER_TAGS" ]]; then + DOCKER_TAGS+="$tag_name" + else + DOCKER_TAGS+=",$tag_name" + fi + + echo_color_message blue "๐Ÿณ Set tag: $tag_name" +} + add_docker_tag() { # Function: add_docker_tag -# Description: Appends Docker tags to the DOCKER_TAGS variable and prints each tag. +# Description: Appends the floating and release tags for a suffix to the DOCKER_TAGS variable. # # Parameters: # docker_tag_suffix - The suffix to be appended to the Docker image name as part of the tag. @@ -91,30 +106,19 @@ add_docker_tag() { for image_name in "${DOCKER_REGISTRY_REPOSITORIES[@]}"; do - # Append a dash to tags that have a suffix - if [[ -n "$DOCKER_TAG_PREFIX" && "$docker_tag_suffix" != "$DOCKER_TAG_PREFIX" ]]; then - prefix_tag="$DOCKER_TAG_PREFIX-" - else - prefix_tag="" - fi - - tag_name="$image_name:$prefix_tag$docker_tag_suffix" - - if [[ -z "$DOCKER_TAGS" ]]; then - # Do not prefix with comma - DOCKER_TAGS+="$tag_name" - else - # Add a comma to separate the tags - DOCKER_TAGS+=",$tag_name" - fi + if [[ "$PUBLISH_FLOATING_TAGS" == "true" ]]; then + # Append a dash to tags that have a suffix + if [[ -n "$DOCKER_TAG_PREFIX" && "$docker_tag_suffix" != "$DOCKER_TAG_PREFIX" ]]; then + prefix_tag="$DOCKER_TAG_PREFIX-" + else + prefix_tag="" + fi - # Trim commas for a better output - echo_color_message blue "๐Ÿณ Set tag: ${tag_name//,} " + append_docker_tag "$image_name:$prefix_tag$docker_tag_suffix" + fi - if [[ -n "$GITHUB_RELEASE_TAG" && "$GITHUB_REF_TYPE" == "tag" && "$docker_tag_suffix" != "latest" ]]; then - release_tag_name="$image_name:$docker_tag_suffix-$GITHUB_RELEASE_TAG" - DOCKER_TAGS+=",$release_tag_name" - echo_color_message blue "๐Ÿณ Set tag: $release_tag_name" + if [[ -n "$GITHUB_RELEASE_TAG" && "$docker_tag_suffix" != "latest" ]]; then + append_docker_tag "$image_name:$docker_tag_suffix-$GITHUB_RELEASE_TAG" fi done @@ -228,6 +232,7 @@ help_menu() { echo " --patch-version Set the PHP patch version (e.g., 8.4.25, 8.1.34)" echo " --github-release-tag Set the GitHub release tag" echo " --stable-release Flag the tags for a stable release" + echo " --release-tags-only Skip floating tags (for a release older than the newest one)" echo echo "Environment Variables (Defaults):" echo " DOCKER_REGISTRY_REPOSITORIES Names of images to tag (default: 'docker.io/serversideup/php' 'ghcr.io/serversideup/php')" @@ -253,10 +258,13 @@ while [[ $# -gt 0 ]]; do shift 2 ;; --github-release-tag) - GITHUB_REF_TYPE="tag" GITHUB_RELEASE_TAG="$2" shift 2 ;; + --release-tags-only) + PUBLISH_FLOATING_TAGS="false" + shift + ;; --stable-release) RELEASE_TYPE="latest" shift @@ -511,6 +519,11 @@ if is_latest_stable_patch_within_build_minor; then fi fi +if [[ -z "$DOCKER_TAGS" ]]; then + echo_color_message red "๐Ÿ›‘ ERROR: No tags to publish. Floating tags are off, so a release tag (--github-release-tag) is required." + exit 1 +fi + echo_color_message green "๐Ÿš€ Summary of Docker Tags Being Shipped: $DOCKER_TAGS" # Save to GitHub's environment diff --git a/scripts/tests/run.sh b/scripts/tests/run.sh index f6833cef..cdfca63e 100755 --- a/scripts/tests/run.sh +++ b/scripts/tests/run.sh @@ -55,6 +55,55 @@ assert_contains "$summary" "| cli | 8.4.25 | bookworm | | | โŒ not built |" " assert_contains "$summary" "| built, not published |" "marks images that were built but not promoted" assert_contains "$summary" '`serversideup/php-dev:700-8.5.10-cli-trixie`' "shows the image reference without the registry prefix" +php_versions_file="$image_details/php-versions.yml" +cat > "$php_versions_file" <<'EOF' +php_variations: + - name: cli + default: true +php_versions: + - major: "8" + minor_versions: + - minor: "8.4" + base_os: [{name: trixie}] + patch_versions: [8.4.27] + - minor: "8.5" + base_os: [{name: trixie}] + patch_versions: [8.5.12] +operating_systems: + - family: debian + default: true + versions: + - {name: "Debian Trixie", version: trixie, number: 13} +EOF +tags() { + PHP_VERSIONS_FILE="$php_versions_file" DOCKER_REGISTRY_REPOSITORIES="docker.io/serversideup/php" \ + bash "$scripts_dir/assemble-docker-tags.sh" --variation cli --os trixie --patch-version 8.5.12 --stable-release "$@" 2>&1 \ + | sed -n 's/.*Summary of Docker Tags Being Shipped: //p' +} + +echo +echo "assemble-docker-tags.sh" +release_tags=$(tags --github-release-tag v5.1.0) +assert_contains ",$release_tags," ",docker.io/serversideup/php:8.5-cli," "a release publishes floating tags" +assert_contains ",$release_tags," ",docker.io/serversideup/php:8.5-cli-v5.1.0," "a release publishes its release tags" +assert_contains ",$release_tags," ",docker.io/serversideup/php:latest," "the newest release moves latest" +backport_tags=$(tags --github-release-tag v5.0.3 --release-tags-only) +assert_contains ",$backport_tags," ",docker.io/serversideup/php:8.5-cli-v5.0.3," "a backport publishes its release tags" +floating_tags=$(tr ',' '\n' <<< "$backport_tags" | grep -v -- '-v5\.0\.3$' || true) +if [ -n "$floating_tags" ]; then + echo "โŒ a backport publishes no floating tags" + echo " got: $floating_tags" + failures=$((failures + 1)) +else + echo "โœ… a backport publishes no floating tags" +fi +if tags --release-tags-only > /dev/null; then + echo "โŒ refuses to run with floating tags off and no release tag" + failures=$((failures + 1)) +else + echo "โœ… refuses to run with floating tags off and no release tag" +fi + echo if [ "$failures" -gt 0 ]; then echo "$failures check(s) failed" >&2 From e1b466eef4dd8d395b847122c643e8bdd3027d2c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 22 Oct 2026 02:19:12 +0000 Subject: [PATCH 2/8] Build redis from pinned GitHub sources instead of PECL --- .../docs/8.reference/2.command-reference.md | 7 ++++ scripts/conf/php-versions-base-config.yml | 6 ++++ scripts/dev.sh | 2 +- scripts/generate-matrix.sh | 2 +- scripts/get-php-versions.sh | 1 + ...er-php-serversideup-install-php-extensions | 35 +++++++++++++++++-- 6 files changed, 49 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/8.reference/2.command-reference.md b/docs/content/docs/8.reference/2.command-reference.md index ddcb1975..14f8a5f6 100644 --- a/docs/content/docs/8.reference/2.command-reference.md +++ b/docs/content/docs/8.reference/2.command-reference.md @@ -88,6 +88,13 @@ docker-php-serversideup-install-php-extensions "opcache redis zip" "redis=phpred The example above installs `opcache` and `zip` normally, but builds `redis` from the `develop` branch of `phpredis/phpredis`. +GitHub's source downloads leave out git submodules. If an extension needs one, add it after a `+` as `path=owner/repo@ref`. The command downloads each part into that path and installs the result. This is how our images build `redis` without PECL: + +```bash [Example - Installing an extension with a git submodule] +docker-php-serversideup-install-php-extensions "redis" \ + "redis=phpredis/phpredis@df4fab2de7fc327c54c94a13af2b9542e4fbd720+liblzf=nemequ/liblzf@fb25820c3c0aeafd127956ae6c115063b47e459a" +``` + ## docker-php-serversideup-install-php-ext-installer This is an internal helper script to shorten up the syntax for the installation of the PHP extension installer. diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index f160a263..5a7bfaab 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -12,6 +12,12 @@ php_variations: - "8.1" - "8.2" +# Applied to every PHP version before any minor's own overrides. redis is built from GitHub +# instead of PECL, which went down during the October 2026 security release. Both commits are +# pinned: phpredis 6.3.0, and the liblzf submodule commit that phpredis 6.3.0 points to. +php_extension_overrides: + - redis=phpredis/phpredis@df4fab2de7fc327c54c94a13af2b9542e4fbd720+liblzf=nemequ/liblzf@fb25820c3c0aeafd127956ae6c115063b47e459a + php_versions: - major: "8" minor_versions: diff --git a/scripts/dev.sh b/scripts/dev.sh index 9a2a3d40..5929abe1 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -212,7 +212,7 @@ check_vars \ # Auto-resolve PHP extension source overrides for the minor version being built if [ -z "$PHP_EXTENSION_OVERRIDES" ]; then PHP_EXTENSION_OVERRIDES=$(MINOR="$(php_minor_version "$PHP_BUILD_VERSION")" yq -r ' - [.php_versions[].minor_versions[] | select(.minor == env(MINOR)) | .php_extension_overrides // [] | .[]] | join(" ") + [(.php_extension_overrides // [] | .[]), (.php_versions[].minor_versions[] | select(.minor == env(MINOR)) | .php_extension_overrides // [] | .[])] | join(" ") ' "$BASE_PHP_VERSIONS_CONFIG_FILE") if [ -n "$PHP_EXTENSION_OVERRIDES" ]; then diff --git a/scripts/generate-matrix.sh b/scripts/generate-matrix.sh index 3b058e7b..12e786d2 100755 --- a/scripts/generate-matrix.sh +++ b/scripts/generate-matrix.sh @@ -43,7 +43,7 @@ yq -o=json "$PHP_VERSIONS_FILE" | jq -c ' | ($minor.base_os // [])[] as $os | ($minor.patch_versions // [])[] as $patch | select(is_supported($variation; $os)) - | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name, php_extension_overrides: (($minor.php_extension_overrides // []) | join(" "))} + | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name, php_extension_overrides: ((($root.php_extension_overrides // []) + ($minor.php_extension_overrides // [])) | join(" "))} ] | { include: ( . | sort_by(.patch_version | version_weight) | reverse ) } ' \ No newline at end of file diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 5808058f..a8b1e61e 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -435,6 +435,7 @@ if [ "$SKIP_DOWNLOAD" = false ]; then merged_json=$(jq -s ' { php_variations: (.[1].php_variations // []), + php_extension_overrides: (.[1].php_extension_overrides // []), php_versions: ( .[0].php_versions + .[1].php_versions | group_by(.major) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-extensions b/src/common/usr/local/bin/docker-php-serversideup-install-php-extensions index b27199ba..76c703cb 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-extensions +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-extensions @@ -8,6 +8,11 @@ set -oe # swapping an extension for a different source. Overrides are space separated # "name=source" pairs, where source is any value install-php-extensions accepts # (for example "redis=phpredis/phpredis@develop"). +# +# GitHub's source tarballs leave out git submodules, so a GitHub source can list +# them after a "+" as "path=owner/repo@ref" (for example +# "redis=phpredis/phpredis@+liblzf=nemequ/liblzf@"). Each one is +# downloaded into that path and the assembled directory is installed instead. script_name="docker-php-serversideup-install-php-extensions" @@ -29,18 +34,44 @@ resolve_extension_source() { resolved_extension="${override#*=}" fi done - echo "$resolved_extension" +} + +# Sets assembled_source_dir. Not called in a subshell, because the download helper's "set -o" prints to stdout. +assemble_github_source() { + assembled_source_dir=$(mktemp -d) + for part in $(printf '%s' "$1" | tr '+' ' '); do + case "$part" in + *=*) target_dir="$assembled_source_dir/${part%%=*}" repository="${part#*=}" ;; + *) target_dir="$assembled_source_dir" repository="$part" ;; + esac + mkdir -p "$target_dir" + docker-php-serversideup-download "https://codeload.github.com/${repository%@*}/tar.gz/${repository#*@}" "$assembled_source_dir.tar.gz" + tar -xzf "$assembled_source_dir.tar.gz" --strip-components 1 -C "$target_dir" + rm -f "$assembled_source_dir.tar.gz" + done + assembled_source_dirs="$assembled_source_dirs $assembled_source_dir" } ############ # Main ############ resolved_extensions="" +assembled_source_dirs="" for extension in $extensions; do - resolved_extensions="$resolved_extensions $(resolve_extension_source "$extension")" + resolve_extension_source "$extension" + case "$resolved_extension" in + *+*) + echo "๐Ÿ“ฆ Assembling $extension from $resolved_extension" + assemble_github_source "$resolved_extension" + resolved_extension="$assembled_source_dir" + ;; + esac + resolved_extensions="$resolved_extensions $resolved_extension" done echo "๐Ÿค– Installing PHP extensions:$resolved_extensions" install-php-extensions $resolved_extensions +rm -rf $assembled_source_dirs + echo "โšก๏ธ Completed installation of PHP extensions:$resolved_extensions" From 2273fa51948c4786157389b9aaa5bce6445118e9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 22 Oct 2026 02:19:12 +0000 Subject: [PATCH 3/8] Pin e-dant/watcher and retry apk installs --- .../bin/docker-php-serversideup-dep-install-alpine | 13 +++++++++++-- src/variations/frankenphp/Dockerfile | 9 +++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine b/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine index b9644df6..0a0f1854 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine +++ b/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine @@ -59,8 +59,17 @@ fi # "$*" joins the packages with single spaces for display DEP_PACKAGES="$*" echo "๐Ÿค– Installing: $DEP_PACKAGES" -apk update -apk add --no-cache "$@" +# apk has no retry option like apt's Acquire::Retries, and --no-cache fetches the index itself +attempt=1 +until apk add --no-cache "$@"; do + if [ "$attempt" -ge 3 ]; then + echo "๐Ÿ›‘ ERROR ($script_name): Unable to install $DEP_PACKAGES after $attempt attempts." + exit 1 + fi + echo "โš ๏ธ WARNING ($script_name): apk add failed. Retrying in $((attempt * 10)) seconds..." + sleep $((attempt * 10)) + attempt=$((attempt + 1)) +done echo "๐Ÿงผ Cleaning up installation of: $DEP_PACKAGES" diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index c4edbcbb..369a9587 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -4,6 +4,7 @@ ARG PHP_VERSION='8.5' ARG BASE_IMAGE="php:${PHP_VERSION}-zts-${BASE_OS_VERSION}" ARG FRANKENPHP_VERSION='1.13.1' ARG GOLANG_VERSION='1.27' +ARG WATCHER_VERSION='0.14.5' ######################## # Common @@ -53,6 +54,7 @@ FROM golang:${GOLANG_VERSION} AS golang-image FROM common AS frankenphp-build ARG FRANKENPHP_VERSION ARG GOLANG_VERSION +ARG WATCHER_VERSION ARG BUILD_DEPENDENCY_PACKAGES_ALPINE="\ argon2-dev \ bash \ @@ -100,12 +102,7 @@ RUN docker-php-serversideup-dep-install-alpine "$PHPIZE_DEPS ${BUILD_DEPENDENCY_ # Install e-dant/watcher (necessary for file watching) WORKDIR /usr/local/src/watcher -RUN WATCHER_TARBALL_URL=$(docker-php-serversideup-download https://api.github.com/repos/e-dant/watcher/releases/latest | \ - grep tarball_url | \ - awk '{ print $2 }' | \ - sed 's/,$//' | \ - sed 's/"//g') && \ - docker-php-serversideup-download "$WATCHER_TARBALL_URL" /tmp/watcher.tar.gz && \ +RUN docker-php-serversideup-download "https://github.com/e-dant/watcher/archive/refs/tags/${WATCHER_VERSION}.tar.gz" /tmp/watcher.tar.gz && \ tar xz --strip-components 1 -f /tmp/watcher.tar.gz && \ rm -f /tmp/watcher.tar.gz && \ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \ From 3ef79e24203967aa253e7ce3af54b6bd1e5124ff Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 22 Oct 2026 02:52:01 +0000 Subject: [PATCH 4/8] Fix helper scripts printing shell options to stdout A bare "set -oe" turns on exit-on-error but also prints the shell's option table. Found by @tomschlick in #726. --- .../usr/local/bin/docker-php-serversideup-dep-install-alpine | 2 +- .../usr/local/bin/docker-php-serversideup-dep-install-debian | 2 +- src/common/usr/local/bin/docker-php-serversideup-download | 2 +- .../bin/docker-php-serversideup-install-php-ext-installer | 2 +- .../local/bin/docker-php-serversideup-install-php-extensions | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine b/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine index 0a0f1854..aee18c12 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine +++ b/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine @@ -1,5 +1,5 @@ #!/bin/sh -set -oe +set -e ################################################### # Usage: docker-php-serversideup-dep-install-alpine [alpine-packages] diff --git a/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian b/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian index 967cf91f..90287ad3 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian +++ b/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian @@ -1,5 +1,5 @@ #!/bin/sh -set -oe +set -e ################################################### # Usage: docker-php-serversideup-dep-install-debian [debian-packages] diff --git a/src/common/usr/local/bin/docker-php-serversideup-download b/src/common/usr/local/bin/docker-php-serversideup-download index e587bc8f..38a180ce 100755 --- a/src/common/usr/local/bin/docker-php-serversideup-download +++ b/src/common/usr/local/bin/docker-php-serversideup-download @@ -1,5 +1,5 @@ #!/bin/sh -set -oe +set -e ################################################### # Usage: docker-php-serversideup-download [output-file] diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index ad37020e..cd494a0c 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -1,5 +1,5 @@ #!/bin/sh -set -oe +set -e ################################################### # Usage: docker-php-serversideup-install-php-ext-installer [version] diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-extensions b/src/common/usr/local/bin/docker-php-serversideup-install-php-extensions index 76c703cb..1e704a5f 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-extensions +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-extensions @@ -1,5 +1,5 @@ #!/bin/sh -set -oe +set -e ################################################### # Usage: docker-php-serversideup-install-php-extensions [extensions] [overrides] @@ -36,7 +36,7 @@ resolve_extension_source() { done } -# Sets assembled_source_dir. Not called in a subshell, because the download helper's "set -o" prints to stdout. +# Sets assembled_source_dir. assemble_github_source() { assembled_source_dir=$(mktemp -d) for part in $(printf '%s' "$1" | tr '+' ' '); do From 987f1a83a47c42b33cb10a9161b905e3eb62a565 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 22 Oct 2026 02:52:01 +0000 Subject: [PATCH 5/8] Add serversideup/php-base, PHP compiled from php.net with docker-library's template --- .gitignore | 3 +- AGENTS.md | 6 + scripts/dev.sh | 33 + scripts/generate-php-base.sh | 153 +++++ scripts/test-base-image.sh | 147 +++++ src/base/UPSTREAM | 28 + ...6895DE9A475111D537A6E69134FF30BC5A99B5.asc | 13 + ...16E93D95AF471243E26761770426E17EBBB3DD.asc | 52 ++ ...98C0117593497A5EC5C199286AF1F9897469DC.asc | 65 ++ ...B641343D8C104B2B146DC3F9C39DC0B9698544.asc | 29 + ...D9AF6BC72A80D6691719C8AA23F5BE9C7097D4.asc | 13 + ...FF17B64DC1C244F5D0EAC3E43535E2EB19010E.asc | 53 ++ ...7F99A0CB8F05C8A6958D6256A97AF7600A39A6.asc | 13 + ...D8691FDAEDF03BDF6E460563F15A9B715376CA.asc | 173 +++++ ...8D937575603EB4ABB725861C0779DC5C0A9DE4.asc | 21 + ...5C03BC702BE9515344AE3374E44BC9067701A5.asc | 29 + ...0913E4DF209907D8E30D96659A97C9CF2A795A.asc | 56 ++ src/base/upstream/Dockerfile-linux.template | 598 ++++++++++++++++++ src/base/upstream/LICENSE | 20 + src/base/upstream/apply-templates.sh | 85 +++ src/base/upstream/bashbrew/LICENSE | 202 ++++++ src/base/upstream/bashbrew/jq-template.awk | 135 ++++ src/base/upstream/docker-php-entrypoint | 9 + src/base/upstream/docker-php-ext-configure | 69 ++ src/base/upstream/docker-php-ext-enable | 121 ++++ src/base/upstream/docker-php-ext-install | 143 +++++ src/base/upstream/docker-php-source | 34 + src/variations/cli/Dockerfile | 3 +- src/variations/fpm-nginx/Dockerfile | 2 +- src/variations/fpm/Dockerfile | 3 +- 30 files changed, 2307 insertions(+), 4 deletions(-) create mode 100755 scripts/generate-php-base.sh create mode 100755 scripts/test-base-image.sh create mode 100644 src/base/UPSTREAM create mode 100644 src/base/keys/016895DE9A475111D537A6E69134FF30BC5A99B5.asc create mode 100644 src/base/keys/0616E93D95AF471243E26761770426E17EBBB3DD.asc create mode 100644 src/base/keys/1198C0117593497A5EC5C199286AF1F9897469DC.asc create mode 100644 src/base/keys/39B641343D8C104B2B146DC3F9C39DC0B9698544.asc create mode 100644 src/base/keys/49D9AF6BC72A80D6691719C8AA23F5BE9C7097D4.asc create mode 100644 src/base/keys/5CFF17B64DC1C244F5D0EAC3E43535E2EB19010E.asc create mode 100644 src/base/keys/9D7F99A0CB8F05C8A6958D6256A97AF7600A39A6.asc create mode 100644 src/base/keys/AFD8691FDAEDF03BDF6E460563F15A9B715376CA.asc create mode 100644 src/base/keys/C28D937575603EB4ABB725861C0779DC5C0A9DE4.asc create mode 100644 src/base/keys/D95C03BC702BE9515344AE3374E44BC9067701A5.asc create mode 100644 src/base/keys/E60913E4DF209907D8E30D96659A97C9CF2A795A.asc create mode 100644 src/base/upstream/Dockerfile-linux.template create mode 100644 src/base/upstream/LICENSE create mode 100755 src/base/upstream/apply-templates.sh create mode 100644 src/base/upstream/bashbrew/LICENSE create mode 100644 src/base/upstream/bashbrew/jq-template.awk create mode 100755 src/base/upstream/docker-php-entrypoint create mode 100755 src/base/upstream/docker-php-ext-configure create mode 100755 src/base/upstream/docker-php-ext-enable create mode 100755 src/base/upstream/docker-php-ext-install create mode 100755 src/base/upstream/docker-php-source diff --git a/.gitignore b/.gitignore index 310c9d1b..ed40daf7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ node_modules php-versions.yml *.tmp /docs/_OLD_ -.playwright-mcp \ No newline at end of file +.playwright-mcp +/build/ diff --git a/AGENTS.md b/AGENTS.md index bb24dad6..dcec86e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,7 @@ This project maintains open source PHP Docker images (`serversideup/php`) for La ``` src/ variations/ # One Dockerfile per image variation (cli, fpm, fpm-apache, fpm-nginx, frankenphp) + base/ # serversideup/php-base: PHP compiled from php.net with docker-library's vendored template common/ # Shared scripts and configs copied into ALL variations usr/local/bin/ # Entrypoint and helper scripts (POSIX /bin/sh) etc/entrypoint.d/# Numbered priority entrypoint scripts (00-*, 50-*, etc.) @@ -54,8 +55,12 @@ Each variation Dockerfile uses multi-stage builds: 2. Variation-specific configs live in `src/variations//etc/` 3. The build context is the **project root** (not `src/`), so COPY paths are relative to root: `COPY --chmod=755 src/common/ /` +### PHP base image +Every variation builds `FROM ${BASE_IMAGE}`, which defaults to the official `php:` image. `serversideup/php-base` is the same image compiled by us from the php.net tarball, so a PHP release can ship without waiting for Docker Hub. `scripts/generate-php-base.sh` renders it with docker-library's own template and renderer, vendored unchanged in `src/base/upstream/` (see `src/base/UPSTREAM`). Every change we make to the rendered Dockerfiles lives in that script's `patch_dockerfile`. Keep that list short, because each patch is a difference from the official image. Signing keys come from `src/base/keys/`, never a keyserver. `scripts/test-base-image.sh ` runs docker-library's tests and checks parity with the official image. The base SAPI is `cli` for cli, `zts` for frankenphp, and `fpm` for the rest. PHP 8.1 is not in upstream's template, so it stays on the official `php:8.1.34` images. + ### Build args used across Dockerfiles - `PHP_VERSION`, `BASE_OS_VERSION`, `PHP_VARIATION` -- set by CI matrix or `scripts/dev.sh` +- `BASE_IMAGE` -- the PHP base image (default: the official `php:` image; `scripts/dev.sh --php-from-source` builds and uses `serversideup/php-base`) - `NGINX_VERSION` -- resolved per-OS from `scripts/conf/php-versions-base-config.yml` - `REPOSITORY_BUILD_VERSION` -- image version label @@ -78,6 +83,7 @@ scripts/dev.sh --variation fpm-nginx --version 8.4 --os bookworm # Other examples scripts/dev.sh --variation cli --version 8.5 --os alpine3.22 scripts/dev.sh --variation fpm-nginx --version 8.4 --os bookworm --no-cache +scripts/dev.sh --variation fpm-nginx --version 8.4 --os trixie --php-from-source # also needs curl, jq, gawk scripts/dev.sh --variation frankenphp --version 8.5 --os bookworm --push ``` diff --git a/scripts/dev.sh b/scripts/dev.sh index 5929abe1..d8e9b079 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -28,6 +28,7 @@ DOCKER_REPOSITORY="${DOCKER_REPOSITORY:-"serversideup/php"}" DOCKER_ADDITIONAL_BUILD_ARGS=() PUSH_TO_REGISTRY=false PLATFORM="" +PHP_FROM_SOURCE=false # UI Colors function ui_set_yellow { @@ -95,6 +96,26 @@ php_minor_version() { fi } +base_image_sapi() { + case "$PHP_BUILD_VARIATION" in + cli) echo "cli" ;; + frankenphp) echo "zts" ;; + *) echo "fpm" ;; + esac +} + +build_php_base_image() { + local sapi base_line version context_dir + sapi=$(base_image_sapi) + base_line=$("$SCRIPT_DIR/generate-php-base.sh" --version "$PHP_BUILD_VERSION" --os "$PHP_BUILD_BASE_OS" --sapi "$sapi") + read -r version _ _ context_dir <<< "$base_line" + BASE_IMAGE="serversideup/php-base:${version}-${sapi}-${PHP_BUILD_BASE_OS}" + + echo_color_message yellow "๐Ÿณ Building PHP $version from source: $BASE_IMAGE" + docker buildx build --platform "$PLATFORM" --tag "$BASE_IMAGE" "$context_dir" + echo_color_message green "โœ… PHP Base Image Built: $BASE_IMAGE" +} + build_docker_image() { build_tag="${DOCKER_REPOSITORY}:${PHP_BUILD_PREFIX}${PHP_BUILD_VERSION}-${PHP_BUILD_VARIATION}-${PHP_BUILD_BASE_OS}" echo_color_message yellow "๐Ÿณ Building Docker Image: $build_tag" @@ -103,9 +124,16 @@ build_docker_image() { if [ -z "$PLATFORM" ]; then PLATFORM=$(detect_platform) fi + + if [ "$PHP_FROM_SOURCE" = true ]; then + build_php_base_image + fi # Assemble build arguments local build_args=() + if [ -n "$BASE_IMAGE" ]; then + build_args+=(--build-arg "BASE_IMAGE=$BASE_IMAGE") + fi build_args+=(--build-arg PHP_VARIATION="$PHP_BUILD_VARIATION") build_args+=(--build-arg PHP_VERSION="$PHP_BUILD_VERSION") build_args+=(--build-arg BASE_OS_VERSION="$PHP_BUILD_BASE_OS") @@ -150,6 +178,7 @@ help_menu() { echo " --registry Set a custom registry (e.g., localhost:5000)" echo " --platform Set the platform (default: detected from system architecture)" echo " --push Push the image to the registry" + echo " --php-from-source Compile PHP from php.net into serversideup/php-base first and build on it" echo " --* Any additional options will be passed to the docker buildx command" } @@ -183,6 +212,10 @@ while [[ $# -gt 0 ]]; do PLATFORM="$2" shift 2 ;; + --php-from-source) + PHP_FROM_SOURCE=true + shift + ;; --*) # If there's a next argument and it starts with '--', treat the current argument as standalone. # Otherwise, pair the current argument with the next. diff --git a/scripts/generate-php-base.sh b/scripts/generate-php-base.sh new file mode 100755 index 00000000..e6324140 --- /dev/null +++ b/scripts/generate-php-base.sh @@ -0,0 +1,153 @@ +#!/bin/bash +set -eo pipefail + +################################################### +# Usage: generate-php-base.sh --version --os [--sapi ] [--output ] +################################################### +# Renders the Dockerfiles for serversideup/php-base: PHP compiled from the php.net +# source tarball, as a drop-in for the official "php" image. It runs docker-library's +# own template and renderer (vendored unchanged in src/base/upstream), so the result +# matches the official image except for the patches in patch_dockerfile below. +# +# --version, --os, and --sapi can be repeated. A version is a minor (8.4, resolved to its +# latest release on php.net) or an exact release (8.4.27). --sapi defaults to cli, fpm, +# and zts. Each Dockerfile is rendered with its build context into +# ///, and one line per image is printed to stdout: +# +# +# +# Requires bash, curl, jq, and gawk. + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +PROJECT_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +BASE_DIR="$PROJECT_ROOT_DIR/src/base" +OUTPUT_DIR="$PROJECT_ROOT_DIR/build/php-base" +VERSIONS=() +OPERATING_SYSTEMS=() +SAPIS=() + +error() { + echo "๐Ÿ›‘ ERROR: $1" >&2 + exit 1 +} + +help_menu() { + sed -n '4,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' +} + +while [[ $# -gt 0 ]]; do + case $1 in + --version) VERSIONS+=("$2"); shift 2 ;; + --os) OPERATING_SYSTEMS+=("$2"); shift 2 ;; + --sapi) SAPIS+=("$2"); shift 2 ;; + --output) OUTPUT_DIR="$2"; shift 2 ;; + --help) help_menu; exit 0 ;; + *) help_menu >&2; error "Unknown argument: $1" ;; + esac +done + +[ "${#VERSIONS[@]}" -gt 0 ] || error "Pass at least one --version." +[ "${#OPERATING_SYSTEMS[@]}" -gt 0 ] || error "Pass at least one --os." +[ "${#SAPIS[@]}" -gt 0 ] || SAPIS=(cli fpm zts) +for tool in curl jq gawk; do + command -v "$tool" > /dev/null || error "$tool is required (on macOS: brew install $tool)." +done + +# The minors upstream's template still supports are the ones it has signing keys for. +SUPPORTED_MINORS=$(sed -n '/^ENV GPG_KEYS/,/\[rcVersion\]/p' "$BASE_DIR/upstream/Dockerfile-linux.template" \ + | grep -oE '^\s*"[0-9]+\.[0-9]+": \[' | grep -oE '[0-9]+\.[0-9]+') + +replace_in_dockerfile() { + local file="$1" old="$2" new="$3" content + content=$(<"$file") + if [[ "$content" != *"$old"* ]]; then + error "Upstream's template changed and a patch no longer applies to $file. Update patch_dockerfile in $0. Missing text: $old" + fi + printf '%s\n' "${content//"$old"/"$new"}" > "$file" +} + +# Every change we make to upstream's output. Keep this list short: each one is a +# difference from the official image that we maintain ourselves. +patch_dockerfile() { + local dockerfile="$1" sha256="$2" + + # Verify the tarball with the release managers' keys from src/base/keys instead of + # fetching them from a keyserver, which can be slow or down when a release lands. + replace_in_dockerfile "$dockerfile" \ + "ENV PHP_SHA256=\"$sha256\""$'\n\n'"RUN set -eux; \\" \ + "ENV PHP_SHA256=\"$sha256\""$'\n\n'"RUN --mount=type=bind,source=keys,target=/usr/src/php-keys set -eux; \\" + replace_in_dockerfile "$dockerfile" \ + 'gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key";' \ + 'gpg --batch --import "/usr/src/php-keys/$key.asc";' + + # Every image in the matrix downloads at once, so retry transient errors from php.net and GitHub. + replace_in_dockerfile "$dockerfile" 'curl -fsSL -o php.tar.xz' 'curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors -o php.tar.xz' + + # pecl.php.net went down during the October 2026 security release. Refreshing the channel + # list is a convenience (install-php-extensions refreshes it again), so it must not fail the build. + if [[ "$(<"$dockerfile")" == *'pecl update-channels;'* ]]; then + replace_in_dockerfile "$dockerfile" 'pecl update-channels;' 'pecl update-channels || true;' + fi + + # php -i reports who built PHP. Point bug reports for these builds to us, not docker-library. + replace_in_dockerfile "$dockerfile" \ + "PHP_BUILD_PROVIDER='https://github.com/docker-library/php'" \ + "PHP_BUILD_PROVIDER='https://github.com/serversideup/docker-php'" +} + +rm -rf "$OUTPUT_DIR" +mkdir -p "$OUTPUT_DIR" +cp -a "$BASE_DIR/upstream/." "$OUTPUT_DIR/" + +variants=() +for os in "${OPERATING_SYSTEMS[@]}"; do + for sapi in "${SAPIS[@]}"; do + variants+=("$os/$sapi") + done +done + +versions_json='{}' +for version in "${VERSIONS[@]}"; do + minor=$(echo "$version" | cut -d. -f1,2) + grep -qxF "$minor" <<< "$SUPPORTED_MINORS" \ + || error "PHP $minor is not supported by docker-library's template. Supported: $(echo $SUPPORTED_MINORS)" + [ "$(jq --arg minor "$minor" 'has($minor)' <<< "$versions_json")" = "false" ] \ + || error "PHP $minor was passed more than once." + + release=$(curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors \ + "https://www.php.net/releases/index.php?json&version=${version}") \ + || error "Could not fetch PHP $version from php.net." + jq -e 'has("error") | not' <<< "$release" > /dev/null || error "php.net does not know PHP $version: $(jq -r .error <<< "$release")" + + versions_json=$(jq -c \ + --arg minor "$minor" \ + --argjson release "$release" \ + --args ' + .[$minor] = ( + ($release.source[] | select(.filename | endswith(".tar.xz"))) as $source + | { + version: $release.version, + url: ("https://www.php.net/distributions/" + $source.filename), + ascUrl: ("https://www.php.net/distributions/" + $source.filename + ".asc"), + sha256: $source.sha256, + variants: [ $ARGS.positional[] ] + } + )' "${variants[@]}" <<< "$versions_json") +done +echo "$versions_json" > "$OUTPUT_DIR/versions.json" + +( + cd "$OUTPUT_DIR" + BASHBREW_SCRIPTS="$OUTPUT_DIR/bashbrew" ./apply-templates.sh >&2 +) + +jq -r 'to_entries[] | .key as $minor | .value | .version as $version | .sha256 as $sha256 | .variants[] | [$minor, $version, $sha256, .] | @tsv' "$OUTPUT_DIR/versions.json" \ + | while IFS=$'\t' read -r minor version sha256 variant; do + context_dir="$OUTPUT_DIR/$minor/$variant" + patch_dockerfile "$context_dir/Dockerfile" "$sha256" + mkdir -p "$context_dir/keys" + for key in $(sed -n 's/^ENV GPG_KEYS //p' "$context_dir/Dockerfile"); do + cp "$BASE_DIR/keys/$key.asc" "$context_dir/keys/" || error "Missing signing key $key in $BASE_DIR/keys. Add it from https://www.php.net/gpg-keys.php." + done + echo "$version ${variant#*/} ${variant%/*} $context_dir" + done diff --git a/scripts/test-base-image.sh b/scripts/test-base-image.sh new file mode 100755 index 00000000..12e21c3b --- /dev/null +++ b/scripts/test-base-image.sh @@ -0,0 +1,147 @@ +#!/bin/bash +set -eo pipefail + +################################################### +# Usage: test-base-image.sh [official-image] +################################################### +# Tests a serversideup/php-base image (see generate-php-base.sh). The first checks are +# docker-library's own tests for the official image (php-hello-world, php-ext-install, +# php-argon2, php-fpm-hello-web from https://github.com/docker-library/official-images/tree/326d6f326e8ed6ea7b66fcc53c6b78dd22334a2d/test/tests). +# +# Given an official image (example: php:8.4.27-fpm-trixie), it also checks that ours is a +# drop-in for it: the same image config, ENV, extensions, configure flags, and +# /usr/local/etc. When the two PHP versions differ, as on the day of a security release +# when the official image isn't out yet, values that change with the version are skipped. + +image="$1" +sapi="$2" +official_image="$3" + +if [ -z "$image" ] || [ -z "$sapi" ]; then + sed -n '4,15p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' >&2 + exit 1 +fi + +failures=0 +pass() { echo "โœ… $1"; } +fail() { + echo "โŒ $1" + [ -z "$2" ] || echo "$2" | sed 's/^/ /' + failures=$((failures + 1)) +} +check() { + local message="$1" + shift + local output + if output=$("$@" 2>&1); then pass "$message"; else fail "$message" "$output"; fi +} +run_php() { + docker run --rm -i "$1" php -d display_errors=stderr +} + +echo "๐Ÿงช Testing $image ($sapi)" + +if [ "$(echo " /dev/null <<'EOF' +FROM debian:trixie-slim +RUN apt-get update && apt-get install -y --no-install-recommends libfcgi-bin && apt-get dist-clean +ENTRYPOINT ["cgi-fcgi"] +EOF + cid=$(docker run -d "$image") + trap 'docker rm -vf "$cid" > /dev/null' EXIT + docker exec "$cid" sh -c "echo ' /var/www/html/index.php" + fcgi_request() { + docker run --rm --network "container:$cid" \ + -e REQUEST_METHOD=GET -e SCRIPT_NAME=/index.php -e SCRIPT_FILENAME=/var/www/html/index.php -e QUERY_STRING="$1" \ + "$client_image" -bind -connect 127.0.0.1:9000 + } + for _ in $(seq 30); do fcgi_request "" > /dev/null 2>&1 && break; sleep 1; done + hello="world-$RANDOM-$RANDOM" + if [ "$(fcgi_request "hello=$hello" 2>&1 | tail -n1)" = "$hello" ]; then + pass "php-fpm-hello-web" + else + fail "php-fpm-hello-web" "$(docker logs "$cid" 2>&1 | tail -n5)" + fi +fi + +if [ "$(docker run --rm "$image" php -i | sed -n 's/^Build Provider => //p')" = "https://github.com/serversideup/docker-php" ]; then + pass "php -i reports serversideup/docker-php as the build provider" +else + fail "php -i reports serversideup/docker-php as the build provider" +fi + +if [ -n "$official_image" ]; then + echo + echo "๐Ÿ” Comparing with $official_image" + docker pull -q "$official_image" > /dev/null + version=$(docker run --rm "$image" php -r 'echo PHP_VERSION;') + official_version=$(docker run --rm "$official_image" php -r 'echo PHP_VERSION;') + same_version=false + if [ "$version" = "$official_version" ]; then + same_version=true + else + echo "โ„น๏ธ PHP $version vs $official_version, so values that change with the version are skipped" + fi + + compare() { + local message="$1" ours theirs + ours=$("$2" "$image" 2>&1) + theirs=$("$2" "$official_image" 2>&1) + if [ "$ours" = "$theirs" ]; then + pass "$message" + else + fail "$message" "$(diff <(echo "$theirs") <(echo "$ours") | grep '^[<>]' | sed 's/^/ours: /')" + fi + } + image_config() { docker image inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}} {{.Config.User}} {{.Config.WorkingDir}} {{.Config.StopSignal}} {{json .Config.ExposedPorts}}' "$1"; } + env_names() { docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$1" | sed 's/=.*//' | sort; } + env_values() { docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$1" | sort; } + modules() { docker run --rm "$1" php -m; } + # The build provider differs on purpose and is checked above. + configure_flags() { docker run --rm "$1" php-config --configure-options | tr ' ' '\n' | grep -v '^PHP_BUILD_PROVIDER=' | sort; } + etc_files() { docker run --rm "$1" sh -c 'cd /usr/local/etc && find . -type f | sort'; } + etc_checksums() { docker run --rm "$1" sh -c 'cd /usr/local/etc && find . -type f | sort | xargs sha256sum'; } + users() { docker run --rm "$1" id www-data; } + + compare "entrypoint, command, user, workdir, stop signal, and ports" image_config + compare "ENV names" env_names + compare "www-data user" users + compare "extensions (php -m)" modules + compare "configure flags" configure_flags + compare "files in /usr/local/etc" etc_files + if [ "$same_version" = true ]; then + compare "ENV values" env_values + compare "contents of /usr/local/etc" etc_checksums + fi +fi + +echo +if [ "$failures" -gt 0 ]; then + echo "$failures check(s) failed" >&2 + exit 1 +fi +echo "All checks passed" diff --git a/src/base/UPSTREAM b/src/base/UPSTREAM new file mode 100644 index 00000000..267d2da4 --- /dev/null +++ b/src/base/UPSTREAM @@ -0,0 +1,28 @@ +serversideup/php-base compiles PHP with docker-library's own template, so it stays +a drop-in for the official "php" image. + +Vendored without changes: + + upstream/ https://github.com/docker-library/php at 5ec2a6a0dad498e89a91a39454dbb52e98ce2bdb (MIT, upstream/LICENSE) + Dockerfile-linux.template, apply-templates.sh, docker-php-entrypoint, + docker-php-ext-configure, docker-php-ext-enable, docker-php-ext-install, + docker-php-source + upstream/bashbrew/jq-template.awk + https://github.com/docker-library/bashbrew at 9f6a35772ac863a0241f147c820354e4008edf38 (Apache 2.0, upstream/bashbrew/LICENSE), + the commit apply-templates.sh pins + +Our changes to the rendered Dockerfiles are all in patch_dockerfile in +scripts/generate-php-base.sh, and the script fails if one stops applying. + +keys/ holds the release managers' public keys, one file per fingerprint listed in +the template's GPG_KEYS, exported from https://www.php.net/distributions/php-keyring.gpg. + +Updating, when upstream adds a PHP minor or an Alpine version (rarely urgent): + + 1. Copy the files above from the new docker-library/php commit and update the commit here. + If apply-templates.sh pins a new bashbrew commit, update jq-template.awk too. + 2. For any new fingerprint in GPG_KEYS, add keys/.asc: + gpg --import php-keyring.gpg && gpg --armor --export-options export-minimal --export + 3. Run scripts/generate-php-base.sh, then diff a rendered Dockerfile against the same + one in docker-library/php. Only the patch_dockerfile changes should differ. + 4. Build and run scripts/test-base-image.sh against the official image. diff --git a/src/base/keys/016895DE9A475111D537A6E69134FF30BC5A99B5.asc b/src/base/keys/016895DE9A475111D537A6E69134FF30BC5A99B5.asc new file mode 100644 index 00000000..97bf22fe --- /dev/null +++ b/src/base/keys/016895DE9A475111D537A6E69134FF30BC5A99B5.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaeCbnBYJKwYBBAHaRw8BAQdAIDg6YVeJ1fMK0GZFbT+qfZOl5tW2aLzvA5z1 +TzMoUqS0IU1hdHRlbyBCZWNjYXRpIDxtYmVjY2F0aUBwaHAubmV0PoiTBBMWCgA7 +FiEEAWiV3ppHURHVN6bmkTT/MLxambUFAmngm5wCGwMFCwkIBwICIgIGFQoJCAsC +BBYCAwECHgcCF4AACgkQkTT/MLxambWTMAEAhDuEjnkjbl1Pbm0WHhSueFqVCMxg +B6kLnV7+qhmk8z0A/1Ej/ld9vdSbLRYiRHl4mou7ydV78d41FtoJeYdxwCAFuDgE +aeCbnBIKKwYBBAGXVQEFAQEHQOP+ueE8Ur7AmHIWhLLB5vYS9ZXfs1ZWLTVQCxJI +vhAwAwEIB4h4BBgWCgAgFiEEAWiV3ppHURHVN6bmkTT/MLxambUFAmngm5wCGwwA +CgkQkTT/MLxambWoxwD/YDVsIkTjfyYCXulqSvd/dH3ENQSD6SgbY03e45FM5fsB +AMUyecsMoZB+3bUQJCuTs0NQ/E2Ii2f4NsBLRUfqkdsL +=rn0K +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/0616E93D95AF471243E26761770426E17EBBB3DD.asc b/src/base/keys/0616E93D95AF471243E26761770426E17EBBB3DD.asc new file mode 100644 index 00000000..d7e8b42b --- /dev/null +++ b/src/base/keys/0616E93D95AF471243E26761770426E17EBBB3DD.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +uiQPA0QDZA== +=67ZA +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/1198C0117593497A5EC5C199286AF1F9897469DC.asc b/src/base/keys/1198C0117593497A5EC5C199286AF1F9897469DC.asc new file mode 100644 index 00000000..14647de3 --- /dev/null +++ b/src/base/keys/1198C0117593497A5EC5C199286AF1F9897469DC.asc @@ -0,0 +1,65 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +wpxdIIyaoaS8NSuPIrV1g4SUXYs= +=w4mR +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/39B641343D8C104B2B146DC3F9C39DC0B9698544.asc b/src/base/keys/39B641343D8C104B2B146DC3F9C39DC0B9698544.asc new file mode 100644 index 00000000..63b0c2f2 --- /dev/null +++ b/src/base/keys/39B641343D8C104B2B146DC3F9C39DC0B9698544.asc @@ -0,0 +1,29 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGCHQTYBEAC6IU9aXEpVuLJNAzXwL7q9Vt1PkxJHr7kJynUg/3ISrOdJy2Eb +ola7GbEdvLZ69o+NbsL5vmOmT2AD0pEGfMgE9KWJ122iEfN4hrWThz+sHcVd/TKX +9E98g+HTpoJGtYcp6jYxG0YRP7dn1zSn9CawVJpMsoS2G0JZB/q2lIELoPRLMjic +RDBEfamTPi3vyYiuf4vvCcYYz3AEMYirt5f7rBrVOs3FVBozEqrN2fPRWvFgTnhs +Q8MEiq5/DMsnxFq2wgheAKPIAXhZiJFoKHsrpiAANEmGZBY2pbW05rBY1ZmJpi1y +CrSk582cdPo2Ca+NrNEPJ9spoEinAtApp/orirJYbjKViWqmh7mYYyv/NAQKwGBE +1up3tSGVj0rKG3f9seLvYsApHf0lhEQN/ypzC+pYDlSXzWxcQ8bifixb6XulaEgB +5XOHwuvpTK6HoNNdZM44Fl+B/eAXaebL6Vh0P1EN/2DBr4FGNb8vlXdvGxs7rJbJ +8+aNmYeNIdg5qbYubNsO2fU7yw9kTDNTej63Ql1a0Md2wfZze8dZy6LJ/tL//IH3 +JPd0jvT1y0LVIKoYVrIfv06zolVplqi72zfec1kpw0VR6dT/LyhBlyNumqJMRHdZ +lX4pUCpx79pOonjHNTNuhjOPLlzai0ooWBtloDZfNGoFRVNNJv/hGvVM+wARAQAB +tBtCZW4gUmFtc2V5IDxyYW1zZXlAcGhwLm5ldD6JAlQEEwEIAD4CGwMFCwkIBwIG +FQoJCAsCBBYCAwECHgECF4AWIQQ5tkE0PYwQSysUbcP5w53AuWmFRAUCYoZZgwUJ +CJ09uAAKCRD5w53AuWmFRPRlD/0f0JsjsWCF4YziRpBe8LCs52o0VrtNMUW206Hz +a6CXI3AHLXHXeCmYwU0LpEC3vssA6vatMhrPKnbzVXaERJ8JCu7HyHP7piYU5lnW +6JY8NO8fjT85+9cS7IvMQ09ddypUnoD0i+rX5Eg6p7+Cqx0CW+B2CXsfcIXJ81vy +kdNeW4h+g4nx0H00cOF6oXnJ3ojQn3WjTE0W9Qz+FEqyQBSJSCvM8ILiT9ImJykn +iJ1gQB4K6FHKy7nFAHQX6EHQER0KJ5ZJUNstasYdZuKorR9DHlCus16E2ABIFTGC +SDduoBWjKAUXdff9Vh/KdodiHHnLGghQT1qSNY278/YfO+nCwujfYYWT2RX7GeAQ +XuCMOd3hIZYAXZuWmukkaG7YSiPKvPQ6CrsOaJP7RCv1mPUBc5Ew4YkB3cn7m6KY +1VaWpGjWJiTBekjZbuwXy7+cnP6R6+8+QQOuFilHeACSlhLTe57tiwJeI7Yb9t1J +yfMdctHv2TNZua/f5XkUM+LhalxAWvGNPyKafDgZ0FjWo5jh6juOBOPwTl69PRWJ +tdoguS9lfE8S+h4FPpy0XrYLLBSFka/TV4XpSE3Xyjoouqq2yz+rkK4xV5y5y6oC +ge9oElwmHRyxJ8LbJJKil7QeI82Jtjj4dE7LXzUXY2kt8Hqbq80qeFJj+NooGceH +BszafA== +=6eAl +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/49D9AF6BC72A80D6691719C8AA23F5BE9C7097D4.asc b/src/base/keys/49D9AF6BC72A80D6691719C8AA23F5BE9C7097D4.asc new file mode 100644 index 00000000..48c35e44 --- /dev/null +++ b/src/base/keys/49D9AF6BC72A80D6691719C8AA23F5BE9C7097D4.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaEXt2RYJKwYBBAHaRw8BAQdAHBr7rD1XPMzuHI9Fadnwln7ce2L1N7kY87Za +RzszU4+0HlZvbGtlciBEdXNjaCA8ZWRvcmlhbkBwaHAubmV0PoiTBBMWCgA7FiEE +Sdmva8cqgNZpFxnIqiP1vpxwl9QFAmhF7dkCGwMFCwkIBwICIgIGFQoJCAsCBBYC +AwECHgcCF4AACgkQqiP1vpxwl9RSGgEAqQm0aRiodbsybCOIj+ifWzXzNN5LCXbX +bxHr9Y/wF4kA+wQh6SJbonHUoKZZwAdBtXOvWT5dlRVAImw6X0DTRXMGuDgEaEXt +2RIKKwYBBAGXVQEFAQEHQMCYkBd2kHAO2IVulCKKzfb37EiRylvhoxAt6ZXRWEVs +AwEIB4h4BBgWCgAgFiEESdmva8cqgNZpFxnIqiP1vpxwl9QFAmhF7dkCGwwACgkQ +qiP1vpxwl9TaZwEA0RIM+Od5FrQtnLXbcCMP+lyGVArCvGDqy9fvm1xEHTIBAJDg +RhaTdv8ETdMd2c2cc82wU0EqTRRS4Nr2S2CSW+QJ +=YcDS +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/5CFF17B64DC1C244F5D0EAC3E43535E2EB19010E.asc b/src/base/keys/5CFF17B64DC1C244F5D0EAC3E43535E2EB19010E.asc new file mode 100644 index 00000000..fd338181 --- /dev/null +++ b/src/base/keys/5CFF17B64DC1C244F5D0EAC3E43535E2EB19010E.asc @@ -0,0 +1,53 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +Ex9htNV+pASz0zxfYBCAAHvpY8itvt4a7fLd +=yx8c +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/9D7F99A0CB8F05C8A6958D6256A97AF7600A39A6.asc b/src/base/keys/9D7F99A0CB8F05C8A6958D6256A97AF7600A39A6.asc new file mode 100644 index 00000000..e7e68fc5 --- /dev/null +++ b/src/base/keys/9D7F99A0CB8F05C8A6958D6256A97AF7600A39A6.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZiANXBYJKwYBBAHaRw8BAQdARP8nnoCEXdWhbte9/J1LeQZVMQ3vPM7ADToq +ZGNvXDe0JkNhbHZpbiBCdWNrbGV5IChQSFApIDxjYWx2aW5iQHBocC5uZXQ+iJME +ExYKADsWIQSdf5mgy48FyKaVjWJWqXr3YAo5pgUCZiANXAIbAwULCQgHAgIiAgYV +CgkICwIEFgIDAQIeBwIXgAAKCRBWqXr3YAo5ppvGAP9TWg8OSAuIZqsBwxd62k01 +YbG08JClqfB8/aPeoEqPcQEA++VwjC5BppelKywM6CtYtxR0tM+zt3OjjKlN400A +wga4OARmIA1cEgorBgEEAZdVAQUBAQdA+SOq91v3irC1wiZR9kesStsulsJ+liFC +01oLuT9OhwEDAQgHiHgEGBYKACAWIQSdf5mgy48FyKaVjWJWqXr3YAo5pgUCZiAN +XAIbDAAKCRBWqXr3YAo5pkbYAP9oqokC5fA5yWPdi3fXGEl4K66tkebIOa5Pbm23 +Hng4PwEAplx8F6SVD7tfszojqJDf5W4w4q0F0wknPSCLjCuldAQ= +=anm6 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/AFD8691FDAEDF03BDF6E460563F15A9B715376CA.asc b/src/base/keys/AFD8691FDAEDF03BDF6E460563F15A9B715376CA.asc new file mode 100644 index 00000000..ded0a3f8 --- /dev/null +++ b/src/base/keys/AFD8691FDAEDF03BDF6E460563F15A9B715376CA.asc @@ -0,0 +1,173 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +ovhBoCTlBaU= +=0/pR +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/C28D937575603EB4ABB725861C0779DC5C0A9DE4.asc b/src/base/keys/C28D937575603EB4ABB725861C0779DC5C0A9DE4.asc new file mode 100644 index 00000000..3cb045df --- /dev/null +++ b/src/base/keys/C28D937575603EB4ABB725861C0779DC5C0A9DE4.asc @@ -0,0 +1,21 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEYHHgIBYJKwYBBAHaRw8BAQdAOm0RDYTmM6omlqoTx6Wy65agVJ4q8EPGczNn +YOrHyo20HUpha3ViIFplbGVua2EgPGJ1a2thQHBocC5uZXQ+iJMEExYIADsCGwMF +CwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTCjZN1dWA+tKu3JYYcB3ncXAqd5AUC +YHHi+QIZAQAKCRAcB3ncXAqd5MZdAP0eAbdHgjHqKYol3QaXRhErAvvLHPSEXN5c +AImF3zIICgD+NJZZMQDXd1OOKDt+YLiIqIq2ibRhOP5mZryPCGkq9QG0J0pha3Vi +IFplbGVua2EgPGpha3ViLm9wZW5zc2xAZ21haWwuY29tPoiQBBMWCAA4FiEEwo2T +dXVgPrSrtyWGHAd53FwKneQFAmBx4owCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC +F4AACgkQHAd53FwKneS0NwEAt0IRpoCN/JNwg1TIseybpmC65nSzYVyX10xe4Ji5 +0dcBAI6TYA+47z6F4IVRg2c8Vtg1xktot7b/tKn6hgdv59sLtCdKYWt1YiBaZWxl +bmthIDxqYWt1Yi56ZWxlbmthQGdtYWlsLmNvbT6IkAQTFggAOBYhBMKNk3V1YD60 +q7clhhwHedxcCp3kBQJgceAgAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJ +EBwHedxcCp3k+3MBAKuK15XyHw+sdFATFuW9vN+lXqHnYid2jRzQUoh5wG/FAQDc +BXQgXRfe72XRoxY4AE3E4onGMfzW3kbDKDVF7o4zDrg4BGBx4CASCisGAQQBl1UB +BQEBB0Bm2FjWsSDUwL5nbAhtl+rwnGLKbWiVYrwHQocgKFj6GwMBCAeIeAQYFggA +IBYhBMKNk3V1YD60q7clhhwHedxcCp3kBQJgceAgAhsMAAoJEBwHedxcCp3k8ZwA +/jLRHUBKIeRAxeGJhoqJRr2oTxrP887FuX9/ikSX21bcAQD90AB2fu6dpAkOFyZz +X9UdE7QxtBhHVWRx3DRr4P/cDg== +=M/T3 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/D95C03BC702BE9515344AE3374E44BC9067701A5.asc b/src/base/keys/D95C03BC702BE9515344AE3374E44BC9067701A5.asc new file mode 100644 index 00000000..6a787241 --- /dev/null +++ b/src/base/keys/D95C03BC702BE9515344AE3374E44BC9067701A5.asc @@ -0,0 +1,29 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGhF6iYBEADYZivyGXqwODqy/A8CZnH+nZ2hQE9P7PVwUxsKHuSUIbsE78XN +akcakTaXFQnD8GssdDC6+OVhd1Rnua3Ihv8AHYgaHXTJsz0ObLZyFgwZ+QF8tmLa +vxFZS7I2G1SGm0erw6gcCw4eGUOtv9AoseJ7rjVmtQEfzWaf63iu4ru1CikxFO5V +aIHXnc64yjfNPNrsrU6M6MTXXjDUx1f5C/gOfy3f7DCjXgaoyfX29w/GJ9XgLZfp +GnxLnogbZvLE+txpiR0ExiaxMdqhxJSR+hBBfVETw74lc6IJOS7NfTTFFRXmp3Fc +5eURwAEW43VgpvcYgy+Nj3FM066Yx+GUa57Q9RT2yZUNozGhq7R3+y9y666ZTQAz +4uIQBVJuQqxODcqqBZ3/DEvFGpZRubZQCV37a1GsKtmTo4HCzmtrlfjZ1GGb+fPv +As6d4IVUMfuqxP5u6D6o70eUtsA7DnQbttVxgYj8oKQgSH+Hnn5sHK3HFOCuJEtZ +POxwDfIqZKo2ygwqpM6gA0oRTGJZ8k7LPoalrqRTpBvMLPDvMh4jw31YEwtUDiOi +FaSGd5ZS4B+k+i4vcRN4rayCLyeaV8Ly46LLms4The92VWyjGmOJu9FeC3N9AJpm +0j+KfMTIDObIIIfdut33OgfDD98mQOzY1+HwHyjAcAAm1VnTAgRrYo4hlwARAQAB +tCtEYW5pZWwgU2NoZXJ6ZXIgKGZvciBQSFApIDxkYW5pZWxzQHBocC5uZXQ+iQJS +BBMBCAA8FiEE2VwDvHAr6VFTRK4zdORLyQZ3AaUFAmhF6iYDGy8EBQsJCAcCAiIC +BhUKCQgLAgQWAgMBAh4HAheAAAoJEHTkS8kGdwGldesQAMQx6JZS+y1Frf5TKb6j +f0FzZIHnH1LGgZZ5UmV8nUI4gV5rt2RNoSCDL6/55h6OGtoXWSDWbEchJJNTCAzd +ab4qoeB1wCisbKG1FxeuLuvdYaauOvUtwIl7dNKED4ablI2WAV3kUDLamRLcbw7P +D/cBzy+F501kCIG/irzKdgyhzg9zG29RS/3aBnv6o3MQDw4nFx/TJC2/wQfA37z0 +vJMRdOQrp6cngIauIxb99RGt0lTAmkdn7zqo0M9ztzzlossS1DStGAK89hJypzmr +wApQqPNQg+4N0yiiZ+9H59RoaLI5VlSOxRIKye/ObUb7orx9bPhcRg+bwWeEYVw7 +77UZHNIahWnZNchq7gJXhSLSf/RRumEqlll11BYeqkfu5erYTzmvuqnqb9HvwU6B +QnZ4j150HZo3n0nWRxvn9DuGaLUIAGKPImzi5hc8fKfXz5h3OFr7RDYeggifeuN+ +PMb8NLEqEHa0pZyKfjazO+GzrFz2AFaWbKpYU9Vbh3hHRSomkjfj/v6rWyfWlWJD +V99vJs6fuDRDTu4EGjtnkM+4Q6/TZHAQdF15MhVBREXd4e6XZyNm3tqgLWZj+Y6U +QJWPa9jmdRlPl2xlGe7NW+iS7cuUcxurIzmbShp7mPz7+QOb65choY27XGW0fPij +PnNzN9AINwYXCyHTB+Gthm31 +=Ttgk +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/keys/E60913E4DF209907D8E30D96659A97C9CF2A795A.asc b/src/base/keys/E60913E4DF209907D8E30D96659A97C9CF2A795A.asc new file mode 100644 index 00000000..2c401166 --- /dev/null +++ b/src/base/keys/E60913E4DF209907D8E30D96659A97C9CF2A795A.asc @@ -0,0 +1,56 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +i6CE+Q== +=p+BW +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/base/upstream/Dockerfile-linux.template b/src/base/upstream/Dockerfile-linux.template new file mode 100644 index 00000000..ed1925de --- /dev/null +++ b/src/base/upstream/Dockerfile-linux.template @@ -0,0 +1,598 @@ +{{ + def version_id: + # https://www.php.net/phpversion + # $version_id = $major_version * 10000 + $minor_version * 100 + $release_version; + sub("[a-zA-Z].*$"; "") + | split(".") + | ( + (.[0] // 0 | tonumber) * 10000 + + (.[1] // 0 | tonumber) * 100 + + (.[2] // 0 | tonumber) + ) + ; + def is_alpine: + env.from | startswith("alpine") + ; + def rcVersion: + env.version | rtrimstr("-rc") + ; + def clean_apt: + # TODO once bookworm is EOL, remove this and just hard-code "apt-get dist-clean" instead + if env.from | contains("bookworm") then + "rm -rf /var/lib/apt/lists/*" + else "apt-get dist-clean" end + ; + def need_patch_18743: + # https://github.com/docker-library/php/pull/1580 + # https://github.com/php/php-src/issues/18743 "Incompatibility in Inline TLS Assembly on Alpine 3.22 with zend_jit_ir.c" + env.variant == "zts" + and is_alpine + and IN(rcVersion; "8.2") + ; + def install_pear_pecl: + # https://github.com/docker-library/php/issues/1554 + IN(rcVersion; "8.2", "8.3", "8.4", "8.5") +-}} +FROM {{ env.from }} + +{{ if is_alpine then "" else ( -}} +# prevent Debian's PHP packages from being installed +# https://github.com/docker-library/php/pull/542 +RUN set -eux; \ + { \ + echo 'Package: php*'; \ + echo 'Pin: release *'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/no-debian-php + +{{ ) end -}} +# dependencies required for running "phpize" +{{ if is_alpine then ( -}} +# these get automatically installed and removed by "docker-php-ext-*" (unless they're already installed) +{{ ) else ( -}} +# (see persistent deps below) +{{ ) end -}} +ENV PHPIZE_DEPS \ + autoconf \ + dpkg-dev {{ if is_alpine then "dpkg " else "" end }}\ + file \ + g++ \ + gcc \ + libc-dev \ + make \ + {{ if is_alpine then "pkgconf" else "pkg-config" end }} \ + re2c + +# persistent / runtime deps +{{ if is_alpine then ( -}} +RUN apk add --no-cache \ + ca-certificates \ + curl \ + openssl \ + tar \ + xz + +# ensure www-data user exists +RUN set -eux; \ + adduser -u 82 -D -S -G www-data www-data +# 82 is the standard uid/gid for "www-data" in Alpine +# https://git.alpinelinux.org/aports/tree/main/apache2/apache2.pre-install?h=3.14-stable +# https://git.alpinelinux.org/aports/tree/main/lighttpd/lighttpd.pre-install?h=3.14-stable +# https://git.alpinelinux.org/aports/tree/main/nginx/nginx.pre-install?h=3.14-stable +{{ ) else ( -}} +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + $PHPIZE_DEPS \ + ca-certificates \ + curl \ + xz-utils \ + ; \ + {{ clean_apt }} +{{ ) end -}} + +ENV PHP_INI_DIR /usr/local/etc/php +RUN set -eux; \ + mkdir -p "$PHP_INI_DIR/conf.d"; \ +# allow running as an arbitrary user (https://github.com/docker-library/php/issues/743) + [ ! -d /var/www/html ]; \ + mkdir -p /var/www/html; \ + chown www-data:www-data /var/www/html; \ + chmod 1777 /var/www/html + +{{ if env.variant == "apache" then ( -}} +ENV APACHE_CONFDIR /etc/apache2 +ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars + +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends apache2; \ + {{ clean_apt }}; \ + \ +# generically convert lines like +# export APACHE_RUN_USER=www-data +# into +# : ${APACHE_RUN_USER:=www-data} +# export APACHE_RUN_USER +# so that they can be overridden at runtime ("-e APACHE_RUN_USER=...") + sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \ + \ +# setup directories and permissions + . "$APACHE_ENVVARS"; \ + for dir in \ + "$APACHE_LOCK_DIR" \ + "$APACHE_RUN_DIR" \ + "$APACHE_LOG_DIR" \ +# https://salsa.debian.org/apache-team/apache2/-/commit/b97ca8714890ead1ba6c095699dde752e8433205 + "$APACHE_RUN_DIR/socks" \ + ; do \ + rm -rvf "$dir"; \ + mkdir -p "$dir"; \ + chown "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$dir"; \ +# allow running as an arbitrary user (https://github.com/docker-library/php/issues/743) + chmod 1777 "$dir"; \ + done; \ + \ +# delete the "index.html" that installing Apache drops in here + rm -rvf /var/www/html/*; \ + \ +# logs should go to stdout / stderr + ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \ + ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \ + ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \ + chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR" + +# Apache + PHP requires preforking Apache for best results +RUN a2dismod mpm_event && a2enmod mpm_prefork + +# PHP files should be handled by PHP, and should be preferred over any other file type +RUN { \ + echo ''; \ + echo '\tSetHandler application/x-httpd-php'; \ + echo ''; \ + echo; \ + echo 'DirectoryIndex disabled'; \ + echo 'DirectoryIndex index.php index.html'; \ + echo; \ + echo ''; \ + echo '\tOptions -Indexes'; \ + echo '\tAllowOverride All'; \ + echo ''; \ + } | tee "$APACHE_CONFDIR/conf-available/docker-php.conf" \ + && a2enconf docker-php + +{{ ) else "" end -}} +# Apply stack smash protection to functions using local buffers and alloca() +# Make PHP's main executable position-independent (improves ASLR security mechanism, and has no performance impact on x86_64) +# Enable optimization (-O2) +# Enable linker optimization (this sorts the hash buckets to improve cache locality, and is non-default) +# https://github.com/docker-library/php/issues/272 +# -D_LARGEFILE_SOURCE and -D_FILE_OFFSET_BITS=64 (https://www.php.net/manual/en/intro.filesystem.php) +ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" +ENV PHP_CPPFLAGS="$PHP_CFLAGS" +ENV PHP_LDFLAGS="-Wl,-O1 -pie" + +ENV GPG_KEYS {{ + { + # https://www.php.net/gpg-keys.php + # https://www.php.net/downloads.php + + "8.6": [ + # https://wiki.php.net/todo/php86#release_managers + # https://www.php.net/gpg-keys.php#gpg-8.6 + "D95C 03BC 702B E951 5344 AE33 74E4 4BC9 0677 01A5", # daniels + "0168 95DE 9A47 5111 D537 A6E6 9134 FF30 BC5A 99B5", # mbeccati + "5CFF 17B6 4DC1 C244 F5D0 EAC3 E435 35E2 EB19 010E" # svpernova09 + ], + + "8.5": [ + # https://wiki.php.net/todo/php85#release_managers + # https://www.php.net/gpg-keys.php#gpg-8.5 + "1198 C011 7593 497A 5EC5 C199 286A F1F9 8974 69DC", # pierrick + "49D9 AF6B C72A 80D6 6917 19C8 AA23 F5BE 9C70 97D4", # edorian + "D95C 03BC 702B E951 5344 AE33 74E4 4BC9 0677 01A5" # daniels + ], + + "8.4": [ + # https://wiki.php.net/todo/php84#release_managers + # https://www.php.net/gpg-keys.php#gpg-8.4 + "AFD8 691F DAED F03B DF6E 4605 63F1 5A9B 7153 76CA", # ericmann + "9D7F 99A0 CB8F 05C8 A695 8D62 56A9 7AF7 600A 39A6", # calvinb + "0616 E93D 95AF 4712 43E2 6761 7704 26E1 7EBB B3DD" # saki + ], + + "8.3": [ + # https://wiki.php.net/todo/php83#release_managers + # https://www.php.net/gpg-keys.php#gpg-8.3 + "1198 C011 7593 497A 5EC5 C199 286A F1F9 8974 69DC", # pierrick + "C28D937575603EB4ABB725861C0779DC5C0A9DE4", # bukka + "AFD8 691F DAED F03B DF6E 4605 63F1 5A9B 7153 76CA" # ericmann + ], + + "8.2": [ + # https://wiki.php.net/todo/php82#release_managers + # https://www.php.net/gpg-keys.php#gpg-8.2 + "39B6 4134 3D8C 104B 2B14 6DC3 F9C3 9DC0 B969 8544", # ramsey + "E609 13E4 DF20 9907 D8E3 0D96 659A 97C9 CF2A 795A", # sergey + "1198 C011 7593 497A 5EC5 C199 286A F1F9 8974 69DC" # pierrick + ], + }[rcVersion] // error("missing GPG keys for " + rcVersion) + | map(gsub(" "; "")) + | join(" ") +}} + +ENV PHP_VERSION {{ .version }} +ENV PHP_URL="{{ .url }}" PHP_ASC_URL="{{ .ascUrl // "" }}" +ENV PHP_SHA256="{{ .sha256 // "" }}" + +RUN set -eux; \ + \ +{{ if is_alpine then ( -}} + apk add --no-cache --virtual .fetch-deps gnupg; \ +{{ ) else ( -}} + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends gnupg; \ + {{ clean_apt }}; \ +{{ ) end -}} + \ + mkdir -p /usr/src; \ + cd /usr/src; \ + \ + curl -fsSL -o php.tar.xz "$PHP_URL"; \ + \ + if [ -n "$PHP_SHA256" ]; then \ + echo "$PHP_SHA256 *php.tar.xz" | sha256sum -c -; \ + fi; \ + \ + curl -fsSL -o php.tar.xz.asc "$PHP_ASC_URL"; \ + export GNUPGHOME="$(mktemp -d)"; \ + for key in $GPG_KEYS; do \ + gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key"; \ + done; \ + gpg --batch --verify php.tar.xz.asc php.tar.xz; \ + gpgconf --kill all; \ + rm -rf "$GNUPGHOME"; \ + \ +{{ if is_alpine then ( -}} + apk del --no-network .fetch-deps +{{ ) else ( -}} + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark > /dev/null; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false +{{ ) end -}} + +COPY docker-php-source /usr/local/bin/ + +RUN set -eux; \ +{{ if is_alpine then ( -}} + apk add --no-cache --virtual .build-deps \ +{{ ) else ( -}} + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ +{{ ) end -}} +{{ + [ + [ if is_alpine then + # alpine packages + "$PHPIZE_DEPS", + "argon2-dev", + "coreutils", + "curl-dev", + "gnu-libiconv-dev", # https://www.php.net/manual/en/intro.iconv.php "it'd be a good idea to install the GNU libiconv library" + "libsodium-dev", + "libxml2-dev", + "openssl-dev", + # https://github.com/docker-library/php/pull/1580 (18743) + if need_patch_18743 then "patch", "patchutils" else empty end, + "readline-dev", + "sqlite-dev", + # https://github.com/docker-library/php/issues/888 + "linux-headers", + # oniguruma is part of mbstring in php 7.4+ + if install_pear_pecl then "oniguruma-dev" else empty end + else + # debian packages + if env.variant == "apache" then "apache2-dev" else empty end, + "libargon2-dev", + "libcurl4-openssl-dev", + "libreadline-dev", + "libsodium-dev", + "libsqlite3-dev", + "libssl-dev", + "libxml2-dev", + "zlib1g-dev", + # oniguruma is part of mbstring in php 7.4+ + if install_pear_pecl then "libonig-dev" else empty end + end ] | sort[] | ( +-}} + {{ . }} \ +{{ + ) + ] | add +-}} + ; \ + \ +{{ if is_alpine then ( -}} +# make sure musl's iconv doesn't get used (https://www.php.net/manual/en/intro.iconv.php) + rm -vf /usr/include/iconv.h; \ + \ +{{ ) else "" end -}} + export \ + CFLAGS="$PHP_CFLAGS" \ + CPPFLAGS="$PHP_CPPFLAGS" \ + LDFLAGS="$PHP_LDFLAGS" \ +# https://github.com/php/php-src/blob/d6299206dd828382753453befd1b915491b741c6/configure.ac#L1496-L1511 + PHP_BUILD_PROVIDER='https://github.com/docker-library/php' \ + PHP_UNAME='Linux - Docker' \ + ; \ + docker-php-source extract; \ + cd /usr/src/php; \ +{{ if need_patch_18743 then ( -}} + # https://github.com/php/php-src/issues/18743 "Incompatibility in Inline TLS Assembly on Alpine 3.22 with zend_jit_ir.c" + # https://github.com/docker-library/php/pull/1580 + curl -fL 'https://github.com/php/php-src/commit/b3c8afe272a6919248986c703c2e1defc73ff707.patch?full_index=1' -o 18743.patch; \ + echo 'b334f73434c9732a4b27a42eb5d417e10df842e854c02a3e753b2479f8978bf5 *18743.patch' | sha256sum -c -; \ + filterdiff -x '*/NEWS' 18743.patch | patch -p1; \ + rm 18743.patch; \ +{{ ) else "" end -}} + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ +{{ if is_alpine then "" else ( -}} + debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ +# https://bugs.php.net/bug.php?id=74125 + if [ ! -d /usr/include/curl ]; then \ + ln -sT "/usr/include/$debMultiarch/curl" /usr/local/include/curl; \ + fi; \ +{{ ) end -}} + case "$gnuArch" in \ + 'i686-linux-'*) \ + # php zend jit assumes SSE registers, xmm0-7, are available (and "Pentium4"/SSE2 is a reasonable baseline) + # https://github.com/php/php-src/pull/22832 + # so, allow it and use SSE/SSE2 floating-point unit instead of the legacy stack + export \ + CFLAGS="$CFLAGS -msse2 -mfpmath=sse" \ + CPPFLAGS="$CPPFLAGS -msse2 -mfpmath=sse" \ + ; \ + ;; \ + esac; \ + test "$PHP_INI_DIR" != "${PHP_INI_DIR%/php}"; \ + ./configure \ + --build="$gnuArch" \ + --sysconfdir="${PHP_INI_DIR%/php}" \ + --with-config-file-path="$PHP_INI_DIR" \ + --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \ + \ +# make sure invalid --configure-flags are fatal errors instead of just warnings + --enable-option-checking=fatal \ + \ +# https://github.com/docker-library/php/issues/439 + --with-mhash \ + \ +{{ if rcVersion | IN("8.2", "8.3", "8.4", "8.5") then ( -}} +# https://github.com/docker-library/php/issues/822 + --with-pic \ + \ +{{ ) elif env.variant == "apache" then ( -}} +# https://github.com/docker-library/php/issues/822 +# https://github.com/php/php-src/pull/21067/changes#diff-4b9db5b77bcd56aa33b310dc44c8bc99c729c9abb195df2e98e6985b81289ae8R130 + --enable-pic \ + \ +{{ ) else ( -}} +# no --enable-pic; forcing PIC also removes -fpie from executable objects, preventing local-exec TLS: https://github.com/docker-library/php/pull/1692 +{{ ) end -}} +{{ if install_pear_pecl then ( -}} +# --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) + --enable-mbstring \ +{{ ) else "" end -}} +# --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) + --enable-mysqlnd \ +# https://wiki.php.net/rfc/argon2_password_hash + --with-password-argon2 \ +# https://wiki.php.net/rfc/libsodium + --with-sodium=shared \ +# always build against system sqlite3 (https://github.com/php/php-src/commit/6083a387a81dbbd66d6316a3a12a63f06d5f7109) + --with-pdo-sqlite=/usr \ + --with-sqlite3=/usr \ + \ + --with-curl \ + --with-iconv{{ if is_alpine then "=/usr" else "" end }} \ + --with-openssl \ + --with-readline \ + --with-zlib \ + \ +{{ if env.variant | IN("cli", "zts") then ( -}} +# https://github.com/docker-library/php/pull/1259 + --enable-phpdbg \ + --enable-phpdbg-readline \ +{{ ) else ( -}} +# https://github.com/bwoebi/phpdbg-docs/issues/1#issuecomment-163872806 ("phpdbg is primarily a CLI debugger, and is not suitable for debugging an fpm stack.") + --disable-phpdbg \ +{{ ) end -}} + \ +{{ if install_pear_pecl then ( -}} +# in PHP 7.4+, the pecl/pear installers are officially deprecated (requiring an explicit "--with-pear") + --with-pear \ + \ +{{ ) else "" end -}} +{{ if rcVersion | IN("8.2") then ( -}} +# bundled pcre does not support JIT on riscv64 until 10.41 (php 8.3+) +# https://github.com/PCRE2Project/pcre2/commits/pcre2-10.41/src/sljit/sljitNativeRISCV_64.c +# https://github.com/php/php-src/tree/php-8.3.0/ext/pcre/pcre2lib +{{ if is_alpine then ( -}} + $(test "$gnuArch" = 'riscv64-linux-musl' && echo '--without-pcre-jit') \ +{{ ) else ( -}} + $(test "$gnuArch" = 'riscv64-linux-gnu' && echo '--without-pcre-jit') \ +{{ ) end -}} +{{ ) else "" end -}} +{{ if is_alpine then "" else ( -}} + --with-libdir="lib/$debMultiarch" \ +{{ ) end -}} +{{ # https://github.com/docker-library/php/issues/280 -}} +{{ if env.variant | IN("cli", "zts") then "" else ( -}} + \ + --disable-cgi \ +{{ ) end -}} +{{ # zts + alpine special cased for embed (otherwise zts is effectively cli): https://github.com/docker-library/php/pull/1342 -}} +{{ if env.variant == "zts" or (env.variant == "cli" and ((rcVersion | version_id) >= 80600 or (is_alpine | not))) then ( -}} + \ +# https://github.com/docker-library/php/pull/939#issuecomment-730501748 + --enable-embed \ +{{ ) else "" end -}} +{{ if env.variant == "apache" then ( -}} + \ + --with-apxs2 \ +{{ ) elif env.variant == "fpm" then ( -}} + \ + --enable-fpm \ + --with-fpm-user=www-data \ + --with-fpm-group=www-data \ +{{ ) else "" end -}} +{{ if env.variant == "zts" or (rcVersion | version_id) >= 80600 then ( -}} + \ + --enable-zts \ +# https://externals.io/message/118859 + --disable-zend-signals \ +{{ if rcVersion | IN("8.2") then ( -}} + --enable-zend-max-execution-timers \ +{{ ) else "" end -}} +{{ ) else "" end -}} +{{ if env.DOCKER_PHP_ENABLE_DEBUG then ( -}} +{{ # DOCKER_PHP_ENABLE_DEBUG is not used or supported by official-images; this is for users who want to build their own php image with debug enabled -}} +{{ # example usage to regenerate Dockerfiles with debug enabled: "DOCKER_PHP_ENABLE_DEBUG=1 ./apply-templates" -}} + --enable-debug \ +{{ ) else "" end -}} + ; \ + make -j "$(nproc)"; \ + find -type f -name '*.a' -delete; \ + make install; \ +{{ if env.DOCKER_PHP_ENABLE_DEBUG then "" else ( -}} +{{ # DOCKER_PHP_ENABLE_DEBUG is not used by official-images -}} + find \ + /usr/local \ + -type f \ + -perm '/0111' \ + -exec sh -euxc ' \ + strip --strip-all "$@" || : \ + ' -- '{}' + \ + ; \ +{{ ) end -}} + make clean; \ + \ +# https://github.com/docker-library/php/issues/692 (copy default example "php.ini" files somewhere easily discoverable) + cp -v php.ini-* "$PHP_INI_DIR/"; \ + \ + cd /; \ + docker-php-source delete; \ + \ +{{ if is_alpine then ( -}} + runDeps="$( \ + scanelf --needed --nobanner --format '%n#p' --recursive /usr/local \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + )"; \ + apk add --no-cache $runDeps; \ + \ + apk del --no-network .build-deps; \ +{{ ) else ( -}} +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ + find /usr/local -type f -executable -exec ldd '{}' ';' \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \ + | sort -u \ + | xargs -rt dpkg-query --search \ +# https://manpages.debian.org/trixie/dpkg/dpkg-query.1.en.html#S (we ignore diversions and it'll be really unusual for more than one package to provide any given .so file) + | awk 'sub(":$", "", $1) { print $1 }' \ + | sort -u \ + | xargs -r apt-mark manual \ + ; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + {{ clean_apt }}; \ +{{ ) end -}} + \ +{{ if install_pear_pecl then ( -}} +# update pecl channel definitions https://github.com/docker-library/php/issues/443 + pecl update-channels; \ + rm -rf /tmp/pear ~/.pearrc; \ + \ +{{ ) else "" end -}} +# smoke test + php --version + +COPY docker-php-ext-* docker-php-entrypoint /usr/local/bin/ + +{{ if IN(rcVersion; "8.2", "8.3", "8.4") then ( -}} +# enable OPcache by default (https://wiki.php.net/rfc/make_opcache_required) +RUN docker-php-ext-enable opcache + +{{ ) else "" end -}} +# sodium was built as a shared module (so that it can be replaced later if so desired), so let's enable it too (https://github.com/docker-library/php/issues/598) +RUN docker-php-ext-enable sodium + +ENTRYPOINT ["docker-php-entrypoint"] +{{ if env.variant == "apache" then ( -}} +# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop +STOPSIGNAL SIGWINCH + +COPY apache2-foreground /usr/local/bin/ +WORKDIR /var/www/html + +EXPOSE 80 +{{ ) elif env.variant == "fpm" then ( -}} +WORKDIR /var/www/html + +RUN set -eux; \ + cd "${PHP_INI_DIR%/php}"; \ + \ + cp -v php-fpm.conf.default php-fpm.conf; \ + cp -v php-fpm.d/www.conf.default php-fpm.d/www.conf; \ + \ + # comment out localhost-only listen address + grep -E '^listen = 127.0.0.1:9000' php-fpm.d/www.conf; \ + sed -ri 's/^(listen = 127.0.0.1:9000)/;\1/' php-fpm.d/www.conf; \ + grep -E '^;listen = 127.0.0.1:9000' php-fpm.d/www.conf; \ + \ + { \ + echo '[global]'; \ + echo 'error_log = /proc/self/fd/2'; \ + echo; echo '; https://github.com/docker-library/php/pull/725#issuecomment-443540114'; echo 'log_limit = 8192'; \ + echo; \ + echo '[www]'; \ + echo '; php-fpm closes STDOUT on startup, so sending logs to /proc/self/fd/1 does not work.'; \ + echo '; https://bugs.php.net/bug.php?id=73886'; \ + echo 'access.log = /proc/self/fd/2'; \ + echo; \ + echo 'clear_env = no'; \ + echo; \ + echo '; Ensure worker stdout and stderr are sent to the main error log.'; \ + echo 'catch_workers_output = yes'; \ + echo 'decorate_workers_output = no'; \ + echo; \ + echo '; default listen address for easy override in later php-fpm.d/*.conf files'; \ + echo 'listen = 9000'; \ + } | tee php-fpm.d/docker.conf; \ + { \ + echo '[global]'; \ + echo 'daemonize = no'; \ +{{ if rcVersion | IN("8.2", "8.3", "8.4", "8.5") then ( -}} + echo; \ + echo '; the [www] ini section below is for backwards compatibility and will be removed in 8.6+'; \ + echo '[www]'; \ +{{ ) else "" end -}} + } | tee php-fpm.d/zz-docker.conf; \ + mkdir -p "$PHP_INI_DIR/conf.d"; \ + { \ + echo '; https://github.com/docker-library/php/issues/878#issuecomment-938595965'; \ + echo 'fastcgi.logging = Off'; \ + } > "$PHP_INI_DIR/conf.d/docker-fpm.ini" + +# Override stop signal to stop process gracefully +# https://github.com/php/php-src/blob/17baa87faddc2550def3ae7314236826bc1b1398/sapi/fpm/php-fpm.8.in#L163 +STOPSIGNAL SIGQUIT + +EXPOSE 9000 +{{ ) else "" end -}} +CMD {{ env.cmd }} diff --git a/src/base/upstream/LICENSE b/src/base/upstream/LICENSE new file mode 100644 index 00000000..fbab368d --- /dev/null +++ b/src/base/upstream/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014 Docker, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/base/upstream/apply-templates.sh b/src/base/upstream/apply-templates.sh new file mode 100755 index 00000000..a644fe5a --- /dev/null +++ b/src/base/upstream/apply-templates.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +[ -f versions.json ] # run "versions.sh" first + +jqt='.jq-template.awk' +if [ -n "${BASHBREW_SCRIPTS:-}" ]; then + jqt="$BASHBREW_SCRIPTS/jq-template.awk" +elif [ "$BASH_SOURCE" -nt "$jqt" ]; then + # https://github.com/docker-library/bashbrew/blob/master/scripts/jq-template.awk + wget -qO "$jqt" 'https://github.com/docker-library/bashbrew/raw/9f6a35772ac863a0241f147c820354e4008edf38/scripts/jq-template.awk' +fi + +if [ "$#" -eq 0 ]; then + versions="$(jq -r 'keys | map(@sh) | join(" ")' versions.json)" + eval "set -- $versions" +fi + +generated_warning() { + cat <<-EOH + # + # NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" + # + # PLEASE DO NOT EDIT IT DIRECTLY. + # + + EOH +} + +for version; do + export version + + rm -rf "$version" + + if jq -e '.[env.version] | not' versions.json > /dev/null; then + echo "deleting $version ..." + continue + fi + + variants="$(jq -r '.[env.version].variants | map(@sh) | join(" ")' versions.json)" + eval "variants=( $variants )" + + for dir in "${variants[@]}"; do + suite="$(dirname "$dir")" # "buster", etc + variant="$(basename "$dir")" # "cli", etc + export suite variant + + alpineVer="${suite#alpine}" # "3.12", etc + if [ "$suite" != "$alpineVer" ]; then + from="alpine:$alpineVer" + else + from="debian:$suite-slim" + fi + export from alpineVer + + case "$variant" in + apache) cmd='["apache2-foreground"]' ;; + fpm) cmd='["php-fpm"]' ;; + *) cmd='["php", "-a"]' ;; + esac + export cmd + + echo "processing $version/$dir ..." + mkdir -p "$version/$dir" + + { + generated_warning + gawk -f "$jqt" 'Dockerfile-linux.template' + } > "$version/$dir/Dockerfile" + + cp -a \ + docker-php-entrypoint \ + docker-php-ext-* \ + docker-php-source \ + "$version/$dir/" + if [ "$variant" = 'apache' ]; then + cp -a apache2-foreground "$version/$dir/" + fi + + cmd="$(jq <<<"$cmd" -r '.[0]')" + if [ "$cmd" != 'php' ]; then + sed -i -e 's! php ! '"$cmd"' !g' "$version/$dir/docker-php-entrypoint" + fi + done +done diff --git a/src/base/upstream/bashbrew/LICENSE b/src/base/upstream/bashbrew/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/src/base/upstream/bashbrew/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/base/upstream/bashbrew/jq-template.awk b/src/base/upstream/bashbrew/jq-template.awk new file mode 100644 index 00000000..942d4b02 --- /dev/null +++ b/src/base/upstream/bashbrew/jq-template.awk @@ -0,0 +1,135 @@ +# this script assumes gawk! (busybox "awk" is not quite sufficient) + +# see https://github.com/docker-library/php or https://github.com/docker-library/golang for examples of usage ("apply-templates.sh") + +# escape an arbitrary string for passing back to jq as program input +function jq_escape(str) { + gsub(/\\/, "\\\\", str) + gsub(/\n/, "\\n", str) + gsub(/\r/, "\\r", str) + gsub(/\t/, "\\t", str) + gsub(/"/, "\\\"", str) + return "\"" str "\"" +} + +# return the number of times needle appears in haystack +function num(haystack, needle, # parameters + ret, i ) # locals +{ + ret = 0 + while (i = index(haystack, needle)) { + ret++ + haystack = substr(haystack, i + length(needle)) + } + return ret +} + +BEGIN { + jq_expr_defs = "" + jq_expr = "" + agg_jq = "" + agg_text = "" + + OPEN = "{{" + CLOSE = "}}" + CLOSE_EAT_EOL = "-" CLOSE ORS +} + +function trim(str) { + sub(/^[[:space:]]+/, "", str) + sub(/[[:space:]]+$/, "", str) + return str +} +function append(str) { + if (jq_expr && jq_expr !~ /\($/ && str !~ /^\)/) { + jq_expr = jq_expr "\n+ " + } else if (jq_expr) { + jq_expr = jq_expr "\n" + } + jq_expr = jq_expr str +} +function append_string(str) { + if (!str) return + str = jq_escape(str) + append(str) +} +function append_jq(expr) { + if (!expr) return + expr = trim(expr) + if (!expr) return + if (expr ~ /^#[^\n]*$/) return # ignore pure comment lines {{ # ... -}} + if (expr ~ /^(def|include|import)[[:space:]]/) { # a few things need to go at the start of our "script" + jq_expr_defs = jq_expr_defs expr ";\n" + return + } + # if expr doesn't begin with ")" or end with "(", wrap it in parenthesis (so our addition chain works properly) + if (expr !~ /^\)/) expr = "(" expr + if (expr !~ /\($/) expr = expr ")" + append(expr) +} + +{ + line = $0 ORS + + i = 0 + if (agg_jq || (i = index(line, OPEN))) { + if (i) { + agg_text = agg_text substr(line, 1, i - 1) + line = substr(line, i) + } + append_string(agg_text) + agg_text = "" + + agg_jq = agg_jq line + line = "" + + if (num(agg_jq, OPEN) > num(agg_jq, CLOSE)) { + next + } + + while (i = index(agg_jq, OPEN)) { + line = substr(agg_jq, 1, i - 1) + agg_jq = substr(agg_jq, i + length(OPEN)) + if (i = index(agg_jq, CLOSE_EAT_EOL)) { + expr = substr(agg_jq, 1, i - 1) + agg_jq = substr(agg_jq, i + length(CLOSE_EAT_EOL)) + } + else { + i = index(agg_jq, CLOSE) + expr = substr(agg_jq, 1, i - 1) + agg_jq = substr(agg_jq, i + length(CLOSE)) + } + append_string(line) + append_jq(expr) + } + line = agg_jq + agg_jq = "" + } + + if (line) { + agg_text = agg_text line + } +} + +END { + append_string(agg_text) + agg_text = "" + + append_jq(agg_jq) + agg_jq = "" + + jq_expr = "if env.version then .[env.version] else . end | (\n" jq_expr "\n)" + jq_expr = jq_expr_defs jq_expr + + if (ENVIRON["DEBUG"]) { + print jq_expr > "/dev/stderr" + } + + prog = "jq --join-output --from-file /dev/stdin versions.json" + printf "%s", jq_expr | prog + + e = close(prog) + if (e != 0) { + exit(e) + } +} diff --git a/src/base/upstream/docker-php-entrypoint b/src/base/upstream/docker-php-entrypoint new file mode 100755 index 00000000..88a016c1 --- /dev/null +++ b/src/base/upstream/docker-php-entrypoint @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +# first arg is `-f` or `--some-option` +if [ "${1#-}" != "$1" ]; then + set -- php "$@" +fi + +exec "$@" diff --git a/src/base/upstream/docker-php-ext-configure b/src/base/upstream/docker-php-ext-configure new file mode 100755 index 00000000..34fc1337 --- /dev/null +++ b/src/base/upstream/docker-php-ext-configure @@ -0,0 +1,69 @@ +#!/bin/sh +set -e + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: ${CFLAGS:=$PHP_CFLAGS} +: ${CPPFLAGS:=$PHP_CPPFLAGS} +: ${LDFLAGS:=$PHP_LDFLAGS} +export CFLAGS CPPFLAGS LDFLAGS + +srcExists= +if [ -d /usr/src/php ]; then + srcExists=1 +fi +docker-php-source extract +if [ -z "$srcExists" ]; then + touch /usr/src/php/.docker-delete-me +fi + +cd /usr/src/php/ext + +usage() { + echo "usage: $0 ext-name [configure flags]" + echo " ie: $0 gd --with-jpeg-dir=/usr/local/something" + echo + echo 'Possible values for ext-name:' + find . \ + -mindepth 2 \ + -maxdepth 2 \ + -type f \ + -name 'config.m4' \ + | xargs -n1 dirname \ + | xargs -n1 basename \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +ext="$1" +if [ -z "$ext" ] || [ ! -d "$ext" ]; then + usage >&2 + exit 1 +fi +shift + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +if [ "$pm" = 'apk' ]; then + if \ + [ -n "$PHPIZE_DEPS" ] \ + && ! apk info --installed .phpize-deps > /dev/null \ + && ! apk info --installed .phpize-deps-configure > /dev/null \ + ; then + apk add --no-cache --virtual .phpize-deps-configure $PHPIZE_DEPS + fi +fi + +if command -v dpkg-architecture > /dev/null; then + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" + set -- --build="$gnuArch" "$@" +fi + +cd "$ext" +phpize +./configure --enable-option-checking=fatal "$@" diff --git a/src/base/upstream/docker-php-ext-enable b/src/base/upstream/docker-php-ext-enable new file mode 100755 index 00000000..41d20bbe --- /dev/null +++ b/src/base/upstream/docker-php-ext-enable @@ -0,0 +1,121 @@ +#!/bin/sh +set -e + +extDir="$(php -d 'display_errors=stderr' -r 'echo ini_get("extension_dir");')" +cd "$extDir" + +usage() { + echo "usage: $0 [options] module-name [module-name ...]" + echo " ie: $0 gd mysqli" + echo " $0 pdo pdo_mysql" + echo " $0 --ini-name 0-apc.ini apcu apc" + echo + echo 'Possible values for module-name:' + find -maxdepth 1 \ + -type f \ + -name '*.so' \ + -exec basename '{}' ';' \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +opts="$(getopt -o 'h?' --long 'help,ini-name:' -- "$@" || { usage >&2 && false; })" +eval set -- "$opts" + +iniName= +while true; do + flag="$1" + shift + case "$flag" in + --help|-h|'-?') usage && exit 0 ;; + --ini-name) iniName="$1" && shift ;; + --) break ;; + *) + { + echo "error: unknown flag: $flag" + usage + } >&2 + exit 1 + ;; + esac +done + +modules= +for module; do + if [ -z "$module" ]; then + continue + fi + if ! [ -f "$module" ] && ! [ -f "$module.so" ]; then + echo >&2 "error: '$module' does not exist" + echo >&2 + usage >&2 + exit 1 + fi + modules="$modules $module" +done + +if [ -z "$modules" ]; then + usage >&2 + exit 1 +fi + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +apkDel= +if [ "$pm" = 'apk' ]; then + if \ + [ -n "$PHPIZE_DEPS" ] \ + && ! apk info --installed .phpize-deps > /dev/null \ + && ! apk info --installed .phpize-deps-configure > /dev/null \ + ; then + apk add --no-cache --virtual '.docker-php-ext-enable-deps' binutils + apkDel='.docker-php-ext-enable-deps' + fi +fi + +for module in $modules; do + moduleFile="$module" + if [ -f "$module.so" ] && ! [ -f "$module" ]; then + moduleFile="$module.so" + fi + if readelf --wide --syms "$moduleFile" | grep -q ' zend_extension_entry$'; then + # https://wiki.php.net/internals/extensions#loading_zend_extensions + line="zend_extension=$module" + else + line="extension=$module" + fi + + ext="$(basename "$module")" + ext="${ext%.*}" + if php -d 'display_errors=stderr' -r 'exit(extension_loaded("'"$ext"'") ? 0 : 1);'; then + # this isn't perfect, but it's better than nothing + # (for example, 'opcache.so' presents inside PHP as 'Zend OPcache', not 'opcache') + echo >&2 + echo >&2 "warning: $ext ($module) is already loaded!" + echo >&2 + continue + fi + + case "$iniName" in + /*) + # allow an absolute path + ini="$iniName" + ;; + *) + ini="$PHP_INI_DIR/conf.d/${iniName:-"docker-php-ext-$ext.ini"}" + ;; + esac + if ! grep -qFx -e "$line" -e "$line.so" "$ini" 2>/dev/null; then + echo "$line" >> "$ini" + fi +done + +if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then + apk del --no-network $apkDel +fi diff --git a/src/base/upstream/docker-php-ext-install b/src/base/upstream/docker-php-ext-install new file mode 100755 index 00000000..fbf44849 --- /dev/null +++ b/src/base/upstream/docker-php-ext-install @@ -0,0 +1,143 @@ +#!/bin/sh +set -e + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: ${CFLAGS:=$PHP_CFLAGS} +: ${CPPFLAGS:=$PHP_CPPFLAGS} +: ${LDFLAGS:=$PHP_LDFLAGS} +export CFLAGS CPPFLAGS LDFLAGS + +srcExists= +if [ -d /usr/src/php ]; then + srcExists=1 +fi +docker-php-source extract +if [ -z "$srcExists" ]; then + touch /usr/src/php/.docker-delete-me +fi + +cd /usr/src/php/ext + +usage() { + echo "usage: $0 [-jN] [--ini-name file.ini] ext-name [ext-name ...]" + echo " ie: $0 gd mysqli" + echo " $0 pdo pdo_mysql" + echo " $0 -j5 gd mbstring mysqli pdo pdo_mysql shmop" + echo + echo 'if custom ./configure arguments are necessary, see docker-php-ext-configure' + echo + echo 'Possible values for ext-name:' + find . \ + -mindepth 2 \ + -maxdepth 2 \ + -type f \ + -name 'config.m4' \ + | xargs -n1 dirname \ + | xargs -n1 basename \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +opts="$(getopt -o 'h?j:' --long 'help,ini-name:,jobs:' -- "$@" || { usage >&2 && false; })" +eval set -- "$opts" + +j=1 +iniName= +while true; do + flag="$1" + shift + case "$flag" in + --help|-h|'-?') usage && exit 0 ;; + --ini-name) iniName="$1" && shift ;; + --jobs|-j) j="$1" && shift ;; + --) break ;; + *) + { + echo "error: unknown flag: $flag" + usage + } >&2 + exit 1 + ;; + esac +done + +exts= +for ext; do + if [ -z "$ext" ]; then + continue + fi + if [ ! -d "$ext" ]; then + echo >&2 "error: $PWD/$ext does not exist" + echo >&2 + usage >&2 + exit 1 + fi + exts="$exts $ext" +done + +if [ -z "$exts" ]; then + usage >&2 + exit 1 +fi + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +apkDel= +if [ "$pm" = 'apk' ]; then + if [ -n "$PHPIZE_DEPS" ]; then + if apk info --installed .phpize-deps-configure > /dev/null; then + apkDel='.phpize-deps-configure' + elif ! apk info --installed .phpize-deps > /dev/null; then + apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS + apkDel='.phpize-deps' + fi + fi +fi + +popDir="$PWD" +for ext in $exts; do + cd "$ext" + + [ -e Makefile ] || docker-php-ext-configure "$ext" + + make -j"$j" + + if ! php -n -d 'display_errors=stderr' -r 'exit(ZEND_DEBUG_BUILD ? 0 : 1);' > /dev/null; then + # only "strip" modules if we aren't using a debug build of PHP + # (none of our builds are debug builds, but PHP might be recompiled with "--enable-debug" configure option) + # https://github.com/docker-library/php/issues/1268 + + find modules \ + -maxdepth 1 \ + -name '*.so' \ + -exec sh -euxc ' \ + strip --strip-all "$@" || : + ' -- '{}' + + fi + + make -j"$j" install + + find modules \ + -maxdepth 1 \ + -name '*.so' \ + -exec basename '{}' '.so' ';' \ + | xargs -r docker-php-ext-enable ${iniName:+--ini-name "$iniName"} + + make -j"$j" clean + + cd "$popDir" +done + +if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then + apk del --no-network $apkDel +fi + +if [ -e /usr/src/php/.docker-delete-me ]; then + docker-php-source delete +fi diff --git a/src/base/upstream/docker-php-source b/src/base/upstream/docker-php-source new file mode 100755 index 00000000..9033d243 --- /dev/null +++ b/src/base/upstream/docker-php-source @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +dir=/usr/src/php + +usage() { + echo "usage: $0 COMMAND" + echo + echo "Manage php source tarball lifecycle." + echo + echo "Commands:" + echo " extract extract php source tarball into directory $dir if not already done." + echo " delete delete extracted php source located into $dir if not already done." + echo +} + +case "$1" in + extract) + mkdir -p "$dir" + if [ ! -f "$dir/.docker-extracted" ]; then + tar -Jxf /usr/src/php.tar.xz -C "$dir" --strip-components=1 + touch "$dir/.docker-extracted" + fi + ;; + + delete) + rm -rf "$dir" + ;; + + *) + usage + exit 1 + ;; +esac diff --git a/src/variations/cli/Dockerfile b/src/variations/cli/Dockerfile index c83b57a1..2c3ec9f5 100644 --- a/src/variations/cli/Dockerfile +++ b/src/variations/cli/Dockerfile @@ -1,11 +1,12 @@ ARG BASE_OS_VERSION='bookworm' ARG PHP_VERSION='8.5' ARG PHP_VARIATION='cli' +ARG BASE_IMAGE="php:${PHP_VERSION}-${PHP_VARIATION}-${BASE_OS_VERSION}" ########## # CLI: Main Image ########## -FROM php:${PHP_VERSION}-${PHP_VARIATION}-${BASE_OS_VERSION} +FROM ${BASE_IMAGE} ARG DEPENDENCY_PACKAGES_ALPINE="\ shadow" diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index 3f50a7b9..b6d8955a 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -20,7 +20,7 @@ RUN docker-php-serversideup-s6-install #################### # NGINX Signing Key #################### -FROM php:${PHP_VERSION}-fpm-${BASE_OS_VERSION} AS nginx-repo-config +FROM ${BASE_IMAGE} AS nginx-repo-config ARG SIGNING_KEY_URL="https://nginx.org/keys/nginx_signing.key" ARG SIGNING_FINGERPRINT="573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62" diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index e0e9ec49..58678c04 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -1,11 +1,12 @@ ARG BASE_OS_VERSION='bookworm' ARG PHP_VERSION='8.5' ARG PHP_VARIATION='fpm' +ARG BASE_IMAGE="php:${PHP_VERSION}-${PHP_VARIATION}-${BASE_OS_VERSION}" ########## # FPM: Main Image ########## -FROM php:${PHP_VERSION}-${PHP_VARIATION}-${BASE_OS_VERSION} +FROM ${BASE_IMAGE} ARG DEPENDENCY_PACKAGES_ALPINE="\ fcgi \ From 902371d6a8bcdee61dbc9f9c8b97608fa589321c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 22 Oct 2026 03:08:08 +0000 Subject: [PATCH 6/8] Compile php-base in CI and build every image on it --- .../workflows/action_publish-images-beta.yml | 17 +- .../action_publish-images-production.yml | 17 +- .../workflows/action_publish-images-prs.yml | 26 +- .../workflows/service_build-base-images.yml | 249 ++++++++++++++++++ .github/workflows/service_build-images.yml | 44 ++++ .github/workflows/service_setup-matrix.yml | 2 + AGENTS.md | 13 +- scripts/conf/php-versions-base-config.yml | 11 +- scripts/dev.sh | 10 +- scripts/generate-matrix.sh | 4 +- scripts/get-php-versions.sh | 1 + scripts/plan-php-base-images.sh | 74 ++++++ 12 files changed, 452 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/service_build-base-images.yml create mode 100755 scripts/plan-php-base-images.sh diff --git a/.github/workflows/action_publish-images-beta.yml b/.github/workflows/action_publish-images-beta.yml index 5f0d158f..65aac656 100644 --- a/.github/workflows/action_publish-images-beta.yml +++ b/.github/workflows/action_publish-images-beta.yml @@ -55,8 +55,21 @@ jobs: with: ref: ${{ needs.release.outputs.tag }} - build: + # PHP compiled from php.net, so a release never waits for Docker Hub. + base: needs: [release, setup] + uses: ./.github/workflows/service_build-base-images.yml + permissions: + contents: read + packages: write + id-token: write + with: + matrix: ${{ needs.setup.outputs.matrix }} + image-prefix: "ghcr.io/serversideup/php-base:" + ref: ${{ needs.release.outputs.tag }} + + build: + needs: [release, setup, base] name: build ${{ matrix.variation }} strategy: fail-fast: false @@ -65,12 +78,14 @@ jobs: permissions: contents: read id-token: write + packages: read uses: ./.github/workflows/service_build-images.yml with: variation: ${{ matrix.variation }} matrix: ${{ needs.setup.outputs.matrix }} ref: ${{ needs.release.outputs.tag }} registry-repositories: "docker.io/serversideup/php,ghcr.io/serversideup/php" + base-images: ${{ needs.base.outputs.base-images }} tag-prefix: "beta" release-type: "testing" # Only the prerelease that fired this gets release tags. Rebuilds refresh the beta floating tags. diff --git a/.github/workflows/action_publish-images-production.yml b/.github/workflows/action_publish-images-production.yml index 53607d45..7bf285ff 100644 --- a/.github/workflows/action_publish-images-production.yml +++ b/.github/workflows/action_publish-images-production.yml @@ -106,8 +106,21 @@ jobs: with: ref: ${{ needs.release.outputs.ref }} - build: + # PHP compiled from php.net, so a release never waits for Docker Hub. + base: needs: [release, setup] + uses: ./.github/workflows/service_build-base-images.yml + permissions: + contents: read + packages: write + id-token: write + with: + matrix: ${{ needs.setup.outputs.matrix }} + image-prefix: "ghcr.io/serversideup/php-base:" + ref: ${{ needs.release.outputs.ref }} + + build: + needs: [release, setup, base] name: build ${{ matrix.variation }} strategy: fail-fast: false @@ -116,12 +129,14 @@ jobs: permissions: contents: read id-token: write + packages: read uses: ./.github/workflows/service_build-images.yml with: variation: ${{ matrix.variation }} matrix: ${{ needs.setup.outputs.matrix }} ref: ${{ needs.release.outputs.ref }} registry-repositories: "docker.io/serversideup/php,ghcr.io/serversideup/php" + base-images: ${{ needs.base.outputs.base-images }} tag-prefix: '' release-type: "latest" release-tag: ${{ needs.release.outputs.release-tag }} diff --git a/.github/workflows/action_publish-images-prs.yml b/.github/workflows/action_publish-images-prs.yml index 35b5f765..ed28a3de 100644 --- a/.github/workflows/action_publish-images-prs.yml +++ b/.github/workflows/action_publish-images-prs.yml @@ -38,8 +38,30 @@ jobs: with: ref: ${{ inputs.pr_number && format('refs/pull/{0}/head', inputs.pr_number) || github.ref }} - build: + # PHP compiled from php.net, so a release never waits for Docker Hub. + base: needs: [setup] + if: >- + github.event_name == 'workflow_dispatch' || + ( + github.event.pull_request.head.repo.full_name == github.repository && + github.actor != 'dependabot[bot]' + ) + uses: ./.github/workflows/service_build-base-images.yml + permissions: + contents: read + packages: write + id-token: write + with: + matrix: ${{ needs.setup.outputs.matrix }} + image-prefix: "docker.io/serversideup/php-dev:php-base-" + ref: ${{ inputs.pr_number && format('refs/pull/{0}/head', inputs.pr_number) || github.ref }} + secrets: inherit + + build: + needs: [setup, base] + # Pull requests from forks skip the base images and build on the official ones. + if: always() && needs.setup.result == 'success' && contains(fromJson('["success", "skipped"]'), needs.base.result) name: build ${{ matrix.variation }} strategy: fail-fast: false @@ -48,12 +70,14 @@ jobs: permissions: contents: read id-token: write + packages: read uses: ./.github/workflows/service_build-images.yml with: variation: ${{ matrix.variation }} matrix: ${{ needs.setup.outputs.matrix }} ref: ${{ inputs.pr_number && format('refs/pull/{0}/head', inputs.pr_number) || github.ref }} registry-repositories: "docker.io/serversideup/php-dev" + base-images: ${{ needs.base.outputs.base-images || '{}' }} tag-prefix: ${{ inputs.pr_number || github.event.pull_request.number }} release-type: testing # Only save and publish when the code comes from this repository or a maintainer asked for it. diff --git a/.github/workflows/service_build-base-images.yml b/.github/workflows/service_build-base-images.yml new file mode 100644 index 00000000..28d51b17 --- /dev/null +++ b/.github/workflows/service_build-base-images.yml @@ -0,0 +1,249 @@ +name: Build PHP Base Images + +# Runs before the image builds. Compiles serversideup/php-base (PHP from the php.net +# tarball, see scripts/generate-php-base.sh) for every PHP version, SAPI, and OS in the +# matrix that doesn't have one yet, so a PHP release never waits on Docker Hub. +# Each image's tag ends in a hash of everything that goes into it (scripts/plan-php-base-images.sh), +# so only new PHP releases, template changes, and OS updates compile anything. Like the +# images built on top, each one is saved to the Depot Registry, tested on amd64 and arm64, +# and only then pushed to GHCR. +on: + workflow_call: + inputs: + matrix: + required: true + type: string + description: 'Build matrix JSON keyed by variation, as produced by service_setup-matrix.yml.' + image-prefix: + required: true + type: string + description: 'Image name and tag prefix for the base images (example: ghcr.io/serversideup/php-base:). Pull requests use serversideup/php-dev, so their code can never replace an image a release builds on.' + ref: + type: string + default: ${{ github.ref }} + description: 'The git ref to checkout (branch, tag, or commit SHA)' + outputs: + base-images: + description: 'JSON map of "--" to the base image to build on, e.g. {"8.4.27-fpm-trixie": "ghcr.io/serversideup/php-base:8.4.27-fpm-trixie-"}' + value: ${{ jobs.plan.outputs.base-images }} + +jobs: + plan: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + packages: read + outputs: + base-images: ${{ steps.plan.outputs.base-images }} + missing: ${{ steps.plan.outputs.missing }} + tests: ${{ steps.plan.outputs.tests }} + count: ${{ steps.plan.outputs.count }} + steps: + - name: Check out code + uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} + + - name: Install gawk + run: command -v gawk || { sudo apt-get update && sudo apt-get install -y gawk; } + + - name: Login to GitHub Container Registry + if: startsWith(inputs.image-prefix, 'ghcr.io/') + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub + if: startsWith(inputs.image-prefix, 'docker.io/') + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Find the base images that still have to be compiled + id: plan + env: + MATRIX: ${{ inputs.matrix }} + IMAGE_PREFIX: ${{ inputs.image-prefix }} + run: | + echo "$MATRIX" > matrix.json + PLAN=$(bash scripts/plan-php-base-images.sh matrix.json "$IMAGE_PREFIX") + COUNT=$(jq 'map(select(.exists | not)) | length' <<< "$PLAN") + { + echo "base-images=$(jq -c 'map({key, value: .image}) | from_entries' <<< "$PLAN")" + echo "missing=$(jq -c '{include: map(select(.exists | not))}' <<< "$PLAN")" + echo "tests=$(jq -c '{include: [ .[] | select(.exists | not) + | . + ({runner: "depot-ubuntu-24.04", arch: "amd64"}, {runner: "depot-ubuntu-24.04-arm", arch: "arm64"}) ]}' <<< "$PLAN")" + echo "count=$COUNT" + } >> "$GITHUB_OUTPUT" + echo "### PHP base images: $COUNT of $(jq length <<< "$PLAN") to compile" >> "$GITHUB_STEP_SUMMARY" + + build: + needs: plan + if: needs.plan.outputs.count > 0 + name: build ${{ matrix.key }} + runs-on: depot-ubuntu-24.04 + timeout-minutes: 45 + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan.outputs.missing) }} + steps: + - name: Check out code + uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} + + - name: Install gawk + run: command -v gawk || { sudo apt-get update && sudo apt-get install -y gawk; } + + - name: Render the Dockerfile + id: render + run: | + read -r _ _ _ CONTEXT_DIR < <(bash scripts/generate-php-base.sh --version "${{ matrix.php }}" --os "${{ matrix.os }}" --sapi "${{ matrix.sapi }}") + echo "context=$CONTEXT_DIR" >> "$GITHUB_OUTPUT" + + - name: Set up Depot CLI + uses: depot/setup-action@v1 + + - name: Build the base image + id: build + uses: depot/build-push-action@v1 + with: + context: ${{ steps.render.outputs.context }} + platforms: | + linux/amd64 + linux/arm64/v8 + pull: true + push: false + save: true + save-tags: ${{ github.run_id }}-php-base-${{ matrix.key }} + tags: ${{ matrix.image }} + + - name: Record the build + run: | + mkdir -p php-base-details + jq -n --arg key "${{ matrix.key }}" --arg image "${{ matrix.image }}" --arg build_id "${{ steps.build.outputs.build-id }}" --arg digest "${{ steps.build.outputs.digest }}" \ + '{key: $key, image: $image, build_id: $build_id, digest: $digest}' | tee "php-base-details/${{ matrix.key }}.json" + + - name: Upload the build record + uses: actions/upload-artifact@v7 + with: + name: php-base-details-${{ matrix.key }} + path: php-base-details/ + retention-days: 7 + + test: + needs: [plan, build] + name: test ${{ matrix.key }} ${{ matrix.arch }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan.outputs.tests) }} + steps: + - name: Check out code + uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} + + - name: Set up Depot CLI + uses: depot/setup-action@v1 + with: + oidc: true + + - name: Login to the Depot Registry + run: docker login registry.depot.dev --username x-token --password-stdin <<< "$(depot pull-token --project "$(jq -r .id depot.json)")" + + # On release day the official image for this patch may not exist yet, so the + # comparison falls back to the newest one Docker Hub has for the same minor. + - name: Test the base image + env: + PHP: ${{ matrix.php }} + SAPI: ${{ matrix.sapi }} + OS: ${{ matrix.os }} + KEY: ${{ matrix.key }} + run: | + SAVED="registry.depot.dev/$(jq -r .id depot.json):${{ github.run_id }}-php-base-${KEY}" + docker pull --quiet "$SAVED" + OFFICIAL="php:${PHP}-${SAPI}-${OS}" + docker manifest inspect "$OFFICIAL" > /dev/null 2>&1 || OFFICIAL="php:${PHP%.*}-${SAPI}-${OS}" + bash scripts/test-base-image.sh "$SAVED" "$SAPI" "$OFFICIAL" + + # Only base images that passed their tests reach GHCR, and only from here can the image + # builds use them. `depot push` copies each tested build server-side. + publish: + needs: [plan, build, test] + runs-on: depot-ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Check out code + uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} + + - name: Download the build records + uses: actions/download-artifact@v8 + with: + pattern: php-base-details-* + path: php-base-details + merge-multiple: true + + - name: Set up Depot CLI + uses: depot/setup-action@v1 + with: + oidc: true + + - name: Login to GitHub Container Registry + if: startsWith(inputs.image-prefix, 'ghcr.io/') + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub + if: startsWith(inputs.image-prefix, 'docker.io/') + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Push the tested base images + run: | + PROJECT=$(jq -r .id depot.json) + FAILED=() + while IFS=$'\t' read -r KEY IMAGE BUILD_ID DIGEST; do + echo "::group::${KEY}" + if depot push --project "$PROJECT" --progress plain --tag "$IMAGE" "$BUILD_ID"; then + # What was pushed must be what was tested: the index itself or one of its manifests. + INDEX=$(docker buildx imagetools inspect --raw "$IMAGE") + PUSHED="sha256:$(printf '%s' "$INDEX" | sha256sum | cut -d' ' -f1) $(jq -r '.manifests[]?.digest' <<< "$INDEX" | tr '\n' ' ')" + if [[ " $PUSHED " == *" $DIGEST "* ]]; then + echo "Pushed $IMAGE ($DIGEST)" + else + echo "::error title=Digest mismatch::$IMAGE is $PUSHED but the tested build was $DIGEST" + FAILED+=("$KEY") + fi + else + FAILED+=("$KEY") + fi + echo "::endgroup::" + done < <(jq -r '[.key, .image, .build_id, .digest] | @tsv' php-base-details/*.json) + + if [ "${#FAILED[@]}" -gt 0 ]; then + echo "::error title=Base image push failed::${FAILED[*]} were not pushed, or GHCR has a different digest than the tested build" + exit 1 + fi diff --git a/.github/workflows/service_build-images.yml b/.github/workflows/service_build-images.yml index eb6612df..7ec1e7e1 100644 --- a/.github/workflows/service_build-images.yml +++ b/.github/workflows/service_build-images.yml @@ -49,6 +49,10 @@ on: type: boolean default: true description: 'Publish floating tags like 8.4-fpm-nginx. False for a release older than the newest one, so a backport never moves them.' + base-images: + type: string + default: '{}' + description: 'JSON map of "--" to the serversideup/php-base image to build on, from service_build-base-images.yml. Empty builds on the official images (pull requests from forks).' jobs: images: @@ -58,6 +62,7 @@ jobs: permissions: contents: read id-token: write + packages: read strategy: fail-fast: false matrix: ${{ fromJson(inputs.matrix)[inputs.variation] }} @@ -116,6 +121,44 @@ jobs: echo "REPOSITORY_BUILD_VERSION=git-${SHORT_SHA}-${{ github.run_id }}" >> "$GITHUB_ENV" fi + # Fork pull requests can't build base images, so they build on the official ones. + - name: Resolve the PHP base image + id: base_image + env: + BASE_IMAGES: ${{ inputs.base-images }} + KEY: ${{ matrix.patch_version }}-${{ matrix.base_sapi }}-${{ matrix.base_os }} + FROM_SOURCE: ${{ matrix.php_from_source && inputs.publish }} + run: | + if [ "$FROM_SOURCE" != "true" ]; then + echo "Building on the official php:${KEY} image" + exit 0 + fi + BASE_IMAGE=$(jq -r --arg key "$KEY" '.[$key] // empty' <<< "$BASE_IMAGES") + if [ -z "$BASE_IMAGE" ]; then + echo "::error title=No base image::service_build-base-images.yml did not plan a php-base image for ${KEY}." + exit 1 + fi + echo "Building on $BASE_IMAGE" + { + echo "arg=BASE_IMAGE=$BASE_IMAGE" + echo "image=$BASE_IMAGE" + } >> "$GITHUB_OUTPUT" + + - name: Login to GitHub Container Registry + if: ${{ startsWith(steps.base_image.outputs.image, 'ghcr.io/') }} + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub + if: ${{ startsWith(steps.base_image.outputs.image, 'docker.io/') }} + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - name: Compute NGINX build-arg (only for fpm-nginx) id: compute_nginx if: ${{ matrix.php_variation == 'fpm-nginx' }} @@ -148,6 +191,7 @@ jobs: PHP_VARIATION=${{ matrix.php_variation }} REPOSITORY_BUILD_VERSION=${{ env.REPOSITORY_BUILD_VERSION }} PHP_EXTENSION_OVERRIDES=${{ matrix.php_extension_overrides }} + ${{ steps.base_image.outputs.arg }} ${{ steps.compute_nginx.outputs.nginx_arg }} platforms: | linux/amd64 diff --git a/.github/workflows/service_setup-matrix.yml b/.github/workflows/service_setup-matrix.yml index 4f301bc1..2ef9efde 100644 --- a/.github/workflows/service_setup-matrix.yml +++ b/.github/workflows/service_setup-matrix.yml @@ -40,6 +40,8 @@ jobs: ./scripts/get-php-versions.sh env: SKIP_DOWNLOAD: false + # PHP is compiled from php.net (service_build-base-images.yml), so a release doesn't wait for Docker Hub. + SKIP_DOCKERHUB_VALIDATION: true - name: Ensure our PHP Versions file exists. run: | diff --git a/AGENTS.md b/AGENTS.md index dcec86e5..c798d6e0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,7 +56,7 @@ Each variation Dockerfile uses multi-stage builds: 3. The build context is the **project root** (not `src/`), so COPY paths are relative to root: `COPY --chmod=755 src/common/ /` ### PHP base image -Every variation builds `FROM ${BASE_IMAGE}`, which defaults to the official `php:` image. `serversideup/php-base` is the same image compiled by us from the php.net tarball, so a PHP release can ship without waiting for Docker Hub. `scripts/generate-php-base.sh` renders it with docker-library's own template and renderer, vendored unchanged in `src/base/upstream/` (see `src/base/UPSTREAM`). Every change we make to the rendered Dockerfiles lives in that script's `patch_dockerfile`. Keep that list short, because each patch is a difference from the official image. Signing keys come from `src/base/keys/`, never a keyserver. `scripts/test-base-image.sh ` runs docker-library's tests and checks parity with the official image. The base SAPI is `cli` for cli, `zts` for frankenphp, and `fpm` for the rest. PHP 8.1 is not in upstream's template, so it stays on the official `php:8.1.34` images. +Every variation builds `FROM ${BASE_IMAGE}`, which defaults to the official `php:` image. `serversideup/php-base` is the same image compiled by us from the php.net tarball, so a PHP release can ship without waiting for Docker Hub. `scripts/generate-php-base.sh` renders it with docker-library's own template and renderer, vendored unchanged in `src/base/upstream/` (see `src/base/UPSTREAM`). Every change we make to the rendered Dockerfiles lives in that script's `patch_dockerfile`. Keep that list short, because each patch is a difference from the official image. Signing keys come from `src/base/keys/`, never a keyserver. `scripts/test-base-image.sh ` runs docker-library's tests and checks parity with the official image. Every Monday, `scheduled-task_check-upstream-php.yml` diffs the vendored files against docker-library/php (`scripts/check-upstream-php.sh`) and re-checks our newest base images against the official ones. If either check finds a difference, it opens an `upstream-php` issue. The base SAPI is `cli` for cli, `zts` for frankenphp, and `fpm` for the rest. PHP 8.1 is not in upstream's template, so it stays on the official `php:8.1.34` images. ### Build args used across Dockerfiles - `PHP_VERSION`, `BASE_OS_VERSION`, `PHP_VARIATION` -- set by CI matrix or `scripts/dev.sh` @@ -102,6 +102,7 @@ When modifying the version pipeline, the base config (`php-versions-base-config. - Builds run via GitHub Actions using **Depot** (`depot/build-push-action`) for multi-arch (`linux/amd64` + `linux/arm64/v8`). Depot builds both architectures natively on its own builders; the GitHub runner only orchestrates, so runner size and architecture do not affect build speed. - Publishing is three phases, each a reusable workflow the callers (`action_publish-images-*.yml`) chain: `service_setup-matrix.yml` generates the matrix once; `service_build-images.yml` builds one variation and saves every image to the Depot Registry (nothing public yet), recording the Depot build ID and digest; `service_test-images.yml` pulls the saved images on native amd64 and arm64 Depot runners (Depot sponsors the project, so they are free here and have no concurrency cap), one job per PHP version and base OS so the variations that share base layers share one pull, and runs `scripts/test-image.sh` against each; `service_publish-images.yml` promotes the tested builds with `depot push`, which copies the saved image to every Docker Hub and GHCR tag server-side (no image bytes touch the runner), one job per variation, and fails if the published digest is not the tested one. GHCR allows 2,000 requests a minute per token and `depot push` does not retry a 429, so the publish job pushes only two images at a time and retries a failed push with a growing pause; raising `MAX_PARALLEL` gets every variation's job rate limited at once. `service_report.yml` renders the run table last. Jobs render as `build cli / 8.4.25-bookworm`, `test / 8.4.25-bookworm arm64`, `publish / cli`. +- Before the variations build, `service_build-base-images.yml` compiles any `serversideup/php-base` images the matrix needs. `scripts/plan-php-base-images.sh` tags each one with a hash of its rendered build context plus the Debian or Alpine digest, so only a new PHP release, a template or patch change, or an OS update compiles anything. Each image is saved to Depot, tested with `scripts/test-base-image.sh` on amd64 and arm64 (against the official image, or the newest official patch on release day), and only then pushed to GHCR. `service_build-images.yml` builds each variation `FROM` the planned image. Pull requests push theirs to `serversideup/php-dev` as `php-base----`, so PR code can never plant an image a release reuses. Fork pull requests, and minors with `php_from_source: false` (8.1), build on the official images. The matrix skips Docker Hub validation, because no release waits on Docker Hub anymore. - A broken image can never reach a public tag: what users pull is byte-for-byte what the tests ran. Publishing is all or nothing: if any image fails to build or fails its test, no image from that run is published, including on the weekly rebuilds. Saved images live in the Depot Registry until the project's retention setting removes them. - Stable releases start from pushing the version tag (`git push origin v5.1.0`), not from publishing a release on GitHub. "Docker Publish (Production Images)" builds, tests, and publishes the tag's images, then publishes the draft release (or one with generated notes). Publishing locks the tag, so a failed build leaves it unlocked: delete the tag, fix the problem, and push it again. Floating tags (`8.4-fpm-nginx`, `latest`) move only when the version is newer than every published stable release. An older version, such as a backport tagged on a `release/*` branch, gets release tags only and `--latest=false`. To build a specific PHP patch, pin it in `php-versions-base-config.yml` and commit it, so the tag alone reproduces the build. Beta releases still start from a GitHub prerelease. - The build matrix is generated from the PHP version pipeline described above. @@ -120,6 +121,16 @@ When modifying the version pipeline, the base config (`php-versions-base-config. - Every build job records its image details (tags, Depot Registry reference, build ID, digest) as an `image-details-*` artifact; the publish job adds a `.published.json` record with the compressed size per architecture. `service_report.yml` merges them with `scripts/build-summary.sh` (the published record wins) into one table on the run summary and in the PR comment. - `fail-fast` is off, so one failed image never cancels the others. `trigger_auto-retry-failed-builds.yml` re-runs the failed jobs of a production, beta, or PR run once when only a few jobs failed and no newer run exists. If that also fails, or too many jobs failed to look transient, it opens a `ci-failure` issue assigned to the maintainer with the failed jobs, their first error annotation, the log tail, and a link to re-run. Only production and beta runs open issues; PR runs have the PR comment. GitHub's own failure emails do not cover this case reliably: scheduled runs notify whoever last edited the cron line, and re-run attempts are triggered by the Actions bot. +## Releasing a PHP security fix + +`scheduled-task_watch-php-releases.yml` checks php.net every 15 minutes. When a PHP patch we build has no `php-base` image yet, it compiles and tests the base images and opens a `php-release` issue with the exact tag to push. When asked to "update to these versions" or to ship a PHP release: + +1. Confirm the versions on https://www.php.net/downloads.php?source=Y. If the watcher's issue exists, check that its base images succeeded. +2. Normally nothing needs editing, because the matrix builds the newest patch php.net lists. If a minor must stay on an older patch, pin it in `scripts/conf/php-versions-base-config.yml` through a PR first. +3. Ask the maintainer before tagging. Pushing the tag publishes the release. Then run `git fetch origin && git tag vX.Y.Z origin/main && git push origin vX.Y.Z`, using the next patch version after the newest published release. +4. Watch it with `gh run watch` on the "Docker Publish (Production Images)" run. If it fails, the release was never published, so the tag is not locked: `git push origin :refs/tags/vX.Y.Z`, fix the cause, and push the tag again. +5. For an older major, tag its `release/vN.x` branch instead. Only its release tags are published. `release/v4.x` predates this workflow: push the tag, then run `gh workflow run action_publish-images-production.yml --ref vX.Y.Z`, and publish its GitHub release last with `--latest=false`. + ## Verification There is no automated test suite for image logic. To verify changes: diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 5a7bfaab..b69e88ed 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -1,13 +1,20 @@ +# base_sapi is the PHP base image each variation builds on (php:--, +# or serversideup/php-base when PHP is compiled from source). php_variations: - name: cli default: true + base_sapi: cli - name: fpm + base_sapi: fpm - name: fpm-apache + base_sapi: fpm supported_os: # Open a discussion on serversideup/php if you want to see Alpine support for fpm-apache (https://github.com/serversideup/docker-php/discussions/66) - bookworm - trixie - name: fpm-nginx + base_sapi: fpm - name: frankenphp + base_sapi: zts excluded_minor_versions: - "8.1" - "8.2" @@ -24,8 +31,10 @@ php_versions: # PHP 8.1 is end of life, so php.net no longer lists it in the active releases feed # and the patch version must be pinned here. The official php:8.1.34 images still # exist on Docker Hub, and Debian Bookworm, Trixie, and Alpine 3.22 still receive - # security updates, so weekly rebuilds keep delivering OS patches. + # security updates, so weekly rebuilds keep delivering OS patches. docker-library's + # template no longer supports 8.1, so it keeps building on the official images. - minor: "8.1" + php_from_source: false base_os: - name: alpine3.22 - name: bookworm diff --git a/scripts/dev.sh b/scripts/dev.sh index d8e9b079..33e79d5c 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -96,17 +96,9 @@ php_minor_version() { fi } -base_image_sapi() { - case "$PHP_BUILD_VARIATION" in - cli) echo "cli" ;; - frankenphp) echo "zts" ;; - *) echo "fpm" ;; - esac -} - build_php_base_image() { local sapi base_line version context_dir - sapi=$(base_image_sapi) + sapi=$(VARIATION="$PHP_BUILD_VARIATION" yq -r '.php_variations[] | select(.name == env(VARIATION)) | .base_sapi' "$BASE_PHP_VERSIONS_CONFIG_FILE") base_line=$("$SCRIPT_DIR/generate-php-base.sh" --version "$PHP_BUILD_VERSION" --os "$PHP_BUILD_BASE_OS" --sapi "$sapi") read -r version _ _ context_dir <<< "$base_line" BASE_IMAGE="serversideup/php-base:${version}-${sapi}-${PHP_BUILD_BASE_OS}" diff --git a/scripts/generate-matrix.sh b/scripts/generate-matrix.sh index 12e786d2..481bc91d 100755 --- a/scripts/generate-matrix.sh +++ b/scripts/generate-matrix.sh @@ -36,14 +36,14 @@ yq -o=json "$PHP_VERSIONS_FILE" | jq -c ' ((($variation.supported_os // []) | any(os_family_match($os.name; .)))); . as $root - | [ ($root.php_variations[] | {name, supported_os, excluded_minor_versions}) as $variation + | [ ($root.php_variations[] | {name, base_sapi, supported_os, excluded_minor_versions}) as $variation | $root.php_versions[] | .minor_versions[] as $minor | select((($variation.excluded_minor_versions // []) | index($minor.minor)) | not) | ($minor.base_os // [])[] as $os | ($minor.patch_versions // [])[] as $patch | select(is_supported($variation; $os)) - | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name, php_extension_overrides: ((($root.php_extension_overrides // []) + ($minor.php_extension_overrides // [])) | join(" "))} + | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name, base_sapi: $variation.base_sapi, php_from_source: ($minor.php_from_source != false), php_extension_overrides: ((($root.php_extension_overrides // []) + ($minor.php_extension_overrides // [])) | join(" "))} ] | { include: ( . | sort_by(.patch_version | version_weight) | reverse ) } ' \ No newline at end of file diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index a8b1e61e..cd13e426 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -447,6 +447,7 @@ if [ "$SKIP_DOWNLOAD" = false ]; then | map({ minor: .[0].minor, base_os: (map(.base_os // []) | add), + php_from_source: all(.[]; .php_from_source != false), patch_versions: (map(.patch_versions // []) | flatten | unique | select(. != null)), php_extension_overrides: (map(.php_extension_overrides // []) | add | unique) }) diff --git a/scripts/plan-php-base-images.sh b/scripts/plan-php-base-images.sh new file mode 100755 index 00000000..4b2a009e --- /dev/null +++ b/scripts/plan-php-base-images.sh @@ -0,0 +1,74 @@ +#!/bin/bash +set -eo pipefail + +################################################### +# Usage: plan-php-base-images.sh [output-dir] +################################################### +# Works out which serversideup/php-base images a build matrix (from generate-matrix.sh, +# keyed by variation) needs, and which of them still have to be compiled. Each image is +# tagged with a hash of everything that goes into it: the rendered Dockerfile and build +# context, plus the digest of the Debian or Alpine image it starts from. So an image is +# only compiled again when PHP, docker-library's template, our patches, or the OS changes. +# The image prefix is the image name and the start of the tag, such as +# "ghcr.io/serversideup/php-base:" or "docker.io/serversideup/php-dev:php-base-". +# +# Prints a JSON array, one entry per image: +# +# {"key": "8.4.27-fpm-trixie", "php": "8.4.27", "sapi": "fpm", "os": "trixie", +# "image": "8.4.27-fpm-trixie-", "exists": true} + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +matrix_file="$1" +image_prefix="$2" +output_dir="${3:-$(dirname "$SCRIPT_DIR")/build/php-base}" + +if [ ! -f "$matrix_file" ] || [ -z "$image_prefix" ]; then + sed -n '4,17p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' >&2 + exit 1 +fi + +needed=$(jq -c '[.[].include[] | select(.php_from_source) | {php: .patch_version, sapi: .base_sapi, os: .base_os}] | unique' "$matrix_file") +if [ "$(jq 'length' <<< "$needed")" = "0" ]; then + echo '[]' + exit 0 +fi + +generate_args=() +for field in php:--version os:--os sapi:--sapi; do + while read -r value; do + generate_args+=("${field#*:}" "$value") + done < <(jq -r --arg field "${field%%:*}" '[.[][$field]] | unique | .[]' <<< "$needed") +done +"$SCRIPT_DIR/generate-php-base.sh" "${generate_args[@]}" --output "$output_dir" > /dev/null + +sha256() { + if command -v sha256sum > /dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi +} + +# One " " line per OS image, since every PHP version shares the same few +from_digests="" +plan='[]' +while IFS=$'\t' read -r php sapi os; do + context_dir="$output_dir/${php%.*}/$os/$sapi" + from=$(sed -n 's/^FROM //p' "$context_dir/Dockerfile" | head -n1) + from_digest=$(awk -v from="$from" '$1 == from { print $2 }' <<< "$from_digests") + if [ -z "$from_digest" ]; then + from_digest=$(docker buildx imagetools inspect "$from" --format '{{json .Manifest}}' | jq -r .digest) + from_digests+="$from $from_digest"$'\n' + fi + hash=$( + cd "$context_dir" + { find . -type f | LC_ALL=C sort | while read -r file; do sha256 "$file"; done; echo "$from@$from_digest"; } | sha256 | cut -c1-12 + ) + key="$php-$sapi-$os" + image="$image_prefix$key-$hash" + exists=false + if docker buildx imagetools inspect "$image" > /dev/null 2>&1; then + exists=true + fi + echo "$image $([ "$exists" = true ] && echo "exists" || echo "to build")" >&2 + plan=$(jq -c --arg key "$key" --arg php "$php" --arg sapi "$sapi" --arg os "$os" --arg image "$image" --argjson exists "$exists" \ + '. + [{key: $key, php: $php, sapi: $sapi, os: $os, image: $image, exists: $exists}]' <<< "$plan") +done < <(jq -r '.[] | [.php, .sapi, .os] | @tsv' <<< "$needed") + +echo "$plan" From e0648153b2dcd1a17697ca877114f8fc7d195c93 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 22 Oct 2026 03:08:08 +0000 Subject: [PATCH 7/8] Watch php.net and compile base images as soon as PHP releases --- .../scheduled-task_watch-php-releases.yml | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 .github/workflows/scheduled-task_watch-php-releases.yml diff --git a/.github/workflows/scheduled-task_watch-php-releases.yml b/.github/workflows/scheduled-task_watch-php-releases.yml new file mode 100644 index 00000000..1addff3f --- /dev/null +++ b/.github/workflows/scheduled-task_watch-php-releases.yml @@ -0,0 +1,131 @@ +name: Watch PHP Releases +run-name: Watch php.net for new PHP releases + +# Checks php.net every 15 minutes. When a PHP patch we build is out and has no php-base +# image yet, this compiles and tests its base images right away (the slow part of a +# release) and opens an issue. Releasing stays a human decision: push the next version +# tag, and the release reuses the base images compiled here. +on: + workflow_dispatch: + schedule: + - cron: '*/15 * * * *' + +concurrency: + group: watch-php-releases + +permissions: {} + +jobs: + check: + # Forks inherit the cron schedule. Their runs always fail and email whoever last edited the schedule upstream. + if: github.event_name != 'schedule' || github.repository == 'serversideup/docker-php' + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + issues: read + packages: read + outputs: + versions: ${{ steps.check.outputs.versions }} + title: ${{ steps.check.outputs.title }} + env: + ISSUE_LABEL: php-release + steps: + - name: Check out code + uses: actions/checkout@v7 + + - name: Find PHP releases with no base image yet + id: check + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + OUR_MINORS=$(yq -r '.php_versions[].minor_versions[] | select(.php_from_source != false) | .minor' scripts/conf/php-versions-base-config.yml) + RELEASED=$(curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors "https://www.php.net/releases/active.php" \ + | jq -r '.[][] | .version // empty' | sort -V) + # The package doesn't exist until the first base image is pushed, so no tags means none are built yet. + TAGS=$(gh api --paginate "/orgs/${GITHUB_REPOSITORY_OWNER}/packages/container/php-base/versions" --jq '.[].metadata.container.tags[]' 2>/dev/null) || TAGS="" + + NEW=() + for VERSION in $RELEASED; do + grep -qxF "${VERSION%.*}" <<< "$OUR_MINORS" || continue + grep -q "^${VERSION//./\\.}-" <<< "$TAGS" || NEW+=("$VERSION") + done + if [ "${#NEW[@]}" -eq 0 ]; then + echo "Every PHP release we build already has base images" + exit 0 + fi + + TITLE="PHP ${NEW[*]} released" + # Someone is already on it. Compiling again every 15 minutes would not help. + if [ -n "$(gh issue list --label "$ISSUE_LABEL" --state open --search "\"$TITLE\" in:title" --json number --jq '.[].number')" ]; then + echo "An open issue already tracks $TITLE" + exit 0 + fi + echo "New PHP releases: ${NEW[*]}" + echo "versions=${NEW[*]}" >> "$GITHUB_OUTPUT" + echo "title=$TITLE" >> "$GITHUB_OUTPUT" + + setup: + needs: check + if: needs.check.outputs.versions != '' + uses: ./.github/workflows/service_setup-matrix.yml + permissions: + contents: read + + base: + needs: [check, setup] + uses: ./.github/workflows/service_build-base-images.yml + permissions: + contents: read + packages: write + id-token: write + with: + matrix: ${{ needs.setup.outputs.matrix }} + image-prefix: "ghcr.io/serversideup/php-base:" + + notify: + needs: [check, setup, base] + if: always() && needs.check.outputs.versions != '' + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + issues: write + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + ASSIGNEE: jaydrogers + ISSUE_LABEL: php-release + TITLE: ${{ needs.check.outputs.title }} + VERSIONS: ${{ needs.check.outputs.versions }} + BASE_RESULT: ${{ needs.base.result }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Open an issue for the release + run: | + LATEST=$(gh release list --exclude-drafts --exclude-pre-releases --limit 100 --json tagName --jq '.[].tagName' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n1) + NEXT="${LATEST%.*}.$(( ${LATEST##*.} + 1 ))" + if [ "$BASE_RESULT" = "success" ]; then + STATUS="The base images are compiled and tested, so a release only builds the images on top." + else + STATUS="โš ๏ธ Compiling the base images did not succeed (\`$BASE_RESULT\`). Check [the run]($RUN_URL) before releasing." + fi + + cat > issue.md < Date: Thu, 22 Oct 2026 03:08:08 +0000 Subject: [PATCH 8/8] Check php-base against docker-library/php every week --- .../scheduled-task_check-upstream-php.yml | 161 ++++++++++++++++++ scripts/check-upstream-php.sh | 33 ++++ src/base/UPSTREAM | 4 + 3 files changed, 198 insertions(+) create mode 100644 .github/workflows/scheduled-task_check-upstream-php.yml create mode 100755 scripts/check-upstream-php.sh diff --git a/.github/workflows/scheduled-task_check-upstream-php.yml b/.github/workflows/scheduled-task_check-upstream-php.yml new file mode 100644 index 00000000..f9514eb4 --- /dev/null +++ b/.github/workflows/scheduled-task_check-upstream-php.yml @@ -0,0 +1,161 @@ +name: Check Upstream PHP Images +run-name: Check php-base against docker-library/php + +# serversideup/php-base renders docker-library's template, vendored in src/base/upstream, +# and must stay a drop-in for the official images. Every Monday, before the beta rebuild, +# this checks both: whether docker-library changed the files we vendor, and whether our +# newest base images still match the official image of the same PHP version. Either one +# opens an issue. See src/base/UPSTREAM for how to pull upstream changes in. +on: + workflow_dispatch: + schedule: + - cron: '0 7 * * 1' + +permissions: {} + +jobs: + drift: + # Forks inherit the cron schedule. Their runs always fail and email whoever last edited the schedule upstream. + if: github.event_name != 'schedule' || github.repository == 'serversideup/docker-php' + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + outputs: + changed: ${{ steps.drift.outputs.changed }} + steps: + - name: Check out code + uses: actions/checkout@v7 + + - name: Compare the vendored files with docker-library/php + id: drift + run: | + if bash scripts/check-upstream-php.sh > drift.diff; then + echo "The vendored files match docker-library/php $(head -n1 drift.diff | cut -d' ' -f2)" + echo "changed=false" >> "$GITHUB_OUTPUT" + else + cat drift.diff + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Upload the diff + if: steps.drift.outputs.changed == 'true' + uses: actions/upload-artifact@v7 + with: + name: upstream-drift + path: drift.diff + retention-days: 30 + + list: + if: github.event_name != 'schedule' || github.repository == 'serversideup/docker-php' + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + packages: read + outputs: + matrix: ${{ steps.list.outputs.matrix }} + steps: + # GHCR lists versions newest first. Keep the newest image of the newest patch for each + # minor, SAPI, and OS, and group them by minor so each job shares its pulls. + - name: Find the newest base images + id: list + env: + GH_TOKEN: ${{ github.token }} + run: | + # The package doesn't exist until the first base image is pushed. Then there is nothing to check yet. + TAGS=$(gh api --paginate "/orgs/${GITHUB_REPOSITORY_OWNER}/packages/container/php-base/versions" --jq '.[].metadata.container.tags[]' 2>/dev/null) || TAGS="" + MATRIX=$(jq -R -s -c ' + split("\n") + | map(select(test("^[0-9]+\\.[0-9]+\\.[0-9]+-[a-z]+-[a-z0-9.]+-[0-9a-f]{12}$")) + | capture("^(?[0-9]+\\.[0-9]+\\.[0-9]+)-(?[a-z]+)-(?[a-z0-9.]+)-[0-9a-f]{12}$") + {tag: .}) + | map(. + {minor: (.php | split(".")[0:2] | join("."))}) + | group_by([.minor, .sapi, .os]) + | map(max_by(.php | split(".") | map(tonumber)) as $newest | map(select(.php == $newest.php)) | first) + | group_by(.minor) + | {include: map({minor: .[0].minor, images: (map({php, sapi, os, tag}) | tojson)})}' <<< "$TAGS") + echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" + echo "$MATRIX" | jq -r '.include[] | "\(.minor): \(.images | fromjson | map(.tag) | join(", "))"' + + parity: + needs: list + if: needs.list.outputs.matrix != '' && fromJson(needs.list.outputs.matrix).include[0] != null + name: parity ${{ matrix.minor }} + runs-on: ubuntu-24.04 + timeout-minutes: 45 + permissions: + contents: read + packages: read + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.list.outputs.matrix) }} + steps: + - name: Check out code + uses: actions/checkout@v7 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Every image is checked even after one fails, so the issue lists all of them at once. + - name: Compare each base image with the official one + env: + IMAGES: ${{ matrix.images }} + run: | + FAILED=() + while IFS=$'\t' read -r PHP SAPI OS TAG; do + IMAGE="ghcr.io/serversideup/php-base:$TAG" + OFFICIAL="php:${PHP}-${SAPI}-${OS}" + docker manifest inspect "$OFFICIAL" > /dev/null 2>&1 || OFFICIAL="php:${PHP%.*}-${SAPI}-${OS}" + echo "::group::$TAG vs $OFFICIAL" + if ! { docker pull --quiet "$IMAGE" && bash scripts/test-base-image.sh "$IMAGE" "$SAPI" "$OFFICIAL"; }; then + FAILED+=("$TAG") + fi + echo "::endgroup::" + docker image rm "$IMAGE" "$OFFICIAL" > /dev/null 2>&1 || true + done < <(jq -r '.[] | [.php, .sapi, .os, .tag] | @tsv' <<< "$IMAGES") + + if [ "${#FAILED[@]}" -gt 0 ]; then + echo "::error title=Base images differ from the official images::${FAILED[*]}" + exit 1 + fi + + notify: + needs: [drift, list, parity] + if: always() && (needs.drift.outputs.changed == 'true' || needs.list.result == 'failure' || needs.parity.result == 'failure') + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + issues: write + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + ASSIGNEE: jaydrogers + ISSUE_LABEL: upstream-php + DRIFT: ${{ needs.drift.outputs.changed }} + LIST_RESULT: ${{ needs.list.result }} + PARITY_RESULT: ${{ needs.parity.result }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Open or update the upstream issue + run: | + { + echo "The weekly upstream check found something to look at ([run]($RUN_URL)):" + echo + if [ "$DRIFT" = "true" ]; then + echo "- **docker-library/php changed files we vendor.** The diff is attached to the run as \`upstream-drift\`. Pull the changes in with the steps in \`src/base/UPSTREAM\`." + fi + if [ "$LIST_RESULT" = "failure" ] || [ "$PARITY_RESULT" = "failure" ]; then + echo "- **Our newest php-base images no longer match the official images,** or could not be checked. The failed checks are in the run's \`parity\` jobs." + fi + } > issue.md + + gh label create "$ISSUE_LABEL" --description "php-base needs to catch up with docker-library/php" --color 0E8A16 --force + EXISTING=$(gh issue list --label "$ISSUE_LABEL" --state open --json number --jq '.[0].number // empty') + if [ -n "$EXISTING" ]; then + gh issue comment "$EXISTING" --body-file issue.md + else + gh issue create --title "php-base needs to catch up with docker-library/php" --body-file issue.md --label "$ISSUE_LABEL" --assignee "$ASSIGNEE" + fi diff --git a/scripts/check-upstream-php.sh b/scripts/check-upstream-php.sh new file mode 100755 index 00000000..d82e3274 --- /dev/null +++ b/scripts/check-upstream-php.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -eo pipefail + +################################################### +# Usage: check-upstream-php.sh [ref] +################################################### +# Compares the docker-library/php files vendored in src/base/upstream with the same files +# upstream at ref (default: master). Prints the upstream commit, then a unified diff of +# every vendored file that changed. Exits 0 when nothing changed and 1 when something did. +# See src/base/UPSTREAM for how to pull the changes in. + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +VENDORED_DIR="$(dirname "$SCRIPT_DIR")/src/base/upstream" +ref="${1:-master}" + +commit=$(curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors \ + -H "Accept: application/vnd.github.sha" "https://api.github.com/repos/docker-library/php/commits/$ref") +work_dir=$(mktemp -d) +trap 'rm -rf "$work_dir"' EXIT +curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors -o "$work_dir/php.tar.gz" \ + "https://codeload.github.com/docker-library/php/tar.gz/$commit" +tar -xzf "$work_dir/php.tar.gz" -C "$work_dir" --strip-components 1 + +echo "docker-library/php $commit" +changed=0 +for vendored in "$VENDORED_DIR"/*; do + [ -f "$vendored" ] || continue + file=$(basename "$vendored") + if ! diff -u --label "vendored/$file" --label "upstream/$file" "$vendored" "$work_dir/$file"; then + changed=1 + fi +done +exit "$changed" diff --git a/src/base/UPSTREAM b/src/base/UPSTREAM index 267d2da4..c2c1c796 100644 --- a/src/base/UPSTREAM +++ b/src/base/UPSTREAM @@ -17,6 +17,10 @@ scripts/generate-php-base.sh, and the script fails if one stops applying. keys/ holds the release managers' public keys, one file per fingerprint listed in the template's GPG_KEYS, exported from https://www.php.net/distributions/php-keyring.gpg. +"Check Upstream PHP Images" (.github/workflows/scheduled-task_check-upstream-php.yml) runs +scripts/check-upstream-php.sh every Monday and opens an upstream-php issue when these files +change upstream, or when our newest base images stop matching the official ones. + Updating, when upstream adds a PHP minor or an Alpine version (rarely urgent): 1. Copy the files above from the new docker-library/php commit and update the commit here.