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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/action_publish-images-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ jobs:
registry-repositories: "docker.io/serversideup/php,ghcr.io/serversideup/php"
tag-prefix: "beta"
release-type: "testing"
secrets: inherit

test:
needs: [release, setup, base, build]
Expand All @@ -106,7 +105,9 @@ jobs:
id-token: write
with:
ref: ${{ needs.release.outputs.tag }}
secrets: inherit
secrets:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

report:
needs: [release, setup, build, test, publish]
Expand Down
21 changes: 19 additions & 2 deletions .github/workflows/action_publish-images-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ run-name: >-
Production images (${{
github.event.release.tag_name ||
(github.event_name == 'schedule' && 'weekly rebuild') ||
inputs.reason ||
'manual rebuild'
}})

on:
workflow_dispatch:
inputs:
reason:
description: 'Shown in the run title (example: PHP 8.5.13 release). Left empty, the run is a "manual rebuild".'
type: string
required: false
release:
types: [released]
schedule:
Expand Down Expand Up @@ -83,7 +89,6 @@ jobs:
registry-repositories: "docker.io/serversideup/php,ghcr.io/serversideup/php"
tag-prefix: ''
release-type: "latest"
secrets: inherit

test:
needs: [release, setup, base, build]
Expand All @@ -106,7 +111,19 @@ jobs:
id-token: write
with:
ref: ${{ needs.release.outputs.tag }}
secrets: inherit
secrets:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

# Scans what was just published: counts in the run summary, fixable findings as code scanning alerts.
scan:
needs: [release, publish]
if: needs.publish.result == 'success'
uses: ./.github/workflows/service_scan-images.yml
permissions:
actions: read
contents: read
security-events: write

report:
needs: [release, setup, build, test, publish]
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/action_publish-images-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ jobs:
github.event_name == 'workflow_dispatch' ||
github.event.pull_request.head.repo.full_name == github.repository
}}
secrets: inherit

test:
needs: [setup, base, build]
Expand All @@ -105,7 +104,9 @@ jobs:
with:
ref: ${{ inputs.pr_number && format('refs/pull/{0}/head', inputs.pr_number) || github.ref }}
authenticate_with_ghcr: false
secrets: inherit
secrets:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

report:
needs: [setup, build, test, publish]
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/action_update-dockerhub-readme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ on:
paths:
- README.md

permissions: {}

jobs:
update_container_readme:
runs-on: ubuntu-24.04
name: Push README to Docker Hub
permissions:
contents: read
steps:
- name: git checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: main

- name: push README to Dockerhub
uses: peter-evans/dockerhub-description@v5
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
with:
username: ${{ secrets.DOCKER_HUB_README_USERNAME }}
password: ${{ secrets.DOCKER_HUB_README_PASSWORD }}
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/scheduled-task_scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: OpenSSF Scorecard

# Scores the repository's supply chain practices (pinned dependencies, token permissions,
# signed releases, branch protection, and more) and publishes the result to
# scorecard.dev, which serves the badge in the README. Publishing only works while this
# workflow follows Scorecard's rules: no workflow-level env, defaults, or write
# permissions, and only Scorecard's approved actions in the job.
on:
workflow_dispatch:
branch_protection_rule:
push:
branches:
- main
schedule:
- cron: '41 6 * * 3'

permissions: {}

jobs:
analysis:
# Forks inherit the cron schedule. Their weekly rebuilds always fail and email whoever last edited the schedule upstream.
if: github.event_name != 'schedule' || github.repository == 'serversideup/docker-php'
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
actions: read
security-events: write
id-token: write
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Run Scorecard
uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: Upload the results to the Security tab
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: results.sarif
2 changes: 1 addition & 1 deletion .github/workflows/scheduled-task_update-readme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ jobs:
update-readme:
# Forks inherit the cron schedule. Their weekly rebuilds always fail and email whoever last edited the schedule upstream.
if: github.event_name != 'schedule' || github.repository == 'serversideup/docker-php'
uses: serversideup/.github/.github/workflows/update-readme.yml@master
uses: serversideup/.github/.github/workflows/update-readme.yml@0cc5de42120438871a6c36a91b6a7dc2a15dbd7f # v1.0.1
permissions:
contents: write
74 changes: 74 additions & 0 deletions .github/workflows/scheduled-task_watch-php-releases.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Watch for PHP releases

# PHP is compiled from php.net, so a new patch can ship as soon as php.net lists it.
# Every hour this resolves the active patches the same way a build does, and starts a
# production run when one of them has no image on Docker Hub yet. Like the weekly
# rebuild, that run rebuilds the latest release and only moves the floating tags.
on:
workflow_dispatch:
schedule:
- cron: '17 * * * *'

permissions: {}

