From 644eeb8e37de50826ed078d17164e19d1496e049 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Thu, 22 Oct 2026 09:53:19 +0200 Subject: [PATCH 1/7] ci: split FPM and FrankenPHP builds --- .github/action/build-bake-publish/action.yml | 4 + .github/workflows/build-fpm.yml | 240 +++++++++++++ .github/workflows/build-frankenphp.yml | 75 +++++ .github/workflows/build.yml | 337 ------------------- .github/workflows/update-php-matrix.yml | 11 +- README.md | 22 ++ docker-bake.fpm.hcl | 196 +++++++++++ docker-bake.frankenphp.hcl | 85 +++++ docker-bake.hcl | 283 +--------------- update-php-matrix.mjs | 29 +- 10 files changed, 647 insertions(+), 635 deletions(-) create mode 100644 .github/workflows/build-fpm.yml create mode 100644 .github/workflows/build-frankenphp.yml delete mode 100644 .github/workflows/build.yml create mode 100644 docker-bake.fpm.hcl create mode 100644 docker-bake.frankenphp.hcl diff --git a/.github/action/build-bake-publish/action.yml b/.github/action/build-bake-publish/action.yml index c619e89..055681c 100644 --- a/.github/action/build-bake-publish/action.yml +++ b/.github/action/build-bake-publish/action.yml @@ -1,6 +1,9 @@ name: 'Build and Publish' description: 'Builds Image and pushes' inputs: + files: + description: 'Bake files to load' + required: true targets: description: 'Targets' required: true @@ -35,6 +38,7 @@ runs: uses: docker/bake-action@v6 with: push: true + files: ${{ inputs.files }} targets: ${{ inputs.targets }} no-cache: true env: diff --git a/.github/workflows/build-fpm.yml b/.github/workflows/build-fpm.yml new file mode 100644 index 0000000..147be3b --- /dev/null +++ b/.github/workflows/build-fpm.yml @@ -0,0 +1,240 @@ +name: Build FPM Images +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'docker-bake.hcl' + - 'docker-bake.fpm.hcl' + - '.github/workflows/build-fpm.yml' + - '.github/action/build-bake-publish/**' + - 'fpm/**' + - 'fpm-otel/**' + - 'caddy/**' + - 'nginx/**' + - 'dev/**' + pull_request: + paths: + - 'docker-bake.hcl' + - 'docker-bake.fpm.hcl' + - '.github/workflows/build-fpm.yml' + - '.github/action/build-bake-publish/**' + - 'fpm/**' + - 'fpm-otel/**' + - 'caddy/**' + - 'nginx/**' + - 'dev/**' + schedule: + - cron: '10 4 * * *' + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +env: + DOCKER_BUILDKIT: 1 + COSIGN_EXPERIMENTAL: 1 + +permissions: + contents: write + id-token: write + packages: write + pull-requests: write + +jobs: + fpm: + name: FPM + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: fpm + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + fpm-otel: + name: FPM with OpenTelemetry + runs-on: ubuntu-latest + needs: [fpm] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: fpm-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + webserver: + name: ${{ matrix.webserver }} + runs-on: ubuntu-latest + needs: [fpm] + strategy: + fail-fast: false + matrix: + webserver: [ caddy, nginx ] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver}} + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + webserver-otel: + name: ${{ matrix.webserver }} with OpenTelemetry + runs-on: ubuntu-latest + needs: [fpm-otel] + strategy: + fail-fast: false + matrix: + webserver: [ caddy, nginx ] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver}}-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + dev: + name: Dev ${{ matrix.webserver }} + runs-on: ubuntu-latest + needs: [webserver-otel] + strategy: + fail-fast: false + matrix: + webserver: [ caddy, nginx ] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver }}-dev + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + dev-check: + name: Check Dev Image + runs-on: ubuntu-latest + needs: [dev] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Install a binary from GitHub releases + uses: jaxxstorm/action-install-gh-release@v3.0.0 + with: + repo: GoogleContainerTools/container-structure-test + extension-matching: disable + rename-to: container-structure-test + chmod: 0755 + + - name: Determine image tag + id: image-tag + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "IMAGE_TAG=ghcr.io/shopware/docker-dev-ci-test:${{ github.event.number }}-php8.4-node24-caddy" >> "$GITHUB_OUTPUT" + else + echo "IMAGE_TAG=ghcr.io/shopware/docker-dev:php8.4-node24-caddy" >> "$GITHUB_OUTPUT" + fi + + - name: Pull image + run: docker pull ${{ steps.image-tag.outputs.IMAGE_TAG }} + + - name: Test Dev Image + run: container-structure-test test --config dev/config.yaml --image ${{ steps.image-tag.outputs.IMAGE_TAG }} + + check: + name: Test Image with Webserver ${{ matrix.webserver }} + runs-on: ubuntu-latest + needs: [webserver] + strategy: + matrix: + webserver: + - caddy + - nginx + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Checkout + uses: actions/checkout@v7 + + - name: Checkout example repo + uses: actions/checkout@v7 + with: + repository: shopwareLabs/example-docker-repository + + - name: Build main branch + if: github.event_name != 'pull_request' + run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base:8.3-${{ matrix.webserver }} + + - name: Build PR + if: github.event_name == 'pull_request' + run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base-ci-test:${{ github.event.number }}-8.3-${{ matrix.webserver}} + + - name: Run image + run: docker compose up -d --wait + + - name: Wait for Webserver reachable + run: | + attempt_counter=0 + max_attempts=5 + + until curl --output /dev/null --silent --head --fail localhost:8000/admin; do + if [ ${attempt_counter} -eq ${max_attempts} ];then + echo "Max attempts reached" + exit 1 + fi + + printf '.' + attempt_counter=$((attempt_counter+1)) + sleep 5 + done + + - name: Check if shopware admin is running + run: curl --fail localhost:8000/admin + + - name: Check if shopware is running + run: curl --fail localhost:8000 + + # output logs if failed + - name: Output logs + if: always() + run: docker compose logs diff --git a/.github/workflows/build-frankenphp.yml b/.github/workflows/build-frankenphp.yml new file mode 100644 index 0000000..87209f7 --- /dev/null +++ b/.github/workflows/build-frankenphp.yml @@ -0,0 +1,75 @@ +name: Build FrankenPHP Images +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'docker-bake.hcl' + - 'docker-bake.frankenphp.hcl' + - '.github/workflows/build-frankenphp.yml' + - '.github/action/build-bake-publish/**' + - 'frankenphp/**' + - 'frankenphp-otel/**' + pull_request: + paths: + - 'docker-bake.hcl' + - 'docker-bake.frankenphp.hcl' + - '.github/workflows/build-frankenphp.yml' + - '.github/action/build-bake-publish/**' + - 'frankenphp/**' + - 'frankenphp-otel/**' + schedule: + - cron: '10 4 * * *' + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +env: + DOCKER_BUILDKIT: 1 + COSIGN_EXPERIMENTAL: 1 + +permissions: + contents: write + id-token: write + packages: write + pull-requests: write + +jobs: + frankenphp: + name: FrankenPHP + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + files: | + docker-bake.hcl + docker-bake.frankenphp.hcl + targets: frankenphp + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + frankenphp-otel: + name: FrankenPHP with OpenTelemetry + runs-on: namespace-profile-default + needs: [frankenphp] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + files: | + docker-bake.hcl + docker-bake.frankenphp.hcl + targets: frankenphp-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 325d2ab..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,337 +0,0 @@ -name: Build Base Images -on: - workflow_dispatch: - push: - branches: - - main - pull_request: - schedule: - - cron: '10 4 * * *' - -concurrency: - group: "${{ github.ref }}" - cancel-in-progress: true - -env: - DOCKER_BUILDKIT: 1 - COSIGN_EXPERIMENTAL: 1 - -permissions: - contents: write - id-token: write - packages: write - pull-requests: write - -jobs: - frankenphp: - name: FrankenPHP - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - docker-bake.hcl - frankenphp*/** - - - name: Build - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: ./.github/action/build-bake-publish - with: - targets: frankenphp - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - frankenphp-otel: - name: FrankenPHP with OpenTelemetry - runs-on: namespace-profile-default - needs: [frankenphp] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - docker-bake.hcl - frankenphp*/** - - - name: Build - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: ./.github/action/build-bake-publish - with: - targets: frankenphp-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - fpm: - name: FPM - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - docker-bake.hcl - fpm*/** - nginx/** - caddy/** - dev/** - - - name: Build - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: ./.github/action/build-bake-publish - with: - targets: fpm - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - fpm-otel: - name: FPM with OpenTelemetry - runs-on: ubuntu-latest - needs: [fpm] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - docker-bake.hcl - fpm*/** - nginx/** - caddy/** - dev/** - - - name: Build - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: ./.github/action/build-bake-publish - with: - targets: fpm-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - webserver: - name: ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [fpm] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - docker-bake.hcl - fpm/** - ${{ matrix.webserver }}/** - dev/** - - - name: Build - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: ./.github/action/build-bake-publish - with: - targets: ${{ matrix.webserver}} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - webserver-otel: - name: ${{ matrix.webserver }} with OpenTelemetry - runs-on: ubuntu-latest - needs: [fpm-otel] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - docker-bake.hcl - fpm/** - ${{ matrix.webserver }}/** - dev/** - - - name: Build - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: ./.github/action/build-bake-publish - with: - targets: ${{ matrix.webserver}}-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - dev: - name: Dev ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [webserver-otel] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - docker-bake.hcl - fpm/** - ${{ matrix.webserver }}/** - dev/** - - - name: Build - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: ./.github/action/build-bake-publish - with: - targets: ${{ matrix.webserver }}-dev - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - dev-check: - name: Check Dev Image - runs-on: ubuntu-latest - needs: [dev] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - docker-bake.hcl - fpm/** - nginx/** - caddy/** - dev/** - - - name: Install a binary from GitHub releases - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: jaxxstorm/action-install-gh-release@v3.0.0 - with: - repo: GoogleContainerTools/container-structure-test - extension-matching: disable - rename-to: container-structure-test - chmod: 0755 - - - name: Determine image tag - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - id: image-tag - run: | - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "IMAGE_TAG=ghcr.io/shopware/docker-dev-ci-test:${{ github.event.number }}-php8.4-node24-caddy" >> $GITHUB_OUTPUT - else - echo "IMAGE_TAG=ghcr.io/shopware/docker-dev:php8.4-node24-caddy" >> $GITHUB_OUTPUT - fi - - - name: Pull image - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - run: docker pull ${{ steps.image-tag.outputs.IMAGE_TAG }} - - - name: Test Dev Image - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - run: container-structure-test test --config dev/config.yaml --image ${{ steps.image-tag.outputs.IMAGE_TAG }} - - check: - name: Test Image with Webserver ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [webserver] - strategy: - matrix: - webserver: - - caddy - - nginx - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Checkout - uses: actions/checkout@v7 - - - name: Get all changed files - id: changed-files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v46 - with: - files: | - ${{ matrix.webserver }}/** - - - name: Checkout example repo - uses: actions/checkout@v7 - with: - repository: shopwareLabs/example-docker-repository - - - name: Build main branch - if: github.ref == 'refs/heads/main' && (steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main') - run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base:8.3-caddy - - - name: Build PR - if: github.ref != 'refs/heads/main' && (steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main') - run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base-ci-test:${{ github.event.number }}-8.3-${{ matrix.webserver}} - - - name: Run image - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - run: docker compose up -d --wait - - - name: Wait for Webserver reachable - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - run: | - attempt_counter=0 - max_attempts=5 - - until $(curl --output /dev/null --silent --head --fail localhost:8000/admin); do - if [ ${attempt_counter} -eq ${max_attempts} ];then - echo "Max attempts reached" - exit 1 - fi - - printf '.' - attempt_counter=$(($attempt_counter+1)) - sleep 5 - done - - - name: Check if shopware admin is running - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - run: curl --fail localhost:8000/admin - - - name: Check if shopware is running - if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main' - run: curl --fail localhost:8000 - - # output logs if failed - - name: Output logs - if: always() && (steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'push' && github.ref == 'refs/heads/main') - run: docker compose logs diff --git a/.github/workflows/update-php-matrix.yml b/.github/workflows/update-php-matrix.yml index 031a677..8199516 100644 --- a/.github/workflows/update-php-matrix.yml +++ b/.github/workflows/update-php-matrix.yml @@ -33,10 +33,10 @@ jobs: id: changes run: | if git diff --quiet; then - echo "changed=false" >> $GITHUB_OUTPUT + echo "changed=false" >> "$GITHUB_OUTPUT" echo "No changes detected" else - echo "changed=true" >> $GITHUB_OUTPUT + echo "changed=true" >> "$GITHUB_OUTPUT" echo "Changes detected" git diff fi @@ -51,9 +51,10 @@ jobs: body: | This PR automatically updates the PHP matrix versions to the latest patch releases. - The following matrices have been updated: - - `phpMatrix`: All supported PHP versions including 8.1 - - `frankenphpMatrix`: All supported PHP versions excluding 8.1 + The shared and family-specific Bake files have been updated: + - `docker-bake.hcl`: Pinned docker-library/php commit + - `docker-bake.fpm.hcl`: Supported PHP patch versions and Alpine version + - `docker-bake.frankenphp.hcl`: Supported PHP patch versions Changes were detected by the automated PHP matrix update workflow. diff --git a/README.md b/README.md index 09ff9a7..f779e91 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,25 @@ This repository contains a base image with Alpine + PHP + (Caddy or Nginx), whic [Documentation can be found here](https://developer.shopware.com/docs/guides/hosting/installation-updates/docker.html) The Nginx, Caddy and FrankenPHP configurations send `script-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'` for static SVG files and existing HTML files under `public/media` and `public/thumbnail`. Application-rendered HTML and bundled HTML applications retain their own response policies. Custom static documents must work without scripts, embedded documents, base URL overrides or form submissions; images and styles remain permitted. Development images inherit the corresponding server configuration. + +## Building images + +Bake settings are split into shared variables in `docker-bake.hcl` and targets +in `docker-bake.fpm.hcl` and `docker-bake.frankenphp.hcl`. Load the shared file +alongside the family you want to build: + +```sh +docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl fpm +docker buildx bake -f docker-bake.hcl -f docker-bake.frankenphp.hcl frankenphp +``` + +FPM targets include `fpm`, `fpm-otel`, `caddy`, `caddy-otel`, `nginx`, +`nginx-otel`, `caddy-dev` and `nginx-dev`. FrankenPHP targets are `frankenphp` +and `frankenphp-otel`. Add `--print` to inspect the build configuration. + +Derived images use published base images. When changing a base image, build and +push it before building its dependents, as the CI workflows do. + +The FPM and FrankenPHP workflows build independently when their files change. +Changes to shared Bake settings or the publishing action trigger both families. +Both workflows also support manual runs and daily rebuilds. diff --git a/docker-bake.fpm.hcl b/docker-bake.fpm.hcl new file mode 100644 index 0000000..97309f6 --- /dev/null +++ b/docker-bake.fpm.hcl @@ -0,0 +1,196 @@ +variable "phpMatrix" { + default = [ "8.2.35", "8.3.36", "8.4.27", "8.5.12" ] +} + +variable "alpineVersion" { + default = "3.24" +} + +# PHP built from source with the upstream docker-library/php Dockerfiles + +target "php-fpm-alpine" { + name = "php-fpm-alpine-${replace(substr(php, 0, 3), ".", "-")}" + context = "https://github.com/docker-library/php.git#${phpLibraryRef}:${substr(php, 0, 3)}/alpine${alpineVersion}/fpm" + matrix = { + "php" = phpMatrix + } + platforms = [ "linux/amd64", "linux/arm64" ] +} + +# FPM + +target "fpm" { + name = "fpm-${replace(substr(php, 0, 3), ".", "-")}" + context = "./fpm" + matrix = { + "php" = phpMatrix + } + contexts = { + base = "target:php-fpm-alpine-${replace(substr(php, 0, 3), ".", "-")}" + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = imageSuffix != "" ? [ + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" + ] : [ + "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm", + "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm", + + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" + ] +} + +target "fpm-otel" { + name = "fpm-otel-${replace(substr(php, 0, 3), ".", "-")}" + context = "./fpm-otel" + matrix = { + "php" = phpMatrix + } + contexts = { + base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = imageSuffix != "" ? [ + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm-otel", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" + ] : [ + "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm-otel", + "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel", + + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm-otel", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" + ] +} + +# Caddy + +target "caddy" { + name = "caddy-${replace(substr(php, 0, 3), ".", "-")}" + context = "./caddy" + matrix = { + "php" = phpMatrix + } + contexts = { + base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = imageSuffix != "" ? [ + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy" + ] : [ + "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy", + "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy", + + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy" + ] +} + +target "caddy-otel" { + name = "caddy-otel-${replace(substr(php, 0, 3), ".", "-")}" + context = "./caddy" + matrix = { + "php" = phpMatrix + } + contexts = { + base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = imageSuffix != "" ? [ + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy-otel", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel" + ] : [ + "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy-otel", + "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel", + + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy-otel", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel" + ] +} + +target "caddy-dev" { + name = "caddy-dev-${replace(substr(php, 0, 3), ".", "-")}-${node}" + context = "./dev" + matrix = { + "php" = phpMatrix + "node" = [ "22", "24" ] + } + contexts = { + base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel" + } + args = { + "NODE_VERSION" = node + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = [ + "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${substr(php, 0, 3)}-node${node}-caddy", + "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${php}-node${node}-caddy" + ] +} + +# Nginx + +target "nginx" { + name = "nginx-${replace(substr(php, 0, 3), ".", "-")}" + context = "./nginx" + matrix = { + "php" = phpMatrix + } + contexts = { + base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = imageSuffix != "" ? [ + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx" + ] : [ + "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx", + "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx", + + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx" + ] +} + +target "nginx-otel" { + name = "nginx-otel-${replace(substr(php, 0, 3), ".", "-")}" + context = "./nginx" + matrix = { + "php" = phpMatrix + } + contexts = { + base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = imageSuffix != "" ? [ + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx-otel", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel" + ] : [ + "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx-otel", + "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel", + + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx-otel", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel" + ] +} + +target "nginx-dev" { + name = "nginx-dev-${replace(substr(php, 0, 3), ".", "-")}-${node}" + context = "./dev" + matrix = { + "php" = phpMatrix + "node" = [ "22", "24" ] + } + contexts = { + base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel" + } + args = { + "NODE_VERSION" = node + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = [ + "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${substr(php, 0, 3)}-node${node}-nginx", + "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${php}-node${node}-nginx" + ] +} diff --git a/docker-bake.frankenphp.hcl b/docker-bake.frankenphp.hcl new file mode 100644 index 0000000..e07ed98 --- /dev/null +++ b/docker-bake.frankenphp.hcl @@ -0,0 +1,85 @@ +variable "frankenphpMatrix" { + default = [ "8.2.35", "8.3.36", "8.4.27", "8.5.12" ] +} + +variable "frankenphpRef" { + default = "v1.13.1" +} + +variable "goVersion" { + default = "1.27" +} + +# PHP built from source with the upstream docker-library/php Dockerfiles + +target "php-zts-trixie" { + name = "php-zts-trixie-${replace(substr(php, 0, 3), ".", "-")}" + context = "https://github.com/docker-library/php.git#${phpLibraryRef}:${substr(php, 0, 3)}/trixie/zts" + matrix = { + "php" = frankenphpMatrix + } + platforms = [ "linux/amd64", "linux/arm64" ] +} + +target "frankenphp-upstream" { + name = "frankenphp-upstream-${replace(substr(php, 0, 3), ".", "-")}" + context = "https://github.com/php/frankenphp.git#${frankenphpRef}" + target = "runner" + matrix = { + "php" = frankenphpMatrix + } + contexts = { + php-base = "target:php-zts-trixie-${replace(substr(php, 0, 3), ".", "-")}" + golang-base = "docker-image://docker.io/library/golang:${goVersion}-trixie" + } + args = { + "FRANKENPHP_VERSION" = frankenphpRef + } + platforms = [ "linux/amd64", "linux/arm64" ] +} + +# Frankenphp + +target "frankenphp" { + name = "frankenphp-${replace(substr(php, 0, 3), ".", "-")}" + context = "./frankenphp" + matrix = { + "php" = frankenphpMatrix + } + contexts = { + base = "target:frankenphp-upstream-${replace(substr(php, 0, 3), ".", "-")}" + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = imageSuffix != "" ? [ + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp" + ] : [ + "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp", + "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp", + + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp" + ] +} + +target "frankenphp-otel" { + name = "frankenphp-otel-${replace(substr(php, 0, 3), ".", "-")}" + context = "./frankenphp-otel" + matrix = { + "php" = frankenphpMatrix + } + contexts = { + base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp" + } + platforms = [ "linux/amd64", "linux/arm64" ] + tags = imageSuffix != "" ? [ + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp-otel", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp-otel" + ] : [ + "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp-otel", + "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp-otel", + + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp-otel", + "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp-otel" + ] +} diff --git a/docker-bake.hcl b/docker-bake.hcl index 88e200e..3ad1a6f 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,3 +1,5 @@ +# Shared settings; load with docker-bake.fpm.hcl or docker-bake.frankenphp.hcl. + variable "imageSuffix" { default = "" } @@ -6,287 +8,6 @@ variable "tagPrefix" { default = "" } -variable "phpMatrix" { - default = [ "8.2.35", "8.3.36", "8.4.27", "8.5.12" ] -} - -variable "frankenphpMatrix" { - default = [ "8.2.35", "8.3.36", "8.4.27", "8.5.12" ] -} - variable "phpLibraryRef" { default = "e3eaff845f7a9f7259c31da54dce0320b47a3bbd" } - -variable "alpineVersion" { - default = "3.24" -} - -variable "frankenphpRef" { - default = "v1.13.1" -} - -variable "goVersion" { - default = "1.27" -} - -# PHP built from source with the upstream docker-library/php Dockerfiles - -target "php-fpm-alpine" { - name = "php-fpm-alpine-${replace(substr(php, 0, 3), ".", "-")}" - context = "https://github.com/docker-library/php.git#${phpLibraryRef}:${substr(php, 0, 3)}/alpine${alpineVersion}/fpm" - matrix = { - "php" = phpMatrix - } - platforms = [ "linux/amd64", "linux/arm64" ] -} - -target "php-zts-trixie" { - name = "php-zts-trixie-${replace(substr(php, 0, 3), ".", "-")}" - context = "https://github.com/docker-library/php.git#${phpLibraryRef}:${substr(php, 0, 3)}/trixie/zts" - matrix = { - "php" = frankenphpMatrix - } - platforms = [ "linux/amd64", "linux/arm64" ] -} - -target "frankenphp-upstream" { - name = "frankenphp-upstream-${replace(substr(php, 0, 3), ".", "-")}" - context = "https://github.com/php/frankenphp.git#${frankenphpRef}" - target = "runner" - matrix = { - "php" = frankenphpMatrix - } - contexts = { - php-base = "target:php-zts-trixie-${replace(substr(php, 0, 3), ".", "-")}" - golang-base = "docker-image://docker.io/library/golang:${goVersion}-trixie" - } - args = { - "FRANKENPHP_VERSION" = frankenphpRef - } - platforms = [ "linux/amd64", "linux/arm64" ] -} - -# Frankenphp - -target "frankenphp" { - name = "frankenphp-${replace(substr(php, 0, 3), ".", "-")}" - context = "./frankenphp" - matrix = { - "php" = frankenphpMatrix - } - contexts = { - base = "target:frankenphp-upstream-${replace(substr(php, 0, 3), ".", "-")}" - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = imageSuffix != "" ? [ - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp" - ] : [ - "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp", - "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp", - - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp" - ] -} - -target "frankenphp-otel" { - name = "frankenphp-otel-${replace(substr(php, 0, 3), ".", "-")}" - context = "./frankenphp-otel" - matrix = { - "php" = frankenphpMatrix - } - contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp" - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = imageSuffix != "" ? [ - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp-otel", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp-otel" - ] : [ - "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp-otel", - "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp-otel", - - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-frankenphp-otel", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp-otel" - ] -} - -# FPM - -target "fpm" { - name = "fpm-${replace(substr(php, 0, 3), ".", "-")}" - context = "./fpm" - matrix = { - "php" = phpMatrix - } - contexts = { - base = "target:php-fpm-alpine-${replace(substr(php, 0, 3), ".", "-")}" - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = imageSuffix != "" ? [ - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" - ] : [ - "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm", - "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm", - - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" - ] -} - -target "fpm-otel" { - name = "fpm-otel-${replace(substr(php, 0, 3), ".", "-")}" - context = "./fpm-otel" - matrix = { - "php" = phpMatrix - } - contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = imageSuffix != "" ? [ - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm-otel", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" - ] : [ - "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm-otel", - "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel", - - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-fpm-otel", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" - ] -} - -# Caddy - -target "caddy" { - name = "caddy-${replace(substr(php, 0, 3), ".", "-")}" - context = "./caddy" - matrix = { - "php" = phpMatrix - } - contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = imageSuffix != "" ? [ - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy" - ] : [ - "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy", - "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy", - - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy" - ] -} - -target "caddy-otel" { - name = "caddy-otel-${replace(substr(php, 0, 3), ".", "-")}" - context = "./caddy" - matrix = { - "php" = phpMatrix - } - contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = imageSuffix != "" ? [ - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy-otel", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel" - ] : [ - "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy-otel", - "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel", - - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-caddy-otel", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel" - ] -} - -target "caddy-dev" { - name = "caddy-dev-${replace(substr(php, 0, 3), ".", "-")}-${node}" - context = "./dev" - matrix = { - "php" = phpMatrix - "node" = [ "22", "24" ] - } - contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel" - } - args = { - "NODE_VERSION" = node - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = [ - "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${substr(php, 0, 3)}-node${node}-caddy", - "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${php}-node${node}-caddy" - ] -} - -# Nginx - -target "nginx" { - name = "nginx-${replace(substr(php, 0, 3), ".", "-")}" - context = "./nginx" - matrix = { - "php" = phpMatrix - } - contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = imageSuffix != "" ? [ - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx" - ] : [ - "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx", - "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx", - - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx" - ] -} - -target "nginx-otel" { - name = "nginx-otel-${replace(substr(php, 0, 3), ".", "-")}" - context = "./nginx" - matrix = { - "php" = phpMatrix - } - contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = imageSuffix != "" ? [ - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx-otel", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel" - ] : [ - "shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx-otel", - "shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel", - - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${substr(php, 0, 3)}-nginx-otel", - "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel" - ] -} - -target "nginx-dev" { - name = "nginx-dev-${replace(substr(php, 0, 3), ".", "-")}-${node}" - context = "./dev" - matrix = { - "php" = phpMatrix - "node" = [ "22", "24" ] - } - contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel" - } - args = { - "NODE_VERSION" = node - } - platforms = [ "linux/amd64", "linux/arm64" ] - tags = [ - "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${substr(php, 0, 3)}-node${node}-nginx", - "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${php}-node${node}-nginx" - ] -} diff --git a/update-php-matrix.mjs b/update-php-matrix.mjs index b189d6f..5524301 100755 --- a/update-php-matrix.mjs +++ b/update-php-matrix.mjs @@ -54,24 +54,29 @@ function replaceVariable(hclContent, name, value) { const regex = new RegExp(`(variable "${name}" \\{\\s*default = )(\\[[^\\]]*\\]|"[^"]*")(\\s*\\})`, 's'); if (!regex.test(hclContent)) { - throw new Error(`${name} variable not found in docker-bake.hcl`); + throw new Error(`${name} variable not found in Bake configuration`); } return hclContent.replace(regex, `$1${value}$3`); } -async function updateBakeFile(phpVersions, phpLibraryRef, alpineVersion) { - const hclPath = 'docker-bake.hcl'; - let hclContent = await fs.readFile(hclPath, 'utf8'); - +async function updateBakeFiles(phpVersions, phpLibraryRef, alpineVersion) { const matrix = `[ ${phpVersions.map(v => `"${v}"`).join(', ')} ]`; - hclContent = replaceVariable(hclContent, 'phpMatrix', matrix); - hclContent = replaceVariable(hclContent, 'frankenphpMatrix', matrix); - hclContent = replaceVariable(hclContent, 'phpLibraryRef', `"${phpLibraryRef}"`); - hclContent = replaceVariable(hclContent, 'alpineVersion', `"${alpineVersion}"`); + const updates = { + 'docker-bake.hcl': { phpLibraryRef: `"${phpLibraryRef}"` }, + 'docker-bake.fpm.hcl': { phpMatrix: matrix, alpineVersion: `"${alpineVersion}"` }, + 'docker-bake.frankenphp.hcl': { frankenphpMatrix: matrix }, + }; + + for (const [hclPath, variables] of Object.entries(updates)) { + let hclContent = await fs.readFile(hclPath, 'utf8'); + for (const [name, value] of Object.entries(variables)) { + hclContent = replaceVariable(hclContent, name, value); + } - await fs.writeFile(hclPath, hclContent); - console.log('Successfully updated docker-bake.hcl'); + await fs.writeFile(hclPath, hclContent); + console.log(`Successfully updated ${hclPath}`); + } console.log('PHP versions:', phpVersions); console.log('docker-library/php ref:', phpLibraryRef); console.log('Alpine version:', alpineVersion); @@ -80,4 +85,4 @@ async function updateBakeFile(phpVersions, phpLibraryRef, alpineVersion) { const phpLibraryRef = await fetchPhpLibraryRef(); const versions = await fetchVersions(phpLibraryRef); -await updateBakeFile(getPhpVersions(versions), phpLibraryRef, getLatestAlpineVersion(versions)); +await updateBakeFiles(getPhpVersions(versions), phpLibraryRef, getLatestAlpineVersion(versions)); From e3a395b0b5a745df7b6f920a05973c639c55db8c Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Thu, 22 Oct 2026 10:00:34 +0200 Subject: [PATCH 2/7] ci: split builds by PHP minor and enable layer caching --- .github/action/build-bake-publish/action.yml | 5 +- .github/workflows/build-fpm-version.yml | 223 ++++++++++++++++++ .github/workflows/build-fpm.yml | 210 ++--------------- .../workflows/build-frankenphp-version.yml | 59 +++++ .github/workflows/build-frankenphp.yml | 57 ++--- README.md | 12 + docker-bake.fpm.hcl | 18 +- docker-bake.frankenphp.hcl | 8 +- docker-bake.hcl | 5 + 9 files changed, 361 insertions(+), 236 deletions(-) create mode 100644 .github/workflows/build-fpm-version.yml create mode 100644 .github/workflows/build-frankenphp-version.yml diff --git a/.github/action/build-bake-publish/action.yml b/.github/action/build-bake-publish/action.yml index 055681c..d20847b 100644 --- a/.github/action/build-bake-publish/action.yml +++ b/.github/action/build-bake-publish/action.yml @@ -1,6 +1,9 @@ name: 'Build and Publish' description: 'Builds Image and pushes' inputs: + php-version: + description: 'PHP minor version to build' + required: true files: description: 'Bake files to load' required: true @@ -40,9 +43,9 @@ runs: push: true files: ${{ inputs.files }} targets: ${{ inputs.targets }} - no-cache: true env: DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_SUMMARY: false + phpMinor: ${{ inputs.php-version }} imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} diff --git a/.github/workflows/build-fpm-version.yml b/.github/workflows/build-fpm-version.yml new file mode 100644 index 0000000..5aa77f5 --- /dev/null +++ b/.github/workflows/build-fpm-version.yml @@ -0,0 +1,223 @@ +name: Build FPM PHP minor + +on: + workflow_call: + inputs: + php-version: + description: PHP minor version to build + required: true + type: string + +env: + DOCKER_BUILDKIT: 1 + COSIGN_EXPERIMENTAL: 1 + +permissions: + contents: write + id-token: write + packages: write + pull-requests: write + +jobs: + fpm: + name: FPM + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: fpm + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + fpm-otel: + name: FPM with OpenTelemetry + runs-on: ubuntu-latest + needs: [fpm] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: fpm-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + webserver: + name: ${{ matrix.webserver }} + runs-on: ubuntu-latest + needs: [fpm] + strategy: + fail-fast: false + matrix: + webserver: [ caddy, nginx ] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver}} + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + webserver-otel: + name: ${{ matrix.webserver }} with OpenTelemetry + runs-on: ubuntu-latest + needs: [fpm-otel] + strategy: + fail-fast: false + matrix: + webserver: [ caddy, nginx ] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver}}-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + dev: + name: Dev ${{ matrix.webserver }} + runs-on: ubuntu-latest + needs: [webserver-otel] + strategy: + fail-fast: false + matrix: + webserver: [ caddy, nginx ] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver }}-dev + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + dev-check: + if: inputs.php-version == '8.4' + name: Check Dev Image + runs-on: ubuntu-latest + needs: [dev] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Install a binary from GitHub releases + uses: jaxxstorm/action-install-gh-release@v3.0.0 + with: + repo: GoogleContainerTools/container-structure-test + extension-matching: disable + rename-to: container-structure-test + chmod: 0755 + + - name: Determine image tag + id: image-tag + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "IMAGE_TAG=ghcr.io/shopware/docker-dev-ci-test:${{ github.event.number }}-php8.4-node24-caddy" >> "$GITHUB_OUTPUT" + else + echo "IMAGE_TAG=ghcr.io/shopware/docker-dev:php8.4-node24-caddy" >> "$GITHUB_OUTPUT" + fi + + - name: Pull image + run: docker pull ${{ steps.image-tag.outputs.IMAGE_TAG }} + + - name: Test Dev Image + run: container-structure-test test --config dev/config.yaml --image ${{ steps.image-tag.outputs.IMAGE_TAG }} + + check: + if: inputs.php-version == '8.3' + name: Test Image with Webserver ${{ matrix.webserver }} + runs-on: ubuntu-latest + needs: [webserver] + strategy: + matrix: + webserver: + - caddy + - nginx + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Checkout + uses: actions/checkout@v7 + + - name: Checkout example repo + uses: actions/checkout@v7 + with: + repository: shopwareLabs/example-docker-repository + + - name: Build main branch + if: github.event_name != 'pull_request' + run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base:8.3-${{ matrix.webserver }} + + - name: Build PR + if: github.event_name == 'pull_request' + run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base-ci-test:${{ github.event.number }}-8.3-${{ matrix.webserver}} + + - name: Run image + run: docker compose up -d --wait + + - name: Wait for Webserver reachable + run: | + attempt_counter=0 + max_attempts=5 + + until curl --output /dev/null --silent --head --fail localhost:8000/admin; do + if [ ${attempt_counter} -eq ${max_attempts} ];then + echo "Max attempts reached" + exit 1 + fi + + printf '.' + attempt_counter=$((attempt_counter+1)) + sleep 5 + done + + - name: Check if shopware admin is running + run: curl --fail localhost:8000/admin + + - name: Check if shopware is running + run: curl --fail localhost:8000 + + # output logs if failed + - name: Output logs + if: always() + run: docker compose logs diff --git a/.github/workflows/build-fpm.yml b/.github/workflows/build-fpm.yml index 147be3b..8125b34 100644 --- a/.github/workflows/build-fpm.yml +++ b/.github/workflows/build-fpm.yml @@ -8,6 +8,7 @@ on: - 'docker-bake.hcl' - 'docker-bake.fpm.hcl' - '.github/workflows/build-fpm.yml' + - '.github/workflows/build-fpm-version.yml' - '.github/action/build-bake-publish/**' - 'fpm/**' - 'fpm-otel/**' @@ -19,6 +20,7 @@ on: - 'docker-bake.hcl' - 'docker-bake.fpm.hcl' - '.github/workflows/build-fpm.yml' + - '.github/workflows/build-fpm-version.yml' - '.github/action/build-bake-publish/**' - 'fpm/**' - 'fpm-otel/**' @@ -32,10 +34,6 @@ concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true -env: - DOCKER_BUILDKIT: 1 - COSIGN_EXPERIMENTAL: 1 - permissions: contents: write id-token: write @@ -43,198 +41,32 @@ permissions: pull-requests: write jobs: - fpm: - name: FPM - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: fpm - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - fpm-otel: - name: FPM with OpenTelemetry - runs-on: ubuntu-latest - needs: [fpm] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: fpm-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - webserver: - name: ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [fpm] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver}} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - webserver-otel: - name: ${{ matrix.webserver }} with OpenTelemetry - runs-on: ubuntu-latest - needs: [fpm-otel] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver}}-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - dev: - name: Dev ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [webserver-otel] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver }}-dev - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - dev-check: - name: Check Dev Image + prepare: + name: PHP versions runs-on: ubuntu-latest - needs: [dev] + outputs: + versions: ${{ steps.php-versions.outputs.versions }} steps: - name: Checkout uses: actions/checkout@v7 - - name: Install a binary from GitHub releases - uses: jaxxstorm/action-install-gh-release@v3.0.0 - with: - repo: GoogleContainerTools/container-structure-test - extension-matching: disable - rename-to: container-structure-test - chmod: 0755 - - - name: Determine image tag - id: image-tag - run: | - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "IMAGE_TAG=ghcr.io/shopware/docker-dev-ci-test:${{ github.event.number }}-php8.4-node24-caddy" >> "$GITHUB_OUTPUT" - else - echo "IMAGE_TAG=ghcr.io/shopware/docker-dev:php8.4-node24-caddy" >> "$GITHUB_OUTPUT" - fi - - - name: Pull image - run: docker pull ${{ steps.image-tag.outputs.IMAGE_TAG }} - - - name: Test Dev Image - run: container-structure-test test --config dev/config.yaml --image ${{ steps.image-tag.outputs.IMAGE_TAG }} - - check: - name: Test Image with Webserver ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [webserver] - strategy: - matrix: - webserver: - - caddy - - nginx - steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - - name: Checkout - uses: actions/checkout@v7 - - - name: Checkout example repo - uses: actions/checkout@v7 - with: - repository: shopwareLabs/example-docker-repository - - - name: Build main branch - if: github.event_name != 'pull_request' - run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base:8.3-${{ matrix.webserver }} - - - name: Build PR - if: github.event_name == 'pull_request' - run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base-ci-test:${{ github.event.number }}-8.3-${{ matrix.webserver}} - - - name: Run image - run: docker compose up -d --wait - - - name: Wait for Webserver reachable + - name: Read PHP versions from Bake + id: php-versions run: | - attempt_counter=0 - max_attempts=5 - - until curl --output /dev/null --silent --head --fail localhost:8000/admin; do - if [ ${attempt_counter} -eq ${max_attempts} ];then - echo "Max attempts reached" - exit 1 - fi - - printf '.' - attempt_counter=$((attempt_counter+1)) - sleep 5 - done - - - name: Check if shopware admin is running - run: curl --fail localhost:8000/admin + versions=$(docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl --print fpm | jq -c '.group["fpm"].targets | map(ltrimstr("fpm-") | gsub("-"; "."))') + echo "versions=$versions" >> "$GITHUB_OUTPUT" - - name: Check if shopware is running - run: curl --fail localhost:8000 - - # output logs if failed - - name: Output logs - if: always() - run: docker compose logs + build: + name: PHP ${{ matrix.php-version }} + needs: [prepare] + strategy: + fail-fast: false + matrix: + php-version: ${{ fromJSON(needs.prepare.outputs.versions) }} + uses: ./.github/workflows/build-fpm-version.yml + with: + php-version: ${{ matrix.php-version }} + secrets: inherit diff --git a/.github/workflows/build-frankenphp-version.yml b/.github/workflows/build-frankenphp-version.yml new file mode 100644 index 0000000..910ad92 --- /dev/null +++ b/.github/workflows/build-frankenphp-version.yml @@ -0,0 +1,59 @@ +name: Build FrankenPHP PHP minor + +on: + workflow_call: + inputs: + php-version: + description: PHP minor version to build + required: true + type: string + +env: + DOCKER_BUILDKIT: 1 + COSIGN_EXPERIMENTAL: 1 + +permissions: + contents: write + id-token: write + packages: write + pull-requests: write + +jobs: + frankenphp: + name: FrankenPHP + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.frankenphp.hcl + targets: frankenphp + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + frankenphp-otel: + name: FrankenPHP with OpenTelemetry + runs-on: namespace-profile-default + needs: [frankenphp] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.frankenphp.hcl + targets: frankenphp-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-frankenphp.yml b/.github/workflows/build-frankenphp.yml index 87209f7..2bded2a 100644 --- a/.github/workflows/build-frankenphp.yml +++ b/.github/workflows/build-frankenphp.yml @@ -8,6 +8,7 @@ on: - 'docker-bake.hcl' - 'docker-bake.frankenphp.hcl' - '.github/workflows/build-frankenphp.yml' + - '.github/workflows/build-frankenphp-version.yml' - '.github/action/build-bake-publish/**' - 'frankenphp/**' - 'frankenphp-otel/**' @@ -16,6 +17,7 @@ on: - 'docker-bake.hcl' - 'docker-bake.frankenphp.hcl' - '.github/workflows/build-frankenphp.yml' + - '.github/workflows/build-frankenphp-version.yml' - '.github/action/build-bake-publish/**' - 'frankenphp/**' - 'frankenphp-otel/**' @@ -26,10 +28,6 @@ concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true -env: - DOCKER_BUILDKIT: 1 - COSIGN_EXPERIMENTAL: 1 - permissions: contents: write id-token: write @@ -37,39 +35,32 @@ permissions: pull-requests: write jobs: - frankenphp: - name: FrankenPHP + prepare: + name: PHP versions runs-on: ubuntu-latest + outputs: + versions: ${{ steps.php-versions.outputs.versions }} steps: - name: Checkout uses: actions/checkout@v7 - - name: Build - uses: ./.github/action/build-bake-publish - with: - files: | - docker-bake.hcl - docker-bake.frankenphp.hcl - targets: frankenphp - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 - frankenphp-otel: - name: FrankenPHP with OpenTelemetry - runs-on: namespace-profile-default - needs: [frankenphp] - steps: - - name: Checkout - uses: actions/checkout@v7 + - name: Read PHP versions from Bake + id: php-versions + run: | + versions=$(docker buildx bake -f docker-bake.hcl -f docker-bake.frankenphp.hcl --print frankenphp | jq -c '.group["frankenphp"].targets | map(ltrimstr("frankenphp-") | gsub("-"; "."))') + echo "versions=$versions" >> "$GITHUB_OUTPUT" - - name: Build - uses: ./.github/action/build-bake-publish - with: - files: | - docker-bake.hcl - docker-bake.frankenphp.hcl - targets: frankenphp-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + build: + name: PHP ${{ matrix.php-version }} + needs: [prepare] + strategy: + fail-fast: false + matrix: + php-version: ${{ fromJSON(needs.prepare.outputs.versions) }} + uses: ./.github/workflows/build-frankenphp-version.yml + with: + php-version: ${{ matrix.php-version }} + secrets: inherit diff --git a/README.md b/README.md index f779e91..75239ec 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,15 @@ push it before building its dependents, as the CI workflows do. The FPM and FrankenPHP workflows build independently when their files change. Changes to shared Bake settings or the publishing action trigger both families. Both workflows also support manual runs and daily rebuilds. + +Each workflow derives its PHP minor-version matrix from Bake and runs an independent +build chain per minor version. Failures in one PHP version do not block the others. +Both CPU architectures remain in each build. The existing application smoke tests +run on PHP 8.3, and the dev image check runs on PHP 8.4. + +To build only one PHP minor version locally: + +```sh +phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl fpm +phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.frankenphp.hcl frankenphp +``` diff --git a/docker-bake.fpm.hcl b/docker-bake.fpm.hcl index 97309f6..83201a1 100644 --- a/docker-bake.fpm.hcl +++ b/docker-bake.fpm.hcl @@ -12,7 +12,7 @@ target "php-fpm-alpine" { name = "php-fpm-alpine-${replace(substr(php, 0, 3), ".", "-")}" context = "https://github.com/docker-library/php.git#${phpLibraryRef}:${substr(php, 0, 3)}/alpine${alpineVersion}/fpm" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } platforms = [ "linux/amd64", "linux/arm64" ] } @@ -23,7 +23,7 @@ target "fpm" { name = "fpm-${replace(substr(php, 0, 3), ".", "-")}" context = "./fpm" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { base = "target:php-fpm-alpine-${replace(substr(php, 0, 3), ".", "-")}" @@ -45,7 +45,7 @@ target "fpm-otel" { name = "fpm-otel-${replace(substr(php, 0, 3), ".", "-")}" context = "./fpm-otel" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" @@ -69,7 +69,7 @@ target "caddy" { name = "caddy-${replace(substr(php, 0, 3), ".", "-")}" context = "./caddy" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" @@ -91,7 +91,7 @@ target "caddy-otel" { name = "caddy-otel-${replace(substr(php, 0, 3), ".", "-")}" context = "./caddy" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" @@ -113,7 +113,7 @@ target "caddy-dev" { name = "caddy-dev-${replace(substr(php, 0, 3), ".", "-")}-${node}" context = "./dev" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] "node" = [ "22", "24" ] } contexts = { @@ -135,7 +135,7 @@ target "nginx" { name = "nginx-${replace(substr(php, 0, 3), ".", "-")}" context = "./nginx" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" @@ -157,7 +157,7 @@ target "nginx-otel" { name = "nginx-otel-${replace(substr(php, 0, 3), ".", "-")}" context = "./nginx" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" @@ -179,7 +179,7 @@ target "nginx-dev" { name = "nginx-dev-${replace(substr(php, 0, 3), ".", "-")}-${node}" context = "./dev" matrix = { - "php" = phpMatrix + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] "node" = [ "22", "24" ] } contexts = { diff --git a/docker-bake.frankenphp.hcl b/docker-bake.frankenphp.hcl index e07ed98..89576e7 100644 --- a/docker-bake.frankenphp.hcl +++ b/docker-bake.frankenphp.hcl @@ -16,7 +16,7 @@ target "php-zts-trixie" { name = "php-zts-trixie-${replace(substr(php, 0, 3), ".", "-")}" context = "https://github.com/docker-library/php.git#${phpLibraryRef}:${substr(php, 0, 3)}/trixie/zts" matrix = { - "php" = frankenphpMatrix + "php" = [for php in frankenphpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } platforms = [ "linux/amd64", "linux/arm64" ] } @@ -26,7 +26,7 @@ target "frankenphp-upstream" { context = "https://github.com/php/frankenphp.git#${frankenphpRef}" target = "runner" matrix = { - "php" = frankenphpMatrix + "php" = [for php in frankenphpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { php-base = "target:php-zts-trixie-${replace(substr(php, 0, 3), ".", "-")}" @@ -44,7 +44,7 @@ target "frankenphp" { name = "frankenphp-${replace(substr(php, 0, 3), ".", "-")}" context = "./frankenphp" matrix = { - "php" = frankenphpMatrix + "php" = [for php in frankenphpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { base = "target:frankenphp-upstream-${replace(substr(php, 0, 3), ".", "-")}" @@ -66,7 +66,7 @@ target "frankenphp-otel" { name = "frankenphp-otel-${replace(substr(php, 0, 3), ".", "-")}" context = "./frankenphp-otel" matrix = { - "php" = frankenphpMatrix + "php" = [for php in frankenphpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp" diff --git a/docker-bake.hcl b/docker-bake.hcl index 3ad1a6f..f0963f9 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -11,3 +11,8 @@ variable "tagPrefix" { variable "phpLibraryRef" { default = "e3eaff845f7a9f7259c31da54dce0320b47a3bbd" } + +# Leave empty to build all supported PHP minor versions. +variable "phpMinor" { + default = "" +} From 3213ddea99447cc7014cd1977bb813a3a05ea5ce Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Thu, 22 Oct 2026 10:55:14 +0200 Subject: [PATCH 3/7] refactor: consolidate build workflows and introduce metadata handling --- .github/action/build-bake-publish/action.yml | 51 ------- .github/scripts/bake-metadata.mjs | 48 ++++++ .github/scripts/bake-metadata.test.mjs | 67 +++++++++ .github/workflows/build-fpm-version.yml | 137 +++++++----------- .github/workflows/build-fpm.yml | 12 +- .../workflows/build-frankenphp-version.yml | 51 ++----- .github/workflows/build-frankenphp.yml | 12 +- .github/workflows/build-image.yml | 87 +++++++++++ README.md | 24 ++- 9 files changed, 304 insertions(+), 185 deletions(-) delete mode 100644 .github/action/build-bake-publish/action.yml create mode 100644 .github/scripts/bake-metadata.mjs create mode 100644 .github/scripts/bake-metadata.test.mjs create mode 100644 .github/workflows/build-image.yml diff --git a/.github/action/build-bake-publish/action.yml b/.github/action/build-bake-publish/action.yml deleted file mode 100644 index d20847b..0000000 --- a/.github/action/build-bake-publish/action.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: 'Build and Publish' -description: 'Builds Image and pushes' -inputs: - php-version: - description: 'PHP minor version to build' - required: true - files: - description: 'Bake files to load' - required: true - targets: - description: 'Targets' - required: true - default: '' - docker_hub_username: - description: 'Docker Hub username' - required: true - docker_hub_password: - description: 'Docker Hub password' - required: true - github_token: - description: 'GitHub token' - required: true -runs: - using: "composite" - steps: - - name: Install and configure Namespace CLI - uses: namespacelabs/nscloud-setup@v0 - - - name: Configure Namespace powered Buildx - uses: namespacelabs/nscloud-setup-buildx-action@v0 - - - name: Login into Docker Hub - shell: bash - run: echo "${{ inputs.docker_hub_password }}" | docker login -u ${{ inputs.docker_hub_username }} --password-stdin - - - name: Login into Github Docker Registry - shell: bash - run: echo "${{ inputs.github_token }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - - name: Build and push - uses: docker/bake-action@v6 - with: - push: true - files: ${{ inputs.files }} - targets: ${{ inputs.targets }} - env: - DOCKER_BUILD_RECORD_UPLOAD: false - DOCKER_BUILD_SUMMARY: false - phpMinor: ${{ inputs.php-version }} - imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} - tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} diff --git a/.github/scripts/bake-metadata.mjs b/.github/scripts/bake-metadata.mjs new file mode 100644 index 0000000..2616a2c --- /dev/null +++ b/.github/scripts/bake-metadata.mjs @@ -0,0 +1,48 @@ +import { readFileSync } from 'node:fs'; +import { pathToFileURL } from 'node:url'; + +// Docker GitHub Builder publishes the Cartesian product of images and tags. +// Resolve one output target and reject tag sets that would change that product. +export function getMetadata(definition, requestedTarget) { + const targets = definition.group?.[requestedTarget]?.targets ?? [requestedTarget]; + if (targets.length !== 1) { + throw new Error(`Expected one output target for ${requestedTarget}, found ${targets.length}`); + } + + const target = targets[0]; + const tags = definition.target?.[target]?.tags; + if (!tags?.length) { + throw new Error(`No image tags found for ${target}`); + } + + const images = new Map(); + for (const reference of tags) { + const match = reference.match(/^([a-z0-9._:/-]+):([\w][\w.-]{0,127})$/); + if (!match) { + throw new Error(`Invalid tagged image reference: ${reference}`); + } + const [, image, tag] = match; + if (!images.has(image)) { + images.set(image, new Set()); + } + images.get(image).add(tag); + } + + const tagNames = [...images.values().next().value]; + for (const imageTags of images.values()) { + if (imageTags.size !== tagNames.length || tagNames.some(tag => !imageTags.has(tag))) { + throw new Error(`Registries must have the same tags for ${target}`); + } + } + + return { + target, + images: [...images.keys()].join('\n'), + tags: tagNames.map(tag => `type=raw,value=${tag}`).join('\n'), + }; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + const definition = JSON.parse(readFileSync(0, 'utf8')); + console.log(`metadata=${JSON.stringify(getMetadata(definition, process.argv[2]))}`); +} diff --git a/.github/scripts/bake-metadata.test.mjs b/.github/scripts/bake-metadata.test.mjs new file mode 100644 index 0000000..2c17a55 --- /dev/null +++ b/.github/scripts/bake-metadata.test.mjs @@ -0,0 +1,67 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { getMetadata } from './bake-metadata.mjs'; + +test('resolves a single-target group without publishing its base target', () => { + const definition = { + group: { fpm: { targets: ['fpm-8-4'] } }, + target: { + 'php-fpm-alpine-8-4': { context: 'https://example.com/php.git' }, + 'fpm-8-4': { + contexts: { base: 'target:php-fpm-alpine-8-4' }, + tags: [ + 'shopware/docker-base:8.4-fpm', + 'shopware/docker-base:8.4.27-fpm', + 'ghcr.io/shopware/docker-base:8.4-fpm', + 'ghcr.io/shopware/docker-base:8.4.27-fpm', + ], + }, + }, + }; + assert.deepEqual(getMetadata(definition, 'fpm'), { + target: 'fpm-8-4', + images: 'shopware/docker-base\nghcr.io/shopware/docker-base', + tags: 'type=raw,value=8.4-fpm\ntype=raw,value=8.4.27-fpm', + }); +}); + +test('preserves PR dev tags without adding Docker Hub or latest', () => { + const definition = { + target: { + 'caddy-dev-8-4-24': { + tags: [ + 'ghcr.io/shopware/docker-dev-ci-test:253-php8.4-node24-caddy', + 'ghcr.io/shopware/docker-dev-ci-test:253-php8.4.27-node24-caddy', + ], + }, + }, + }; + assert.deepEqual(getMetadata(definition, 'caddy-dev-8-4-24'), { + target: 'caddy-dev-8-4-24', + images: 'ghcr.io/shopware/docker-dev-ci-test', + tags: 'type=raw,value=253-php8.4-node24-caddy\ntype=raw,value=253-php8.4.27-node24-caddy', + }); +}); + +test('splits registry ports from image tags correctly', () => { + assert.equal(getMetadata({ target: { image: { tags: ['localhost:5000/image:v1'] } } }, 'image').images, 'localhost:5000/image'); +}); + +test('rejects a group containing multiple output images', () => { + assert.throws(() => getMetadata({ group: { dev: { targets: ['node22', 'node24'] } } }, 'dev'), /Expected one output target/); +}); + +test('rejects missing or untagged targets', () => { + assert.throws(() => getMetadata({}, 'missing'), /No image tags/); + assert.throws(() => getMetadata({ target: { base: {} } }, 'base'), /No image tags/); +}); + +test('rejects differing registry tags instead of publishing additional tags', () => { + const definition = { target: { image: { tags: ['shopware/image:v1', 'ghcr.io/shopware/image:v2'] } } }; + assert.throws(() => getMetadata(definition, 'image'), /Registries must have the same tags/); +}); + +test('rejects invalid tags that could be interpreted as metadata options', () => { + const definition = { target: { image: { tags: ['shopware/image:v1,enable=false'] } } }; + assert.throws(() => getMetadata(definition, 'image'), /Invalid tagged image reference/); +}); diff --git a/.github/workflows/build-fpm-version.yml b/.github/workflows/build-fpm-version.yml index 5aa77f5..0abbf1c 100644 --- a/.github/workflows/build-fpm-version.yml +++ b/.github/workflows/build-fpm-version.yml @@ -8,127 +8,92 @@ on: required: true type: string -env: - DOCKER_BUILDKIT: 1 - COSIGN_EXPERIMENTAL: 1 - permissions: - contents: write + contents: read id-token: write packages: write - pull-requests: write jobs: - fpm: - name: FPM + prepare-dev: + name: Dev targets runs-on: ubuntu-latest + outputs: + targets: ${{ steps.dev-targets.outputs.targets }} steps: - name: Checkout uses: actions/checkout@v7 - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: fpm - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Read dev targets from Bake + id: dev-targets + env: + phpMinor: ${{ inputs.php-version }} + run: | + targets=$(docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl --print caddy-dev nginx-dev | jq -c '[.group["caddy-dev"].targets[], .group["nginx-dev"].targets[]]') + echo "targets=$targets" >> "$GITHUB_OUTPUT" + + fpm: + name: FPM + uses: ./.github/workflows/build-image.yml + with: + family: fpm + php-version: ${{ inputs.php-version }} + target: fpm + secrets: inherit fpm-otel: name: FPM with OpenTelemetry - runs-on: ubuntu-latest needs: [fpm] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: fpm-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/build-image.yml + with: + family: fpm + php-version: ${{ inputs.php-version }} + target: fpm-otel + secrets: inherit webserver: name: ${{ matrix.webserver }} - runs-on: ubuntu-latest needs: [fpm] strategy: fail-fast: false matrix: webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver}} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/build-image.yml + with: + family: fpm + php-version: ${{ inputs.php-version }} + target: ${{ matrix.webserver }} + secrets: inherit webserver-otel: name: ${{ matrix.webserver }} with OpenTelemetry - runs-on: ubuntu-latest needs: [fpm-otel] strategy: fail-fast: false matrix: webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver}}-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/build-image.yml + with: + family: fpm + php-version: ${{ inputs.php-version }} + target: ${{ matrix.webserver }}-otel + secrets: inherit dev: - name: Dev ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [webserver-otel] + name: ${{ matrix.target }} + needs: [webserver-otel, prepare-dev] strategy: fail-fast: false matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver }}-dev - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + target: ${{ fromJSON(needs.prepare-dev.outputs.targets) }} + uses: ./.github/workflows/build-image.yml + with: + family: fpm + php-version: ${{ inputs.php-version }} + target: ${{ matrix.target }} + secrets: inherit dev-check: if: inputs.php-version == '8.4' diff --git a/.github/workflows/build-fpm.yml b/.github/workflows/build-fpm.yml index 8125b34..49107ae 100644 --- a/.github/workflows/build-fpm.yml +++ b/.github/workflows/build-fpm.yml @@ -9,7 +9,8 @@ on: - 'docker-bake.fpm.hcl' - '.github/workflows/build-fpm.yml' - '.github/workflows/build-fpm-version.yml' - - '.github/action/build-bake-publish/**' + - '.github/workflows/build-image.yml' + - '.github/scripts/bake-metadata*.mjs' - 'fpm/**' - 'fpm-otel/**' - 'caddy/**' @@ -21,7 +22,8 @@ on: - 'docker-bake.fpm.hcl' - '.github/workflows/build-fpm.yml' - '.github/workflows/build-fpm-version.yml' - - '.github/action/build-bake-publish/**' + - '.github/workflows/build-image.yml' + - '.github/scripts/bake-metadata*.mjs' - 'fpm/**' - 'fpm-otel/**' - 'caddy/**' @@ -35,10 +37,9 @@ concurrency: cancel-in-progress: true permissions: - contents: write + contents: read id-token: write packages: write - pull-requests: write jobs: prepare: @@ -53,6 +54,9 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 + - name: Test Bake metadata conversion + run: node --test .github/scripts/bake-metadata.test.mjs + - name: Read PHP versions from Bake id: php-versions run: | diff --git a/.github/workflows/build-frankenphp-version.yml b/.github/workflows/build-frankenphp-version.yml index 910ad92..a0ba668 100644 --- a/.github/workflows/build-frankenphp-version.yml +++ b/.github/workflows/build-frankenphp-version.yml @@ -8,52 +8,27 @@ on: required: true type: string -env: - DOCKER_BUILDKIT: 1 - COSIGN_EXPERIMENTAL: 1 - permissions: - contents: write + contents: read id-token: write packages: write - pull-requests: write jobs: frankenphp: name: FrankenPHP - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.frankenphp.hcl - targets: frankenphp - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/build-image.yml + with: + family: frankenphp + php-version: ${{ inputs.php-version }} + target: frankenphp + secrets: inherit frankenphp-otel: name: FrankenPHP with OpenTelemetry - runs-on: namespace-profile-default needs: [frankenphp] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.frankenphp.hcl - targets: frankenphp-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/build-image.yml + with: + family: frankenphp + php-version: ${{ inputs.php-version }} + target: frankenphp-otel + secrets: inherit diff --git a/.github/workflows/build-frankenphp.yml b/.github/workflows/build-frankenphp.yml index 2bded2a..909ea5b 100644 --- a/.github/workflows/build-frankenphp.yml +++ b/.github/workflows/build-frankenphp.yml @@ -9,7 +9,8 @@ on: - 'docker-bake.frankenphp.hcl' - '.github/workflows/build-frankenphp.yml' - '.github/workflows/build-frankenphp-version.yml' - - '.github/action/build-bake-publish/**' + - '.github/workflows/build-image.yml' + - '.github/scripts/bake-metadata*.mjs' - 'frankenphp/**' - 'frankenphp-otel/**' pull_request: @@ -18,7 +19,8 @@ on: - 'docker-bake.frankenphp.hcl' - '.github/workflows/build-frankenphp.yml' - '.github/workflows/build-frankenphp-version.yml' - - '.github/action/build-bake-publish/**' + - '.github/workflows/build-image.yml' + - '.github/scripts/bake-metadata*.mjs' - 'frankenphp/**' - 'frankenphp-otel/**' schedule: @@ -29,10 +31,9 @@ concurrency: cancel-in-progress: true permissions: - contents: write + contents: read id-token: write packages: write - pull-requests: write jobs: prepare: @@ -47,6 +48,9 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 + - name: Test Bake metadata conversion + run: node --test .github/scripts/bake-metadata.test.mjs + - name: Read PHP versions from Bake id: php-versions run: | diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml new file mode 100644 index 0000000..01677de --- /dev/null +++ b/.github/workflows/build-image.yml @@ -0,0 +1,87 @@ +name: Build and publish image + +on: + workflow_call: + inputs: + family: + description: Bake family (fpm or frankenphp) + required: true + type: string + php-version: + description: PHP minor version to build + required: true + type: string + target: + description: Concrete Bake target or group resolving to one target + required: true + type: string + secrets: + DOCKER_HUB_USERNAME: + required: true + DOCKER_HUB_PASSWORD: + required: true + +permissions: + contents: read + id-token: write + packages: write + +jobs: + metadata: + name: Resolve image metadata + runs-on: ubuntu-latest + outputs: + target: ${{ fromJSON(steps.metadata.outputs.metadata).target }} + images: ${{ fromJSON(steps.metadata.outputs.metadata).images }} + tags: ${{ fromJSON(steps.metadata.outputs.metadata).tags }} + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Read image metadata from Bake + id: metadata + env: + BAKE_FAMILY: ${{ inputs.family }} + BAKE_TARGET: ${{ inputs.target }} + phpMinor: ${{ inputs.php-version }} + imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} + tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} + run: | + docker buildx bake -f docker-bake.hcl -f "docker-bake.${BAKE_FAMILY}.hcl" --print "$BAKE_TARGET" | + node .github/scripts/bake-metadata.mjs "$BAKE_TARGET" >> "$GITHUB_OUTPUT" + + build: + name: Build + needs: [metadata] + uses: docker/github-builder/.github/workflows/bake.yml@cbc9d42f8c89a55623392aadd03600965b80e963 # v1 + with: + files: | + docker-bake.hcl + docker-bake.${{ inputs.family }}.hcl + target: ${{ needs.metadata.outputs.target }} + vars: | + phpMinor=${{ inputs.php-version }} + imageSuffix=${{ github.event_name == 'pull_request' && '-ci-test' || '' }} + tagPrefix=${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} + output: image + push: true + distribute: true + cache: true + cache-mode: max + cache-scope: ${{ needs.metadata.outputs.target }} + sbom: true + sign: 'true' + meta-images: ${{ needs.metadata.outputs.images }} + meta-tags: ${{ needs.metadata.outputs.tags }} + meta-flavor: latest=false + secrets: + registry-auths: | + - registry: docker.io + username: ${{ toJSON(secrets.DOCKER_HUB_USERNAME) }} + password: ${{ toJSON(secrets.DOCKER_HUB_PASSWORD) }} + - registry: ghcr.io + username: ${{ toJSON(github.actor) }} + password: ${{ toJSON(secrets.GITHUB_TOKEN) }} diff --git a/README.md b/README.md index 75239ec..39457a8 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,14 @@ Derived images use published base images. When changing a base image, build and push it before building its dependents, as the CI workflows do. The FPM and FrankenPHP workflows build independently when their files change. -Changes to shared Bake settings or the publishing action trigger both families. +Changes to shared Bake settings, the publishing workflow or metadata script trigger both families. Both workflows also support manual runs and daily rebuilds. Each workflow derives its PHP minor-version matrix from Bake and runs an independent build chain per minor version. Failures in one PHP version do not block the others. -Both CPU architectures remain in each build. The existing application smoke tests +Each image is built natively on GitHub-hosted AMD64 and ARM64 runners, then published +as a multi-platform image. Dev images are also split by Node version, with their +targets derived from Bake. The existing application smoke tests run on PHP 8.3, and the dev image check runs on PHP 8.4. To build only one PHP minor version locally: @@ -39,3 +41,21 @@ To build only one PHP minor version locally: phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl fpm phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.frankenphp.hcl frankenphp ``` + +CI delegates builds to a pinned revision of +[Docker GitHub Builder](https://github.com/docker/github-builder), through +`.github/workflows/build-image.yml`. It enables GitHub Actions layer caching in +`max` mode, SBOM generation and signed provenance. Cache scopes include the concrete +target name (variant, PHP minor and, for dev images, Node version); the builder adds +the CPU platform. + +Publishing metadata is derived from the Bake tags, preserving both registries, +minor/patch tags and PR image names without adding a `latest` tag. Each call builds +one concrete target, including any `target:` base dependencies. The family workflows +still order separately published base and dependent images. + +To check the publishing metadata conversion locally: + +```sh +node --test .github/scripts/bake-metadata.test.mjs +``` From 8cb92337a566789c2ba7412f31322896190c780f Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Thu, 22 Oct 2026 11:02:19 +0200 Subject: [PATCH 4/7] ci: update Dockerfile syntax version to 1 --- caddy/Dockerfile | 2 +- fpm-otel/Dockerfile | 2 +- fpm/Dockerfile | 2 +- frankenphp-otel/Dockerfile | 2 +- frankenphp/Dockerfile | 4 ++-- nginx/Dockerfile | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/caddy/Dockerfile b/caddy/Dockerfile index 9cdb48e..b21c520 100644 --- a/caddy/Dockerfile +++ b/caddy/Dockerfile @@ -1,4 +1,4 @@ -#syntax=docker/dockerfile:1.4 +#syntax=docker/dockerfile:1 FROM base diff --git a/fpm-otel/Dockerfile b/fpm-otel/Dockerfile index 6f5f1f8..50a30de 100644 --- a/fpm-otel/Dockerfile +++ b/fpm-otel/Dockerfile @@ -1,4 +1,4 @@ -#syntax=docker/dockerfile:1.4 +#syntax=docker/dockerfile:1 FROM base diff --git a/fpm/Dockerfile b/fpm/Dockerfile index 80e4d9b..f37040f 100644 --- a/fpm/Dockerfile +++ b/fpm/Dockerfile @@ -1,4 +1,4 @@ -#syntax=docker/dockerfile:1.4 +#syntax=docker/dockerfile:1 FROM base diff --git a/frankenphp-otel/Dockerfile b/frankenphp-otel/Dockerfile index 0d37ef7..03bbf16 100644 --- a/frankenphp-otel/Dockerfile +++ b/frankenphp-otel/Dockerfile @@ -1,4 +1,4 @@ -#syntax=docker/dockerfile:1.4 +#syntax=docker/dockerfile:1 FROM base diff --git a/frankenphp/Dockerfile b/frankenphp/Dockerfile index 0b7fb31..8dc0cfe 100644 --- a/frankenphp/Dockerfile +++ b/frankenphp/Dockerfile @@ -1,4 +1,4 @@ -#syntax=docker/dockerfile:1.4 +#syntax=docker/dockerfile:1 #check=skip=SecretsUsedInArgOrEnv FROM base @@ -75,4 +75,4 @@ WORKDIR /var/www/html EXPOSE 8000 -CMD ["--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] \ No newline at end of file +CMD ["--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/nginx/Dockerfile b/nginx/Dockerfile index d0a293d..a0a67ec 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,4 +1,4 @@ -#syntax=docker/dockerfile:1.4 +#syntax=docker/dockerfile:1 FROM base From 281e171b1531f849c4548bde12df15237cf7f6a7 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Thu, 22 Oct 2026 11:52:33 +0200 Subject: [PATCH 5/7] refactor: replace build-image workflow with build-bake-publish action and update README --- .github/action/build-bake-publish/action.yml | 51 +++++++ .github/scripts/bake-metadata.mjs | 48 ------- .github/scripts/bake-metadata.test.mjs | 67 --------- .github/workflows/build-fpm-version.yml | 130 +++++++++++------- .github/workflows/build-fpm.yml | 9 +- .../workflows/build-frankenphp-version.yml | 44 ++++-- .github/workflows/build-frankenphp.yml | 9 +- .github/workflows/build-image.yml | 87 ------------ README.md | 27 +--- 9 files changed, 173 insertions(+), 299 deletions(-) create mode 100644 .github/action/build-bake-publish/action.yml delete mode 100644 .github/scripts/bake-metadata.mjs delete mode 100644 .github/scripts/bake-metadata.test.mjs delete mode 100644 .github/workflows/build-image.yml diff --git a/.github/action/build-bake-publish/action.yml b/.github/action/build-bake-publish/action.yml new file mode 100644 index 0000000..d20847b --- /dev/null +++ b/.github/action/build-bake-publish/action.yml @@ -0,0 +1,51 @@ +name: 'Build and Publish' +description: 'Builds Image and pushes' +inputs: + php-version: + description: 'PHP minor version to build' + required: true + files: + description: 'Bake files to load' + required: true + targets: + description: 'Targets' + required: true + default: '' + docker_hub_username: + description: 'Docker Hub username' + required: true + docker_hub_password: + description: 'Docker Hub password' + required: true + github_token: + description: 'GitHub token' + required: true +runs: + using: "composite" + steps: + - name: Install and configure Namespace CLI + uses: namespacelabs/nscloud-setup@v0 + + - name: Configure Namespace powered Buildx + uses: namespacelabs/nscloud-setup-buildx-action@v0 + + - name: Login into Docker Hub + shell: bash + run: echo "${{ inputs.docker_hub_password }}" | docker login -u ${{ inputs.docker_hub_username }} --password-stdin + + - name: Login into Github Docker Registry + shell: bash + run: echo "${{ inputs.github_token }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Build and push + uses: docker/bake-action@v6 + with: + push: true + files: ${{ inputs.files }} + targets: ${{ inputs.targets }} + env: + DOCKER_BUILD_RECORD_UPLOAD: false + DOCKER_BUILD_SUMMARY: false + phpMinor: ${{ inputs.php-version }} + imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} + tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} diff --git a/.github/scripts/bake-metadata.mjs b/.github/scripts/bake-metadata.mjs deleted file mode 100644 index 2616a2c..0000000 --- a/.github/scripts/bake-metadata.mjs +++ /dev/null @@ -1,48 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { pathToFileURL } from 'node:url'; - -// Docker GitHub Builder publishes the Cartesian product of images and tags. -// Resolve one output target and reject tag sets that would change that product. -export function getMetadata(definition, requestedTarget) { - const targets = definition.group?.[requestedTarget]?.targets ?? [requestedTarget]; - if (targets.length !== 1) { - throw new Error(`Expected one output target for ${requestedTarget}, found ${targets.length}`); - } - - const target = targets[0]; - const tags = definition.target?.[target]?.tags; - if (!tags?.length) { - throw new Error(`No image tags found for ${target}`); - } - - const images = new Map(); - for (const reference of tags) { - const match = reference.match(/^([a-z0-9._:/-]+):([\w][\w.-]{0,127})$/); - if (!match) { - throw new Error(`Invalid tagged image reference: ${reference}`); - } - const [, image, tag] = match; - if (!images.has(image)) { - images.set(image, new Set()); - } - images.get(image).add(tag); - } - - const tagNames = [...images.values().next().value]; - for (const imageTags of images.values()) { - if (imageTags.size !== tagNames.length || tagNames.some(tag => !imageTags.has(tag))) { - throw new Error(`Registries must have the same tags for ${target}`); - } - } - - return { - target, - images: [...images.keys()].join('\n'), - tags: tagNames.map(tag => `type=raw,value=${tag}`).join('\n'), - }; -} - -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - const definition = JSON.parse(readFileSync(0, 'utf8')); - console.log(`metadata=${JSON.stringify(getMetadata(definition, process.argv[2]))}`); -} diff --git a/.github/scripts/bake-metadata.test.mjs b/.github/scripts/bake-metadata.test.mjs deleted file mode 100644 index 2c17a55..0000000 --- a/.github/scripts/bake-metadata.test.mjs +++ /dev/null @@ -1,67 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { getMetadata } from './bake-metadata.mjs'; - -test('resolves a single-target group without publishing its base target', () => { - const definition = { - group: { fpm: { targets: ['fpm-8-4'] } }, - target: { - 'php-fpm-alpine-8-4': { context: 'https://example.com/php.git' }, - 'fpm-8-4': { - contexts: { base: 'target:php-fpm-alpine-8-4' }, - tags: [ - 'shopware/docker-base:8.4-fpm', - 'shopware/docker-base:8.4.27-fpm', - 'ghcr.io/shopware/docker-base:8.4-fpm', - 'ghcr.io/shopware/docker-base:8.4.27-fpm', - ], - }, - }, - }; - assert.deepEqual(getMetadata(definition, 'fpm'), { - target: 'fpm-8-4', - images: 'shopware/docker-base\nghcr.io/shopware/docker-base', - tags: 'type=raw,value=8.4-fpm\ntype=raw,value=8.4.27-fpm', - }); -}); - -test('preserves PR dev tags without adding Docker Hub or latest', () => { - const definition = { - target: { - 'caddy-dev-8-4-24': { - tags: [ - 'ghcr.io/shopware/docker-dev-ci-test:253-php8.4-node24-caddy', - 'ghcr.io/shopware/docker-dev-ci-test:253-php8.4.27-node24-caddy', - ], - }, - }, - }; - assert.deepEqual(getMetadata(definition, 'caddy-dev-8-4-24'), { - target: 'caddy-dev-8-4-24', - images: 'ghcr.io/shopware/docker-dev-ci-test', - tags: 'type=raw,value=253-php8.4-node24-caddy\ntype=raw,value=253-php8.4.27-node24-caddy', - }); -}); - -test('splits registry ports from image tags correctly', () => { - assert.equal(getMetadata({ target: { image: { tags: ['localhost:5000/image:v1'] } } }, 'image').images, 'localhost:5000/image'); -}); - -test('rejects a group containing multiple output images', () => { - assert.throws(() => getMetadata({ group: { dev: { targets: ['node22', 'node24'] } } }, 'dev'), /Expected one output target/); -}); - -test('rejects missing or untagged targets', () => { - assert.throws(() => getMetadata({}, 'missing'), /No image tags/); - assert.throws(() => getMetadata({ target: { base: {} } }, 'base'), /No image tags/); -}); - -test('rejects differing registry tags instead of publishing additional tags', () => { - const definition = { target: { image: { tags: ['shopware/image:v1', 'ghcr.io/shopware/image:v2'] } } }; - assert.throws(() => getMetadata(definition, 'image'), /Registries must have the same tags/); -}); - -test('rejects invalid tags that could be interpreted as metadata options', () => { - const definition = { target: { image: { tags: ['shopware/image:v1,enable=false'] } } }; - assert.throws(() => getMetadata(definition, 'image'), /Invalid tagged image reference/); -}); diff --git a/.github/workflows/build-fpm-version.yml b/.github/workflows/build-fpm-version.yml index 0abbf1c..e6e0e77 100644 --- a/.github/workflows/build-fpm-version.yml +++ b/.github/workflows/build-fpm-version.yml @@ -14,86 +14,116 @@ permissions: packages: write jobs: - prepare-dev: - name: Dev targets + fpm: + name: FPM runs-on: ubuntu-latest - outputs: - targets: ${{ steps.dev-targets.outputs.targets }} steps: - name: Checkout uses: actions/checkout@v7 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Read dev targets from Bake - id: dev-targets - env: - phpMinor: ${{ inputs.php-version }} - run: | - targets=$(docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl --print caddy-dev nginx-dev | jq -c '[.group["caddy-dev"].targets[], .group["nginx-dev"].targets[]]') - echo "targets=$targets" >> "$GITHUB_OUTPUT" - - fpm: - name: FPM - uses: ./.github/workflows/build-image.yml - with: - family: fpm - php-version: ${{ inputs.php-version }} - target: fpm - secrets: inherit + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: fpm + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} fpm-otel: name: FPM with OpenTelemetry + runs-on: ubuntu-latest needs: [fpm] - uses: ./.github/workflows/build-image.yml - with: - family: fpm - php-version: ${{ inputs.php-version }} - target: fpm-otel - secrets: inherit + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: fpm-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} webserver: name: ${{ matrix.webserver }} + runs-on: ubuntu-latest needs: [fpm] strategy: fail-fast: false matrix: webserver: [ caddy, nginx ] - uses: ./.github/workflows/build-image.yml - with: - family: fpm - php-version: ${{ inputs.php-version }} - target: ${{ matrix.webserver }} - secrets: inherit + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver}} + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} webserver-otel: name: ${{ matrix.webserver }} with OpenTelemetry + runs-on: ubuntu-latest needs: [fpm-otel] strategy: fail-fast: false matrix: webserver: [ caddy, nginx ] - uses: ./.github/workflows/build-image.yml - with: - family: fpm - php-version: ${{ inputs.php-version }} - target: ${{ matrix.webserver }}-otel - secrets: inherit + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver}}-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} dev: - name: ${{ matrix.target }} - needs: [webserver-otel, prepare-dev] + name: Dev ${{ matrix.webserver }} + runs-on: ubuntu-latest + needs: [webserver-otel] strategy: fail-fast: false matrix: - target: ${{ fromJSON(needs.prepare-dev.outputs.targets) }} - uses: ./.github/workflows/build-image.yml - with: - family: fpm - php-version: ${{ inputs.php-version }} - target: ${{ matrix.target }} - secrets: inherit + webserver: [ caddy, nginx ] + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: ${{ matrix.webserver }}-dev + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} dev-check: if: inputs.php-version == '8.4' diff --git a/.github/workflows/build-fpm.yml b/.github/workflows/build-fpm.yml index 49107ae..ac1d91b 100644 --- a/.github/workflows/build-fpm.yml +++ b/.github/workflows/build-fpm.yml @@ -9,8 +9,7 @@ on: - 'docker-bake.fpm.hcl' - '.github/workflows/build-fpm.yml' - '.github/workflows/build-fpm-version.yml' - - '.github/workflows/build-image.yml' - - '.github/scripts/bake-metadata*.mjs' + - '.github/action/build-bake-publish/**' - 'fpm/**' - 'fpm-otel/**' - 'caddy/**' @@ -22,8 +21,7 @@ on: - 'docker-bake.fpm.hcl' - '.github/workflows/build-fpm.yml' - '.github/workflows/build-fpm-version.yml' - - '.github/workflows/build-image.yml' - - '.github/scripts/bake-metadata*.mjs' + - '.github/action/build-bake-publish/**' - 'fpm/**' - 'fpm-otel/**' - 'caddy/**' @@ -54,9 +52,6 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - - name: Test Bake metadata conversion - run: node --test .github/scripts/bake-metadata.test.mjs - - name: Read PHP versions from Bake id: php-versions run: | diff --git a/.github/workflows/build-frankenphp-version.yml b/.github/workflows/build-frankenphp-version.yml index a0ba668..b557099 100644 --- a/.github/workflows/build-frankenphp-version.yml +++ b/.github/workflows/build-frankenphp-version.yml @@ -16,19 +16,39 @@ permissions: jobs: frankenphp: name: FrankenPHP - uses: ./.github/workflows/build-image.yml - with: - family: frankenphp - php-version: ${{ inputs.php-version }} - target: frankenphp - secrets: inherit + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.frankenphp.hcl + targets: frankenphp + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} frankenphp-otel: name: FrankenPHP with OpenTelemetry + runs-on: namespace-profile-default needs: [frankenphp] - uses: ./.github/workflows/build-image.yml - with: - family: frankenphp - php-version: ${{ inputs.php-version }} - target: frankenphp-otel - secrets: inherit + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Build + uses: ./.github/action/build-bake-publish + with: + php-version: ${{ inputs.php-version }} + files: | + docker-bake.hcl + docker-bake.frankenphp.hcl + targets: frankenphp-otel + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-frankenphp.yml b/.github/workflows/build-frankenphp.yml index 909ea5b..ccd551a 100644 --- a/.github/workflows/build-frankenphp.yml +++ b/.github/workflows/build-frankenphp.yml @@ -9,8 +9,7 @@ on: - 'docker-bake.frankenphp.hcl' - '.github/workflows/build-frankenphp.yml' - '.github/workflows/build-frankenphp-version.yml' - - '.github/workflows/build-image.yml' - - '.github/scripts/bake-metadata*.mjs' + - '.github/action/build-bake-publish/**' - 'frankenphp/**' - 'frankenphp-otel/**' pull_request: @@ -19,8 +18,7 @@ on: - 'docker-bake.frankenphp.hcl' - '.github/workflows/build-frankenphp.yml' - '.github/workflows/build-frankenphp-version.yml' - - '.github/workflows/build-image.yml' - - '.github/scripts/bake-metadata*.mjs' + - '.github/action/build-bake-publish/**' - 'frankenphp/**' - 'frankenphp-otel/**' schedule: @@ -48,9 +46,6 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - - name: Test Bake metadata conversion - run: node --test .github/scripts/bake-metadata.test.mjs - - name: Read PHP versions from Bake id: php-versions run: | diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml deleted file mode 100644 index 01677de..0000000 --- a/.github/workflows/build-image.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Build and publish image - -on: - workflow_call: - inputs: - family: - description: Bake family (fpm or frankenphp) - required: true - type: string - php-version: - description: PHP minor version to build - required: true - type: string - target: - description: Concrete Bake target or group resolving to one target - required: true - type: string - secrets: - DOCKER_HUB_USERNAME: - required: true - DOCKER_HUB_PASSWORD: - required: true - -permissions: - contents: read - id-token: write - packages: write - -jobs: - metadata: - name: Resolve image metadata - runs-on: ubuntu-latest - outputs: - target: ${{ fromJSON(steps.metadata.outputs.metadata).target }} - images: ${{ fromJSON(steps.metadata.outputs.metadata).images }} - tags: ${{ fromJSON(steps.metadata.outputs.metadata).tags }} - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Read image metadata from Bake - id: metadata - env: - BAKE_FAMILY: ${{ inputs.family }} - BAKE_TARGET: ${{ inputs.target }} - phpMinor: ${{ inputs.php-version }} - imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} - tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} - run: | - docker buildx bake -f docker-bake.hcl -f "docker-bake.${BAKE_FAMILY}.hcl" --print "$BAKE_TARGET" | - node .github/scripts/bake-metadata.mjs "$BAKE_TARGET" >> "$GITHUB_OUTPUT" - - build: - name: Build - needs: [metadata] - uses: docker/github-builder/.github/workflows/bake.yml@cbc9d42f8c89a55623392aadd03600965b80e963 # v1 - with: - files: | - docker-bake.hcl - docker-bake.${{ inputs.family }}.hcl - target: ${{ needs.metadata.outputs.target }} - vars: | - phpMinor=${{ inputs.php-version }} - imageSuffix=${{ github.event_name == 'pull_request' && '-ci-test' || '' }} - tagPrefix=${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} - output: image - push: true - distribute: true - cache: true - cache-mode: max - cache-scope: ${{ needs.metadata.outputs.target }} - sbom: true - sign: 'true' - meta-images: ${{ needs.metadata.outputs.images }} - meta-tags: ${{ needs.metadata.outputs.tags }} - meta-flavor: latest=false - secrets: - registry-auths: | - - registry: docker.io - username: ${{ toJSON(secrets.DOCKER_HUB_USERNAME) }} - password: ${{ toJSON(secrets.DOCKER_HUB_PASSWORD) }} - - registry: ghcr.io - username: ${{ toJSON(github.actor) }} - password: ${{ toJSON(secrets.GITHUB_TOKEN) }} diff --git a/README.md b/README.md index 39457a8..43ee289 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,12 @@ Derived images use published base images. When changing a base image, build and push it before building its dependents, as the CI workflows do. The FPM and FrankenPHP workflows build independently when their files change. -Changes to shared Bake settings, the publishing workflow or metadata script trigger both families. +Changes to shared Bake settings or the publishing action trigger both families. Both workflows also support manual runs and daily rebuilds. Each workflow derives its PHP minor-version matrix from Bake and runs an independent build chain per minor version. Failures in one PHP version do not block the others. -Each image is built natively on GitHub-hosted AMD64 and ARM64 runners, then published -as a multi-platform image. Dev images are also split by Node version, with their -targets derived from Bake. The existing application smoke tests +Both CPU architectures remain in each build. The existing application smoke tests run on PHP 8.3, and the dev image check runs on PHP 8.4. To build only one PHP minor version locally: @@ -42,20 +40,7 @@ phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl fpm phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.frankenphp.hcl frankenphp ``` -CI delegates builds to a pinned revision of -[Docker GitHub Builder](https://github.com/docker/github-builder), through -`.github/workflows/build-image.yml`. It enables GitHub Actions layer caching in -`max` mode, SBOM generation and signed provenance. Cache scopes include the concrete -target name (variant, PHP minor and, for dev images, Node version); the builder adds -the CPU platform. - -Publishing metadata is derived from the Bake tags, preserving both registries, -minor/patch tags and PR image names without adding a `latest` tag. Each call builds -one concrete target, including any `target:` base dependencies. The family workflows -still order separately published base and dependent images. - -To check the publishing metadata conversion locally: - -```sh -node --test .github/scripts/bake-metadata.test.mjs -``` +CI uses the shared `.github/action/build-bake-publish` action to submit each Bake +build to Namespace's remote builder, with layer caching enabled. Both architectures +are built in one job; dev targets also build their Node variants together using +the Bake matrix. Tags and registry destinations come directly from the Bake files. From 78800d17c6c70be4420876b7b233cbe766a38fb4 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Thu, 22 Oct 2026 13:20:22 +0200 Subject: [PATCH 6/7] refactor: update build workflows to use composite actions and introduce webserver testing --- .github/action/build-bake-publish/action.yml | 91 ++++++-- .github/action/test-webserver/action.yml | 53 +++++ .github/workflows/actions.lock | 108 +++++++++ .github/workflows/build-fpm-version.yml | 218 ------------------ .github/workflows/build-fpm.yml | 58 ++++- .../workflows/build-frankenphp-version.yml | 54 ----- .github/workflows/build-frankenphp.yml | 25 +- .github/workflows/cleanup.yml | 4 +- .github/workflows/hub.yml | 6 +- .github/workflows/lint.yml | 8 +- .github/workflows/security.yml | 8 +- .github/workflows/shell-lint.yml | 6 +- .github/workflows/update-php-matrix.yml | 6 +- README.md | 12 +- 14 files changed, 329 insertions(+), 328 deletions(-) create mode 100644 .github/action/test-webserver/action.yml create mode 100644 .github/workflows/actions.lock delete mode 100644 .github/workflows/build-fpm-version.yml delete mode 100644 .github/workflows/build-frankenphp-version.yml diff --git a/.github/action/build-bake-publish/action.yml b/.github/action/build-bake-publish/action.yml index d20847b..236009d 100644 --- a/.github/action/build-bake-publish/action.yml +++ b/.github/action/build-bake-publish/action.yml @@ -1,16 +1,12 @@ name: 'Build and Publish' -description: 'Builds Image and pushes' +description: 'Builds and publishes one PHP minor image family using Namespace' inputs: php-version: description: 'PHP minor version to build' required: true - files: - description: 'Bake files to load' + family: + description: 'Image family: fpm or frankenphp' required: true - targets: - description: 'Targets' - required: true - default: '' docker_hub_username: description: 'Docker Hub username' required: true @@ -23,26 +19,91 @@ inputs: runs: using: "composite" steps: + - name: Validate image family + shell: bash + env: + FAMILY: ${{ inputs.family }} + run: | + case "$FAMILY" in + fpm|frankenphp) ;; + *) echo "Unsupported image family: $FAMILY" >&2; exit 1 ;; + esac + - name: Install and configure Namespace CLI - uses: namespacelabs/nscloud-setup@v0 + uses: namespacelabs/nscloud-setup@v0.0.12 - name: Configure Namespace powered Buildx - uses: namespacelabs/nscloud-setup-buildx-action@v0 + uses: namespacelabs/nscloud-setup-buildx-action@v0.0.23 - name: Login into Docker Hub shell: bash - run: echo "${{ inputs.docker_hub_password }}" | docker login -u ${{ inputs.docker_hub_username }} --password-stdin + env: + DOCKER_HUB_USERNAME: ${{ inputs.docker_hub_username }} + DOCKER_HUB_PASSWORD: ${{ inputs.docker_hub_password }} + run: printf '%s' "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_USERNAME" --password-stdin - name: Login into Github Docker Registry shell: bash - run: echo "${{ inputs.github_token }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + env: + GH_TOKEN: ${{ inputs.github_token }} + run: printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin + + - name: Build and push base images + uses: docker/bake-action@v7.5.0 + with: + push: true + files: | + docker-bake.hcl + docker-bake.${{ inputs.family }}.hcl + targets: ${{ inputs.family }} + env: + DOCKER_BUILD_RECORD_UPLOAD: false + DOCKER_BUILD_SUMMARY: false + phpMinor: ${{ inputs.php-version }} + imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} + tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} + + # These targets consume the base images published by the previous step. + - name: Build and push OpenTelemetry and webserver images + uses: docker/bake-action@v7.5.0 + with: + push: true + files: | + docker-bake.hcl + docker-bake.${{ inputs.family }}.hcl + targets: ${{ inputs.family == 'fpm' && 'fpm-otel,caddy,nginx' || 'frankenphp-otel' }} + env: + DOCKER_BUILD_RECORD_UPLOAD: false + DOCKER_BUILD_SUMMARY: false + phpMinor: ${{ inputs.php-version }} + imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} + tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} + + - name: Build and push OpenTelemetry webserver images + if: inputs.family == 'fpm' + uses: docker/bake-action@v7.5.0 + with: + push: true + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: caddy-otel,nginx-otel + env: + DOCKER_BUILD_RECORD_UPLOAD: false + DOCKER_BUILD_SUMMARY: false + phpMinor: ${{ inputs.php-version }} + imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} + tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} - - name: Build and push - uses: docker/bake-action@v6 + - name: Build and push dev images + if: inputs.family == 'fpm' + uses: docker/bake-action@v7.5.0 with: push: true - files: ${{ inputs.files }} - targets: ${{ inputs.targets }} + files: | + docker-bake.hcl + docker-bake.fpm.hcl + targets: caddy-dev,nginx-dev env: DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_SUMMARY: false diff --git a/.github/action/test-webserver/action.yml b/.github/action/test-webserver/action.yml new file mode 100644 index 0000000..2191413 --- /dev/null +++ b/.github/action/test-webserver/action.yml @@ -0,0 +1,53 @@ +name: Test webserver image +description: Run the example Shopware application against a published image +inputs: + php-version: + description: PHP minor version to test + required: true + webserver: + description: Webserver to test + required: true +runs: + using: composite + steps: + # Application containers run on this runner, not the remote Namespace builder. + - name: Set up local Docker Buildx + uses: docker/setup-buildx-action@v4.4.1 + + - name: Checkout example repository + uses: actions/checkout@v7.0.1 + with: + repository: shopwareLabs/example-docker-repository + path: .smoke-test + + - name: Build and test application + shell: bash + working-directory: .smoke-test + env: + BASE_IMAGE: ghcr.io/shopware/docker-base${{ github.event_name == 'pull_request' && '-ci-test' || '' }}:${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }}${{ inputs.php-version }}-${{ inputs.webserver }} + run: | + # Always collect logs and remove test containers/volumes before the next server. + cleanup() { + set +e + docker compose -p image-smoke-test logs + docker compose -p image-smoke-test down --volumes + } + trap cleanup EXIT + + docker compose -p image-smoke-test build --build-arg BASE_IMAGE="$BASE_IMAGE" + docker compose -p image-smoke-test up -d --wait + + attempt_counter=0 + max_attempts=5 + until curl --output /dev/null --silent --head --fail localhost:8000/admin; do + if [ "$attempt_counter" -eq "$max_attempts" ]; then + echo "Max attempts reached" + exit 1 + fi + printf '.' + attempt_counter=$((attempt_counter+1)) + sleep 5 + done + + curl --fail localhost:8000/admin + curl --fail localhost:8000 diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock new file mode 100644 index 0000000..3063f29 --- /dev/null +++ b/.github/workflows/actions.lock @@ -0,0 +1,108 @@ +# This file is machine-generated by `gh actions-lock`. +# Do not edit by hand; run `gh actions-lock` to update. +# Docs: https://gh.io/actions-lockfile +version: 'v0.0.3' +workflows: + '.github/workflows/build-fpm.yml': + - 'actions/checkout@v7.0.1' + - 'docker/bake-action@v7.5.0' + - 'docker/setup-buildx-action@v4.4.1' + - 'jaxxstorm/action-install-gh-release@v3.0.0' + - 'namespacelabs/nscloud-setup-buildx-action@v0.0.23' + - 'namespacelabs/nscloud-setup@v0.0.12' + '.github/workflows/build-frankenphp.yml': + - 'actions/checkout@v7.0.1' + - 'docker/bake-action@v7.5.0' + - 'docker/setup-buildx-action@v4.4.1' + - 'namespacelabs/nscloud-setup-buildx-action@v0.0.23' + - 'namespacelabs/nscloud-setup@v0.0.12' + '.github/workflows/cleanup.yml': + - 'dataaxiom/ghcr-cleanup-action@v1.2.2' + '.github/workflows/hub.yml': + - 'actions/checkout@v7.0.1' + - 'peter-evans/dockerhub-description@v5.0.0' + '.github/workflows/lint.yml': + - 'actions/checkout@v7.0.1' + - 'hadolint/hadolint-action@v3.5.0' + '.github/workflows/security.yml': + - 'actions/checkout@v7.0.1' + - 'crazy-max/ghaction-container-scan@v4.1.0' + - 'github/codeql-action@v4.39.1' + '.github/workflows/shell-lint.yml': + - 'actions/checkout@v7.0.1' + - 'ludeeus/action-shellcheck@2.0.0' + '.github/workflows/update-php-matrix.yml': + - 'actions/checkout@v7.0.1' + - 'octo-sts/action@v1.1.1' + - 'peter-evans/create-pull-request@v8.1.1' +dependencies: + 'actions/checkout@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'crazy-max/ghaction-container-scan@v4.1.0': + ref: 'v4.1.0' + commit: 'sha1-ffcba8deb5cb3531954cbedbbcd0179d79edf510' + owner_id: 1951866 + repo_id: 414171338 + 'dataaxiom/ghcr-cleanup-action@v1.2.2': + ref: 'v1.2.2' + commit: 'sha1-d52806a0dc70b430571a37da1fde39733ffd640f' + owner_id: 131044173 + repo_id: 799711900 + 'docker/bake-action@v7.5.0': + ref: 'v7.5.0' + commit: 'sha1-59cb1203cd42bcefd40a1b6d73b589e48c86040e' + owner_id: 5429470 + repo_id: 301684204 + 'docker/setup-buildx-action@v4.4.1': + ref: 'v4.4.1' + commit: 'sha1-f87e5991a6d7451dcb8d9637bfbc97413f497069' + owner_id: 5429470 + repo_id: 288485773 + 'github/codeql-action@v4.39.1': + ref: 'v4.39.1' + commit: 'sha1-9b3254085494b55a0f7c4c2b3d2d52cc712ce370' + owner_id: 9919 + repo_id: 259445878 + 'hadolint/hadolint-action@v3.5.0': + ref: 'v3.5.0' + commit: 'sha1-06be81baf89a55ffd0e24b8f04a4185738dd3387' + owner_id: 34047791 + repo_id: 212438777 + 'jaxxstorm/action-install-gh-release@v3.0.0': + ref: 'v3.0.0' + commit: 'sha1-25e24d2d23ae098373794ef1d6faecb48ee52da8' + owner_id: 1622940 + repo_id: 265029375 + 'ludeeus/action-shellcheck@2.0.0': + ref: '2.0.0' + commit: 'sha1-00cae500b08a931fb5698e11e79bfbd38e612a38' + owner_id: 15093472 + repo_id: 174679242 + 'namespacelabs/nscloud-setup-buildx-action@v0.0.23': + ref: 'v0.0.23' + commit: 'sha1-d059ed7184f0bc7c8b27e8810cea153d02bcc6dd' + owner_id: 90391633 + repo_id: 613929575 + 'namespacelabs/nscloud-setup@v0.0.12': + ref: 'v0.0.12' + commit: 'sha1-df198f982fcecfb8264bea3f1274b56a61b6dfdc' + owner_id: 90391633 + repo_id: 610758328 + 'octo-sts/action@v1.1.1': + ref: 'v1.1.1' + commit: 'sha1-f603d3be9d8dd9871a265776e625a27b00effe05' + owner_id: 160145231 + repo_id: 749429568 + 'peter-evans/create-pull-request@v8.1.1': + ref: 'v8.1.1' + commit: 'sha1-5f6978faf089d4d20b00c7766989d076bb2fc7f1' + owner_id: 18365890 + repo_id: 197176365 + 'peter-evans/dockerhub-description@v5.0.0': + ref: 'v5.0.0' + commit: 'sha1-1b9a80c056b620d92cedb9d9b5a223409c68ddfa' + owner_id: 18365890 + repo_id: 193685475 diff --git a/.github/workflows/build-fpm-version.yml b/.github/workflows/build-fpm-version.yml deleted file mode 100644 index e6e0e77..0000000 --- a/.github/workflows/build-fpm-version.yml +++ /dev/null @@ -1,218 +0,0 @@ -name: Build FPM PHP minor - -on: - workflow_call: - inputs: - php-version: - description: PHP minor version to build - required: true - type: string - -permissions: - contents: read - id-token: write - packages: write - -jobs: - fpm: - name: FPM - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: fpm - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - fpm-otel: - name: FPM with OpenTelemetry - runs-on: ubuntu-latest - needs: [fpm] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: fpm-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - webserver: - name: ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [fpm] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver}} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - webserver-otel: - name: ${{ matrix.webserver }} with OpenTelemetry - runs-on: ubuntu-latest - needs: [fpm-otel] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver}}-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - dev: - name: Dev ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [webserver-otel] - strategy: - fail-fast: false - matrix: - webserver: [ caddy, nginx ] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.fpm.hcl - targets: ${{ matrix.webserver }}-dev - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - dev-check: - if: inputs.php-version == '8.4' - name: Check Dev Image - runs-on: ubuntu-latest - needs: [dev] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Install a binary from GitHub releases - uses: jaxxstorm/action-install-gh-release@v3.0.0 - with: - repo: GoogleContainerTools/container-structure-test - extension-matching: disable - rename-to: container-structure-test - chmod: 0755 - - - name: Determine image tag - id: image-tag - run: | - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "IMAGE_TAG=ghcr.io/shopware/docker-dev-ci-test:${{ github.event.number }}-php8.4-node24-caddy" >> "$GITHUB_OUTPUT" - else - echo "IMAGE_TAG=ghcr.io/shopware/docker-dev:php8.4-node24-caddy" >> "$GITHUB_OUTPUT" - fi - - - name: Pull image - run: docker pull ${{ steps.image-tag.outputs.IMAGE_TAG }} - - - name: Test Dev Image - run: container-structure-test test --config dev/config.yaml --image ${{ steps.image-tag.outputs.IMAGE_TAG }} - - check: - if: inputs.php-version == '8.3' - name: Test Image with Webserver ${{ matrix.webserver }} - runs-on: ubuntu-latest - needs: [webserver] - strategy: - matrix: - webserver: - - caddy - - nginx - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Checkout - uses: actions/checkout@v7 - - - name: Checkout example repo - uses: actions/checkout@v7 - with: - repository: shopwareLabs/example-docker-repository - - - name: Build main branch - if: github.event_name != 'pull_request' - run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base:8.3-${{ matrix.webserver }} - - - name: Build PR - if: github.event_name == 'pull_request' - run: docker compose build --build-arg BASE_IMAGE=ghcr.io/shopware/docker-base-ci-test:${{ github.event.number }}-8.3-${{ matrix.webserver}} - - - name: Run image - run: docker compose up -d --wait - - - name: Wait for Webserver reachable - run: | - attempt_counter=0 - max_attempts=5 - - until curl --output /dev/null --silent --head --fail localhost:8000/admin; do - if [ ${attempt_counter} -eq ${max_attempts} ];then - echo "Max attempts reached" - exit 1 - fi - - printf '.' - attempt_counter=$((attempt_counter+1)) - sleep 5 - done - - - name: Check if shopware admin is running - run: curl --fail localhost:8000/admin - - - name: Check if shopware is running - run: curl --fail localhost:8000 - - # output logs if failed - - name: Output logs - if: always() - run: docker compose logs diff --git a/.github/workflows/build-fpm.yml b/.github/workflows/build-fpm.yml index ac1d91b..5500bba 100644 --- a/.github/workflows/build-fpm.yml +++ b/.github/workflows/build-fpm.yml @@ -8,7 +8,7 @@ on: - 'docker-bake.hcl' - 'docker-bake.fpm.hcl' - '.github/workflows/build-fpm.yml' - - '.github/workflows/build-fpm-version.yml' + - '.github/action/test-webserver/**' - '.github/action/build-bake-publish/**' - 'fpm/**' - 'fpm-otel/**' @@ -20,7 +20,7 @@ on: - 'docker-bake.hcl' - 'docker-bake.fpm.hcl' - '.github/workflows/build-fpm.yml' - - '.github/workflows/build-fpm-version.yml' + - '.github/action/test-webserver/**' - '.github/action/build-bake-publish/**' - 'fpm/**' - 'fpm-otel/**' @@ -42,15 +42,15 @@ permissions: jobs: prepare: name: PHP versions - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: versions: ${{ steps.php-versions.outputs.versions }} steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v7.0.1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@v4.4.1 - name: Read PHP versions from Bake id: php-versions @@ -61,11 +61,51 @@ jobs: build: name: PHP ${{ matrix.php-version }} needs: [prepare] + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: php-version: ${{ fromJSON(needs.prepare.outputs.versions) }} - uses: ./.github/workflows/build-fpm-version.yml - with: - php-version: ${{ matrix.php-version }} - secrets: inherit + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + + - name: Build and publish FPM images + uses: $/.github/action/build-bake-publish + with: + php-version: ${{ matrix.php-version }} + family: fpm + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Test Caddy image + if: matrix.php-version == '8.3' + uses: $/.github/action/test-webserver + with: + php-version: ${{ matrix.php-version }} + webserver: caddy + + - name: Test Nginx image + if: matrix.php-version == '8.3' + uses: $/.github/action/test-webserver + with: + php-version: ${{ matrix.php-version }} + webserver: nginx + + - name: Install container structure test + if: matrix.php-version == '8.4' + uses: jaxxstorm/action-install-gh-release@v3.0.0 + with: + repo: GoogleContainerTools/container-structure-test + extension-matching: disable + rename-to: container-structure-test + chmod: 0755 + + - name: Test dev image + if: matrix.php-version == '8.4' + env: + IMAGE_TAG: ghcr.io/shopware/docker-dev${{ github.event_name == 'pull_request' && '-ci-test' || '' }}:${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }}php${{ matrix.php-version }}-node24-caddy + run: | + docker pull "$IMAGE_TAG" + container-structure-test test --config dev/config.yaml --image "$IMAGE_TAG" diff --git a/.github/workflows/build-frankenphp-version.yml b/.github/workflows/build-frankenphp-version.yml deleted file mode 100644 index b557099..0000000 --- a/.github/workflows/build-frankenphp-version.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Build FrankenPHP PHP minor - -on: - workflow_call: - inputs: - php-version: - description: PHP minor version to build - required: true - type: string - -permissions: - contents: read - id-token: write - packages: write - -jobs: - frankenphp: - name: FrankenPHP - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.frankenphp.hcl - targets: frankenphp - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - frankenphp-otel: - name: FrankenPHP with OpenTelemetry - runs-on: namespace-profile-default - needs: [frankenphp] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Build - uses: ./.github/action/build-bake-publish - with: - php-version: ${{ inputs.php-version }} - files: | - docker-bake.hcl - docker-bake.frankenphp.hcl - targets: frankenphp-otel - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-frankenphp.yml b/.github/workflows/build-frankenphp.yml index ccd551a..e9eb52e 100644 --- a/.github/workflows/build-frankenphp.yml +++ b/.github/workflows/build-frankenphp.yml @@ -8,7 +8,6 @@ on: - 'docker-bake.hcl' - 'docker-bake.frankenphp.hcl' - '.github/workflows/build-frankenphp.yml' - - '.github/workflows/build-frankenphp-version.yml' - '.github/action/build-bake-publish/**' - 'frankenphp/**' - 'frankenphp-otel/**' @@ -17,7 +16,6 @@ on: - 'docker-bake.hcl' - 'docker-bake.frankenphp.hcl' - '.github/workflows/build-frankenphp.yml' - - '.github/workflows/build-frankenphp-version.yml' - '.github/action/build-bake-publish/**' - 'frankenphp/**' - 'frankenphp-otel/**' @@ -36,15 +34,15 @@ permissions: jobs: prepare: name: PHP versions - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: versions: ${{ steps.php-versions.outputs.versions }} steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v7.0.1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@v4.4.1 - name: Read PHP versions from Bake id: php-versions @@ -55,11 +53,20 @@ jobs: build: name: PHP ${{ matrix.php-version }} needs: [prepare] + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: php-version: ${{ fromJSON(needs.prepare.outputs.versions) }} - uses: ./.github/workflows/build-frankenphp-version.yml - with: - php-version: ${{ matrix.php-version }} - secrets: inherit + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + + - name: Build and publish FrankenPHP images + uses: $/.github/action/build-bake-publish + with: + php-version: ${{ matrix.php-version }} + family: frankenphp + docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 7d809fe..c060452 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -9,10 +9,10 @@ permissions: jobs: cleanup: name: Cleanup GHCR - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Cleanup GHCR - uses: dataaxiom/ghcr-cleanup-action@d52806a0dc70b430571a37da1fde39733ffd640f # ratchet:dataaxiom/ghcr-cleanup-action@v1 + uses: dataaxiom/ghcr-cleanup-action@v1.2.2 with: packages: docker-base-ci-test,docker-dev-ci-test,demo-ci-test older-than: 1 day diff --git a/.github/workflows/hub.yml b/.github/workflows/hub.yml index ec7e2d0..c95fe3b 100644 --- a/.github/workflows/hub.yml +++ b/.github/workflows/hub.yml @@ -9,13 +9,13 @@ on: jobs: sync: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v7.0.1 - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v5 + uses: peter-evans/dockerhub-description@v5.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f3ccf0c..533ec7b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,19 +11,19 @@ on: jobs: lint: name: Lint Dockerfiles - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v7.0.1 - name: Lint FPM - uses: hadolint/hadolint-action@master + uses: hadolint/hadolint-action@v3.5.0 with: dockerfile: ./fpm/Dockerfile ignore: DL3018 - name: Lint Caddy - uses: hadolint/hadolint-action@master + uses: hadolint/hadolint-action@v3.5.0 with: dockerfile: ./caddy/Dockerfile ignore: DL3018 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 0f0c596..2b5ebc9 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -11,7 +11,7 @@ permissions: jobs: scan: name: 'Image Scan (PHP: ${{ matrix.php-version }}, Variant: ${{ matrix.variant.name }})' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: @@ -39,7 +39,7 @@ jobs: dockerfile: frankenphp-otel/Dockerfile steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v7.0.1 - name: Pull image run: docker pull shopware/docker-base:${{ matrix.php-version }}-${{ matrix.variant.name }} @@ -49,14 +49,14 @@ jobs: - name: Scan for vulnerabilities id: scan - uses: crazy-max/ghaction-container-scan@v4 + uses: crazy-max/ghaction-container-scan@v4.1.0 with: image: shopware/docker-base:${{ matrix.php-version }}-${{ matrix.variant.name }} dockerfile: ./${{ matrix.variant.dockerfile }} - name: Upload SARIF file if: ${{ steps.scan.outputs.sarif != '' }} - uses: github/codeql-action/upload-sarif@v4.39.0 + uses: github/codeql-action/upload-sarif@v4.39.1 with: sarif_file: ${{ steps.scan.outputs.sarif }} category: ${{ matrix.php-version }}-${{ matrix.variant.name }} diff --git a/.github/workflows/shell-lint.yml b/.github/workflows/shell-lint.yml index c720163..955bb61 100644 --- a/.github/workflows/shell-lint.yml +++ b/.github/workflows/shell-lint.yml @@ -10,10 +10,10 @@ permissions: jobs: lint: name: Shell Lint - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v7.0.1 - name: Run ShellCheck - uses: ludeeus/action-shellcheck@master + uses: ludeeus/action-shellcheck@2.0.0 diff --git a/.github/workflows/update-php-matrix.yml b/.github/workflows/update-php-matrix.yml index 8199516..d96bc5f 100644 --- a/.github/workflows/update-php-matrix.yml +++ b/.github/workflows/update-php-matrix.yml @@ -12,7 +12,7 @@ permissions: jobs: update-php-matrix: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Generate GitHub token via octo-sts id: generate-token @@ -22,7 +22,7 @@ jobs: identity: update-php-matrix - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v7.0.1 with: token: ${{ steps.generate-token.outputs.token }} @@ -43,7 +43,7 @@ jobs: - name: Create Pull Request if: steps.changes.outputs.changed == 'true' - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@v8.1.1 with: token: ${{ steps.generate-token.outputs.token }} commit-message: 'chore: update PHP matrix to latest patch versions' diff --git a/README.md b/README.md index 43ee289..85119ee 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,11 @@ phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl fpm phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.frankenphp.hcl frankenphp ``` -CI uses the shared `.github/action/build-bake-publish` action to submit each Bake -build to Namespace's remote builder, with layer caching enabled. Both architectures -are built in one job; dev targets also build their Node variants together using -the Bake matrix. Tags and registry destinations come directly from the Bake files. +CI uses the shared `.github/action/build-bake-publish` composite action, rather than +reusable workflows. Each PHP minor runs in a single job, setting up Namespace's +remote builder once with layer caching enabled. Sequential Bake steps publish base +images before their dependents, grouping independent targets together. Both CPU +architectures and dev Node variants stay together in Bake. Tags and registry +destinations come directly from the Bake files. The FPM job also runs the existing +checks for the applicable PHP minor, sharing application smoke-test steps through +`.github/action/test-webserver`. From 7d729e6b5b4d56f3f344ccbdcc24891b838f4d36 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Thu, 22 Oct 2026 14:12:26 +0200 Subject: [PATCH 7/7] refactor: enhance Dockerfile stages and update README for OS package management --- .github/action/build-bake-publish/action.yml | 2 +- README.md | 31 ++++++-- caddy/Dockerfile | 12 +++- dev/Dockerfile | 12 +++- docker-bake.fpm.hcl | 76 ++++++++++++++++++-- docker-bake.frankenphp.hcl | 19 ++++- docker-bake.hcl | 12 ++++ fpm-otel/Dockerfile | 12 +++- fpm/Dockerfile | 13 +++- frankenphp-otel/Dockerfile | 14 +++- frankenphp/Dockerfile | 15 +++- nginx/Dockerfile | 12 +++- 12 files changed, 208 insertions(+), 22 deletions(-) diff --git a/.github/action/build-bake-publish/action.yml b/.github/action/build-bake-publish/action.yml index 236009d..c21a185 100644 --- a/.github/action/build-bake-publish/action.yml +++ b/.github/action/build-bake-publish/action.yml @@ -63,7 +63,7 @@ runs: imageSuffix: ${{ github.event_name == 'pull_request' && '-ci-test' || '' }} tagPrefix: ${{ github.event_name == 'pull_request' && format('{0}-', github.event.number) || '' }} - # These targets consume the base images published by the previous step. + # Derived targets reuse internal build stages, excluding the final OS upgrade. - name: Build and push OpenTelemetry and webserver images uses: docker/bake-action@v7.5.0 with: diff --git a/README.md b/README.md index 85119ee..0cd3ca6 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,31 @@ FPM targets include `fpm`, `fpm-otel`, `caddy`, `caddy-otel`, `nginx`, `nginx-otel`, `caddy-dev` and `nginx-dev`. FrankenPHP targets are `frankenphp` and `frankenphp-otel`. Add `--print` to inspect the build configuration. -Derived images use published base images. When changing a base image, build and -push it before building its dependents, as the CI workflows do. +Derived images use internal Bake targets that select the Dockerfiles' `build` +stages. Bake builds these dependencies automatically; publishing base images first +is not required. Internal `*-build` targets are not published. + +Every published image ends with a `runtime` stage that upgrades installed OS +packages using `apk upgrade` or `apt-get upgrade --with-new-pkgs`. Bake passes the +current UTC date as `OS_PACKAGES_REFRESH`, refreshing this layer once per day while +reusing the PHP, gRPC and other extension compilation layers. Derived images use +the stages before this upgrade, so a daily refresh does not invalidate their +compilation cache either. Changes to other build inputs can still rebuild these +layers. + +To force another package refresh on the same day, override `osPackagesRefresh`: + +```sh +osPackagesRefresh="$(date -u +%Y-%m-%d)-2" phpMinor=8.4 \ + docker buildx bake -f docker-bake.hcl -f docker-bake.fpm.hcl fpm +``` + +When invoking `docker buildx build` directly, pass +`--build-arg OS_PACKAGES_REFRESH="$(date -u +%Y-%m-%d)"` explicitly. OS upgrades +add replacement files over the cached layers, increasing image size. They do not +update source-built PHP/extensions or libraries statically linked into them; +those still require rebuilding their compilation stages. Compiled extensions must +remain compatible with upgraded runtime libraries. The FPM and FrankenPHP workflows build independently when their files change. Changes to shared Bake settings or the publishing action trigger both families. @@ -42,8 +65,8 @@ phpMinor=8.4 docker buildx bake -f docker-bake.hcl -f docker-bake.frankenphp.hcl CI uses the shared `.github/action/build-bake-publish` composite action, rather than reusable workflows. Each PHP minor runs in a single job, setting up Namespace's -remote builder once with layer caching enabled. Sequential Bake steps publish base -images before their dependents, grouping independent targets together. Both CPU +remote builder once with layer caching enabled. Sequential Bake steps publish each +image group, reusing internal build stages across steps. Both CPU architectures and dev Node variants stay together in Bake. Tags and registry destinations come directly from the Bake files. The FPM job also runs the existing checks for the applicable PHP minor, sharing application smoke-test steps through diff --git a/caddy/Dockerfile b/caddy/Dockerfile index b21c520..43061c5 100644 --- a/caddy/Dockerfile +++ b/caddy/Dockerfile @@ -1,6 +1,6 @@ #syntax=docker/dockerfile:1 -FROM base +FROM base AS build USER root @@ -16,3 +16,13 @@ WORKDIR /var/www/html ENV FPM_LISTEN=/tmp/php-fpm.sock ENTRYPOINT [ "/usr/bin/supervisord", "-c", "/etc/supervisord.conf" ] + +# Derived images use the build stage so this refresh cannot invalidate compilation. +FROM build AS runtime + +USER root +ARG OS_PACKAGES_REFRESH +RUN echo "Refreshing OS packages: ${OS_PACKAGES_REFRESH}" \ + && apk upgrade --no-cache + +USER www-data diff --git a/dev/Dockerfile b/dev/Dockerfile index 8806e07..28fe718 100644 --- a/dev/Dockerfile +++ b/dev/Dockerfile @@ -2,7 +2,7 @@ ARG NODE_VERSION=22 FROM node:${NODE_VERSION}-alpine AS node -FROM base +FROM base AS build ARG NODE_VERSION @@ -68,3 +68,13 @@ ENV PHP_OPCACHE_VALIDATE_TIMESTAMPS=1 \ ENTRYPOINT [ "/entrypoint" ] CMD [ "/usr/bin/supervisord", "-c", "/etc/supervisord.conf" ] + +# Derived images use the build stage so this refresh cannot invalidate compilation. +FROM build AS runtime + +USER root +ARG OS_PACKAGES_REFRESH +RUN echo "Refreshing OS packages: ${OS_PACKAGES_REFRESH}" \ + && apk upgrade --no-cache + +USER www-data diff --git a/docker-bake.fpm.hcl b/docker-bake.fpm.hcl index 83201a1..f9cdf88 100644 --- a/docker-bake.fpm.hcl +++ b/docker-bake.fpm.hcl @@ -20,6 +20,7 @@ target "php-fpm-alpine" { # FPM target "fpm" { + inherits = [ "_package-refresh" ] name = "fpm-${replace(substr(php, 0, 3), ".", "-")}" context = "./fpm" matrix = { @@ -42,13 +43,14 @@ target "fpm" { } target "fpm-otel" { + inherits = [ "_package-refresh" ] name = "fpm-otel-${replace(substr(php, 0, 3), ".", "-")}" context = "./fpm-otel" matrix = { "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" + base = "target:fpm-build-${replace(substr(php, 0, 3), ".", "-")}" } platforms = [ "linux/amd64", "linux/arm64" ] tags = imageSuffix != "" ? [ @@ -66,13 +68,14 @@ target "fpm-otel" { # Caddy target "caddy" { + inherits = [ "_package-refresh" ] name = "caddy-${replace(substr(php, 0, 3), ".", "-")}" context = "./caddy" matrix = { "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" + base = "target:fpm-build-${replace(substr(php, 0, 3), ".", "-")}" } platforms = [ "linux/amd64", "linux/arm64" ] tags = imageSuffix != "" ? [ @@ -88,13 +91,14 @@ target "caddy" { } target "caddy-otel" { + inherits = [ "_package-refresh" ] name = "caddy-otel-${replace(substr(php, 0, 3), ".", "-")}" context = "./caddy" matrix = { "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" + base = "target:fpm-otel-build-${replace(substr(php, 0, 3), ".", "-")}" } platforms = [ "linux/amd64", "linux/arm64" ] tags = imageSuffix != "" ? [ @@ -110,6 +114,7 @@ target "caddy-otel" { } target "caddy-dev" { + inherits = [ "_package-refresh" ] name = "caddy-dev-${replace(substr(php, 0, 3), ".", "-")}-${node}" context = "./dev" matrix = { @@ -117,7 +122,7 @@ target "caddy-dev" { "node" = [ "22", "24" ] } contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-caddy-otel" + base = "target:caddy-otel-build-${replace(substr(php, 0, 3), ".", "-")}" } args = { "NODE_VERSION" = node @@ -132,13 +137,14 @@ target "caddy-dev" { # Nginx target "nginx" { + inherits = [ "_package-refresh" ] name = "nginx-${replace(substr(php, 0, 3), ".", "-")}" context = "./nginx" matrix = { "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm" + base = "target:fpm-build-${replace(substr(php, 0, 3), ".", "-")}" } platforms = [ "linux/amd64", "linux/arm64" ] tags = imageSuffix != "" ? [ @@ -154,13 +160,14 @@ target "nginx" { } target "nginx-otel" { + inherits = [ "_package-refresh" ] name = "nginx-otel-${replace(substr(php, 0, 3), ".", "-")}" context = "./nginx" matrix = { "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-fpm-otel" + base = "target:fpm-otel-build-${replace(substr(php, 0, 3), ".", "-")}" } platforms = [ "linux/amd64", "linux/arm64" ] tags = imageSuffix != "" ? [ @@ -176,6 +183,7 @@ target "nginx-otel" { } target "nginx-dev" { + inherits = [ "_package-refresh" ] name = "nginx-dev-${replace(substr(php, 0, 3), ".", "-")}-${node}" context = "./dev" matrix = { @@ -183,7 +191,7 @@ target "nginx-dev" { "node" = [ "22", "24" ] } contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-nginx-otel" + base = "target:nginx-otel-build-${replace(substr(php, 0, 3), ".", "-")}" } args = { "NODE_VERSION" = node @@ -194,3 +202,57 @@ target "nginx-dev" { "ghcr.io/shopware/docker-dev${imageSuffix}:${tagPrefix}php${php}-node${node}-nginx" ] } + +# Internal stages exclude the daily upgrade and are never published. + +target "fpm-build" { + name = "fpm-build-${replace(substr(php, 0, 3), ".", "-")}" + matrix = { + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] + } + inherits = [ "fpm-${replace(substr(php, 0, 3), ".", "-")}" ] + target = "build" + tags = [] + args = { + OS_PACKAGES_REFRESH = "" + } +} + +target "fpm-otel-build" { + name = "fpm-otel-build-${replace(substr(php, 0, 3), ".", "-")}" + matrix = { + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] + } + inherits = [ "fpm-otel-${replace(substr(php, 0, 3), ".", "-")}" ] + target = "build" + tags = [] + args = { + OS_PACKAGES_REFRESH = "" + } +} + +target "caddy-otel-build" { + name = "caddy-otel-build-${replace(substr(php, 0, 3), ".", "-")}" + matrix = { + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] + } + inherits = [ "caddy-otel-${replace(substr(php, 0, 3), ".", "-")}" ] + target = "build" + tags = [] + args = { + OS_PACKAGES_REFRESH = "" + } +} + +target "nginx-otel-build" { + name = "nginx-otel-build-${replace(substr(php, 0, 3), ".", "-")}" + matrix = { + "php" = [for php in phpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] + } + inherits = [ "nginx-otel-${replace(substr(php, 0, 3), ".", "-")}" ] + target = "build" + tags = [] + args = { + OS_PACKAGES_REFRESH = "" + } +} diff --git a/docker-bake.frankenphp.hcl b/docker-bake.frankenphp.hcl index 89576e7..3dcfdf3 100644 --- a/docker-bake.frankenphp.hcl +++ b/docker-bake.frankenphp.hcl @@ -41,6 +41,7 @@ target "frankenphp-upstream" { # Frankenphp target "frankenphp" { + inherits = [ "_package-refresh" ] name = "frankenphp-${replace(substr(php, 0, 3), ".", "-")}" context = "./frankenphp" matrix = { @@ -63,13 +64,14 @@ target "frankenphp" { } target "frankenphp-otel" { + inherits = [ "_package-refresh" ] name = "frankenphp-otel-${replace(substr(php, 0, 3), ".", "-")}" context = "./frankenphp-otel" matrix = { "php" = [for php in frankenphpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] } contexts = { - base = "docker-image://ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp" + base = "target:frankenphp-build-${replace(substr(php, 0, 3), ".", "-")}" } platforms = [ "linux/amd64", "linux/arm64" ] tags = imageSuffix != "" ? [ @@ -83,3 +85,18 @@ target "frankenphp-otel" { "ghcr.io/shopware/docker-base${imageSuffix}:${tagPrefix}${php}-frankenphp-otel" ] } + +# Internal stages exclude the daily upgrade and are never published. + +target "frankenphp-build" { + name = "frankenphp-build-${replace(substr(php, 0, 3), ".", "-")}" + matrix = { + "php" = [for php in frankenphpMatrix : php if phpMinor == "" || substr(php, 0, 3) == phpMinor] + } + inherits = [ "frankenphp-${replace(substr(php, 0, 3), ".", "-")}" ] + target = "build" + tags = [] + args = { + OS_PACKAGES_REFRESH = "" + } +} diff --git a/docker-bake.hcl b/docker-bake.hcl index f0963f9..1f89f09 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -16,3 +16,15 @@ variable "phpLibraryRef" { variable "phpMinor" { default = "" } + +# Refresh installed OS packages once per UTC day without rebuilding extensions. +# Override this value to force another refresh within the same day. +variable "osPackagesRefresh" { + default = formattimestamp("YYYY-MM-DD", timestamp()) +} + +target "_package-refresh" { + args = { + OS_PACKAGES_REFRESH = osPackagesRefresh + } +} diff --git a/fpm-otel/Dockerfile b/fpm-otel/Dockerfile index 50a30de..983bc7a 100644 --- a/fpm-otel/Dockerfile +++ b/fpm-otel/Dockerfile @@ -1,6 +1,6 @@ #syntax=docker/dockerfile:1 -FROM base +FROM base AS build USER root @@ -27,3 +27,13 @@ RUN --mount=type=bind,from=ghcr.io/php/pie:bin,source=/pie,target=/usr/local/bin EOF USER www-data + +# Derived images use the build stage so this refresh cannot invalidate compilation. +FROM build AS runtime + +USER root +ARG OS_PACKAGES_REFRESH +RUN echo "Refreshing OS packages: ${OS_PACKAGES_REFRESH}" \ + && apk upgrade --no-cache + +USER www-data diff --git a/fpm/Dockerfile b/fpm/Dockerfile index f37040f..7851a1e 100644 --- a/fpm/Dockerfile +++ b/fpm/Dockerfile @@ -1,6 +1,6 @@ #syntax=docker/dockerfile:1 -FROM base +FROM base AS build ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ @@ -8,7 +8,6 @@ RUN <