diff --git a/.github/workflows/build-83.yml b/.github/workflows/build-83.yml index 5903421..cf07b7e 100644 --- a/.github/workflows/build-83.yml +++ b/.github/workflows/build-83.yml @@ -7,6 +7,10 @@ on: description: "Stable php-src tag matching installed apk version (e.g. 8.3.31). Leave empty to skip." required: false +permissions: + contents: read + packages: write + jobs: build: uses: ./.github/workflows/build-php.yml diff --git a/.github/workflows/build-84.yml b/.github/workflows/build-84.yml index e4d38e7..b7cb1ad 100644 --- a/.github/workflows/build-84.yml +++ b/.github/workflows/build-84.yml @@ -7,6 +7,10 @@ on: description: "Stable php-src tag matching installed apk version (e.g. 8.4.19). Leave empty to skip." required: false +permissions: + contents: read + packages: write + jobs: build: uses: ./.github/workflows/build-php.yml diff --git a/.github/workflows/build-85.yml b/.github/workflows/build-85.yml index fbafd28..ed28147 100644 --- a/.github/workflows/build-85.yml +++ b/.github/workflows/build-85.yml @@ -7,6 +7,10 @@ on: description: "Stable php-src tag matching installed apk version (e.g. 8.5.4). Leave empty to skip." required: false +permissions: + contents: read + packages: write + jobs: build: uses: ./.github/workflows/build-php.yml diff --git a/.github/workflows/build-php.yml b/.github/workflows/build-php.yml index 62beecc..6077e15 100644 --- a/.github/workflows/build-php.yml +++ b/.github/workflows/build-php.yml @@ -29,9 +29,14 @@ on: env: REGISTRY: skilldlabs/php + GHCR_REGISTRY: ghcr.io/skilld-labs/php COMPOSER_HASH: "c8b085408188070d5f52bcfe4ecfbee5f727afa458b2573b8eaaf77b3419b0bf2768dc67c86944da1544f06fa544fd47" DRUSH_VERSION: "8.5.0" +permissions: + contents: read + packages: write + jobs: build-base: runs-on: ubuntu-latest @@ -45,13 +50,18 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Generate tags id: tags run: | - TAGS="${{ env.REGISTRY }}:${{ inputs.php }}" + TAGS="${{ env.REGISTRY }}:${{ inputs.php }},${{ env.GHCR_REGISTRY }}:${{ inputs.php }}" if [ -n "${{ inputs.version }}" ]; then - TAGS="${TAGS},${{ env.REGISTRY }}:${{ inputs.version }}" + TAGS="${TAGS},${{ env.REGISTRY }}:${{ inputs.version }},${{ env.GHCR_REGISTRY }}:${{ inputs.version }}" fi echo "tags=${TAGS}" >> "$GITHUB_OUTPUT" echo "base_tag=${{ env.REGISTRY }}:${{ inputs.php }}" >> "$GITHUB_OUTPUT" @@ -103,13 +113,18 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Generate tags id: tags run: | - TAGS="${{ env.REGISTRY }}:${{ inputs.php }}-fpm" + TAGS="${{ env.REGISTRY }}:${{ inputs.php }}-fpm,${{ env.GHCR_REGISTRY }}:${{ inputs.php }}-fpm" if [ -n "${{ inputs.version }}" ]; then - TAGS="${TAGS},${{ env.REGISTRY }}:${{ inputs.php }}-fpm-${{ inputs.version }}" + TAGS="${TAGS},${{ env.REGISTRY }}:${{ inputs.php }}-fpm-${{ inputs.version }},${{ env.GHCR_REGISTRY }}:${{ inputs.php }}-fpm-${{ inputs.version }}" fi echo "tags=${TAGS}" >> "$GITHUB_OUTPUT" echo "base_tag=${{ env.REGISTRY }}:${{ inputs.php }}-fpm" >> "$GITHUB_OUTPUT" @@ -157,13 +172,18 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Generate tags id: tags run: | - TAGS="${{ env.REGISTRY }}:${{ inputs.php }}-unit" + TAGS="${{ env.REGISTRY }}:${{ inputs.php }}-unit,${{ env.GHCR_REGISTRY }}:${{ inputs.php }}-unit" if [ -n "${{ inputs.version }}" ]; then - TAGS="${TAGS},${{ env.REGISTRY }}:${{ inputs.php }}-unit-${{ inputs.version }}" + TAGS="${TAGS},${{ env.REGISTRY }}:${{ inputs.php }}-unit-${{ inputs.version }},${{ env.GHCR_REGISTRY }}:${{ inputs.php }}-unit-${{ inputs.version }}" fi echo "tags=${TAGS}" >> "$GITHUB_OUTPUT" echo "base_tag=${{ env.REGISTRY }}:${{ inputs.php }}-unit" >> "$GITHUB_OUTPUT" @@ -211,11 +231,16 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Generate tags id: tags run: | - echo "tags=${{ env.REGISTRY }}:${{ inputs.php }}-unit-dev" >> "$GITHUB_OUTPUT" + echo "tags=${{ env.REGISTRY }}:${{ inputs.php }}-unit-dev,${{ env.GHCR_REGISTRY }}:${{ inputs.php }}-unit-dev" >> "$GITHUB_OUTPUT" - uses: docker/build-push-action@v7 with: diff --git a/CLAUDE.md b/CLAUDE.md index 39cc001..0cd0524 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Docker image collection for PHP (7.4 through 8.5) optimized for Drupal, published as `skilldlabs/php` on Docker Hub. All images are multi-platform (linux/amd64, linux/arm64) and Alpine-based (except FrankenPHP which uses Ubuntu). +Docker image collection for PHP (7.4 through 8.5) optimized for Drupal, published to two registries from a single build: Docker Hub (`skilldlabs/php`) and GitHub Container Registry (`ghcr.io/skilld-labs/php`). All images are multi-platform (linux/amd64, linux/arm64) and Alpine-based (except FrankenPHP which uses Ubuntu). Read `RELEASE_RUNBOOK.md` before doing a monthly PHP patch release. It contains the release checklist and the March 11, 2026 postmortem for the CI failure that was fixed in commit `7531dc0`. @@ -192,7 +192,22 @@ docker-php/ 3. **Push All Platforms** (if tests pass) - Builds for all platforms using cache - - Pushes to Docker Hub with version tags + - Pushes the same digest to **both** Docker Hub and ghcr.io with version tags (see Container Registries below) + +### Container Registries + +CI publishes every image to two registries in a single `docker/build-push-action` push (same digest, no rebuild or re-pull, multi-arch preserved): + +| Registry | Image | Auth | +|----------|-------|------| +| Docker Hub | `skilldlabs/php` (`env.REGISTRY`) | `DOCKERHUB_USERNAME` / `DOCKERHUB_TOKEN` secrets | +| GitHub Container Registry | `ghcr.io/skilld-labs/php` (`env.GHCR_REGISTRY`) | built-in `GITHUB_TOKEN` (`github.actor`) | + +- The ghcr namespace is the **GitHub org** `skilld-labs` (hyphen), not the Docker Hub org `skilldlabs` (no hyphen). They are deliberately different and cannot be unified. +- ghcr push needs `packages: write`. The permission is declared at the workflow level in `build-php.yml` **and** in each caller (`build-{VER}.yml`) — for `workflow_call`, the caller's `permissions` block governs the `GITHUB_TOKEN` scope handed to the reusable workflow. No extra secret is required because the package lives under the same org as the repo. +- New ghcr packages are **private** by default. After the first push, set the `php` package visibility to Public and link it to the repo on the org Packages page. +- Both `:NN` rolling tags and optional `:VERSION` tags are mirrored. The amd64 test image (`base_tag`) is Docker Hub-only since it is loaded locally and never pushed. +- FrankenPHP workflows push to Docker Hub only. ### Cache Strategy