jobs:
watch:
# Forks inherit the cron schedule. Their weekly rebuilds always fail and email whoever last edited the schedule upstream.
if: github.event_name != 'schedule' || github.repository == 'serversideup/docker-php'
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
actions: write
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PRODUCTION_WORKFLOW: action_publish-images-production.yml
DOCKER_HUB_REPOSITORY: serversideup/php
# A run that started this recently either is still going or already opened a ci-failure issue.
COOLDOWN_HOURS: 6
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Resolve the active PHP patches
run: ./scripts/get-php-versions.sh

# Each patch is looked up as its cli tag on the first OS it builds for. Anything other
# than "found" or "not found" is skipped, so a Docker Hub outage never starts a run.
- name: Find patches that have no image yet
id: missing
run: |
MISSING=()
while IFS=$'\t' read -r PATCH OS; do
TAG="${PATCH}-cli-${OS}"
STATUS=$(curl -s -o /dev/null -w '%{http_code}' --retry 3 --retry-delay 5 \
"https://hub.docker.com/v2/namespaces/${DOCKER_HUB_REPOSITORY%/*}/repositories/${DOCKER_HUB_REPOSITORY#*/}/tags/${TAG}")
case "$STATUS" in
200) echo "$PATCH is published ($TAG)" ;;
404) echo "$PATCH has no image yet ($TAG)"; MISSING+=("$PATCH") ;;
*) echo "::warning title=Docker Hub lookup failed::$TAG returned HTTP $STATUS, skipping $PATCH this hour" ;;
esac
done < <(yq -o=json scripts/conf/php-versions.yml | jq -r '
.php_versions[].minor_versions[]
| .base_os[0].name as $os
| .patch_versions[] | select(test("rc") | not)
| [., $os] | @tsv')
echo "patches=${MISSING[*]}" >> "$GITHUB_OUTPUT"

- name: Start a production run
if: steps.missing.outputs.patches != ''
env:
PATCHES: ${{ steps.missing.outputs.patches }}
run: |
RECENT=$(gh run list --workflow "$PRODUCTION_WORKFLOW" --limit 10 --json status,createdAt,url \
| jq -r --argjson hours "$COOLDOWN_HOURS" \
'[.[] | select(.status != "completed" or ((.createdAt | fromdateiso8601) > (now - $hours * 3600)))][0].url // ""')
if [ -n "$RECENT" ]; then
echo "::notice title=Already building::$RECENT started in the last ${COOLDOWN_HOURS} hours, so no new run was started for PHP $PATCHES."
exit 0
fi
gh workflow run "$PRODUCTION_WORKFLOW" -f reason="PHP ${PATCHES// /, } release"
echo "::notice title=Production run started::PHP $PATCHES has no published image yet."
59 changes: 40 additions & 19 deletions .github/workflows/service_build-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,24 @@ jobs:

steps:
- name: Check out code.
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ inputs.ref }}

- name: Download PHP Versions file
uses: actions/download-artifact@v8
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: php-versions.yml
path: ./artifacts

- name: Move PHP Versions file
run: mv ./artifacts/php-versions.yml ${{ inputs.php-versions-file }}
env:
PHP_VERSIONS_FILE: ${{ inputs.php-versions-file }}
run: mv ./artifacts/php-versions.yml "$PHP_VERSIONS_FILE"

- name: Set up Depot CLI
uses: depot/setup-action@v1
uses: depot/setup-action@91bc8495a33ebfc504ffc89e5674379ccf23c29c # v1.7.2

- name: "📦 Assemble the Docker Tags"
run: |
Expand All @@ -92,35 +95,43 @@ jobs:

- name: Set REPOSITORY_BUILD_VERSION
id: set_version
env:
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
INPUT_REF: ${{ inputs.ref }}
RUN_ID: ${{ github.run_id }}
run: |
if [[ "${{ github.ref_type }}" == "tag" || "${{ inputs.ref }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
if [[ "$REF_TYPE" == "tag" || "$INPUT_REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# Building from a tag (release or scheduled task)
echo "🚀 Setting REPOSITORY_BUILD_VERSION to Tag + Run ID"
if [[ "${{ github.ref_type }}" == "tag" ]]; then
VERSION="${{ github.ref_name }}"
if [[ "$REF_TYPE" == "tag" ]]; then
VERSION="$REF_NAME"
else
VERSION="${{ inputs.ref }}"
VERSION="$INPUT_REF"
fi
echo "REPOSITORY_BUILD_VERSION=${VERSION}-${{ github.run_id }}" >> "$GITHUB_ENV"
echo "REPOSITORY_BUILD_VERSION=${VERSION}-${RUN_ID}" >> "$GITHUB_ENV"
else
# Regular git build (PR, branch push, etc). Read the SHA from the checkout so
# builds dispatched for a PR number report the PR's commit, not the dispatched branch.
echo "👨‍🔬 Setting REPOSITORY_BUILD_VERSION to Git SHA + Run ID"
SHORT_SHA=$(git rev-parse --short=7 HEAD)
echo "REPOSITORY_BUILD_VERSION=git-${SHORT_SHA}-${{ github.run_id }}" >> "$GITHUB_ENV"
echo "REPOSITORY_BUILD_VERSION=git-${SHORT_SHA}-${RUN_ID}" >> "$GITHUB_ENV"
fi

- name: Compute NGINX build-arg (only for fpm-nginx)
id: compute_nginx
if: ${{ matrix.php_variation == 'fpm-nginx' }}
env:
BASE_OS: ${{ matrix.base_os }}
PHP_VERSIONS_FILE: ${{ inputs.php-versions-file }}
run: |
if command -v yq >/dev/null 2>&1; then
VERSION=$(yq -r '.operating_systems[].versions[] | select(.version == "${{ matrix.base_os }}") | .nginx_version' '${{ inputs.php-versions-file }}' | head -n1)
VERSION=$(yq -r '.operating_systems[].versions[] | select(.version == strenv(BASE_OS)) | .nginx_version' "$PHP_VERSIONS_FILE" | head -n1)
else
VERSION=$(awk -v key="${{ matrix.base_os }}" 'BEGIN{found=0} $1=="version:" && $2==key {found=1} found && $1=="nginx_version:" {print $2; exit}' "${{ inputs.php-versions-file }}")
VERSION=$(awk -v key="$BASE_OS" 'BEGIN{found=0} $1=="version:" && $2==key {found=1} found && $1=="nginx_version:" {print $2; exit}' "$PHP_VERSIONS_FILE")
fi
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "Unable to determine NGINX version for OS ${{ matrix.base_os }}" 1>&2
echo "Unable to determine NGINX version for OS $BASE_OS" 1>&2
exit 1
fi
echo "nginx_arg=NGINX_VERSION=$VERSION" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -152,7 +163,7 @@ jobs:
# onto the runner for the test step below instead.
- name: Build images
id: build
uses: depot/build-push-action@v1
uses: depot/build-push-action@0f27be7c41d76663a98518dfe68f1c46582242c4 # v1.19.0
with:
file: src/variations/${{ matrix.php_variation }}/Dockerfile
build-args: |
Expand All @@ -171,6 +182,11 @@ jobs:
save: ${{ inputs.publish }}
save-tags: ${{ github.run_id }}-${{ env.IMAGE_NAME }}
load: ${{ !inputs.publish }}
# Saved images carry an SBOM and full build provenance in their index, so the
# signature service_publish-images.yml puts on the index covers both. An image
# loaded onto the runner cannot hold attestations.
sbom: ${{ inputs.publish }}
provenance: ${{ inputs.publish && 'mode=max' || 'false' }}
tags: ${{ env.DOCKER_TAGS }}
outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=Supercharge your PHP experience with Docker images that are based off the official PHP images but are optimized to be run in production environments for Laravel and WordPress and more

Expand All @@ -179,24 +195,29 @@ jobs:
# dispatches the workflow for the pull request.
- name: Test the image (amd64)
if: ${{ !inputs.publish }}
run: bash scripts/test-image.sh "${DOCKER_TAGS%%,*}" "${{ matrix.patch_version }}"
env:
PHP_VERSION: ${{ matrix.patch_version }}
run: bash scripts/test-image.sh "${DOCKER_TAGS%%,*}" "$PHP_VERSION"

# The test, publish, and report workflows all read these files.
- name: Record image details
env:
PUBLISH: ${{ inputs.publish }}
BUILD_ID: ${{ steps.build.outputs.build-id }}
DIGEST: ${{ steps.build.outputs.digest }}
VARIATION: ${{ matrix.php_variation }}
PHP_VERSION: ${{ matrix.patch_version }}
BASE_OS: ${{ matrix.base_os }}
run: |
mkdir -p image-details
SAVED=null
if [ "$PUBLISH" = "true" ]; then
SAVED="\"registry.depot.dev/$(jq -r .id depot.json):${{ github.run_id }}-${IMAGE_NAME}\""
fi
jq -n \
--arg variation "${{ matrix.php_variation }}" \
--arg php "${{ matrix.patch_version }}" \
--arg os "${{ matrix.base_os }}" \
--arg variation "$VARIATION" \
--arg php "$PHP_VERSION" \
--arg os "$BASE_OS" \
--arg tags "$DOCKER_TAGS" \
--arg build_id "$BUILD_ID" \
--arg digest "$DIGEST" \
Expand All @@ -205,7 +226,7 @@ jobs:
| tee "image-details/${IMAGE_NAME}.json"

- name: Upload image details
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: image-details-${{ env.IMAGE_NAME }}
path: image-details/
Expand Down
Loading
Loading