Skip to content

Docker CI (v1 + v2) #65

Docker CI (v1 + v2)

Docker CI (v1 + v2) #65

Workflow file for this run

name: Docker CI (v1 + v2)
on:
push:
# Only run full CI on pushes to main (publishes/metrics). Running on
# every branch push plus pull_request creates duplicate runs when a PR
# receives new commits because GitHub emits both push and pull_request
# events for the same push. Restrict push to main to avoid that.
branches:
- 'main'
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE'
- '.gitignore'
pull_request:
branches:
- '**'
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE'
- '.gitignore'
schedule:
- cron: '0 3 * * 2' # Weekly on Tuesday at 3:00 AM UTC
workflow_dispatch:
concurrency:
group: docker-ci-${{ github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
# runs-on: ubuntu-latest
runs-on: arc-s2-runner
strategy:
fail-fast: false
matrix:
variant: [v1, v2]
php-version: ['8.4', '8.3', '8.2']
php-type: [fpm, cli]
php-base: [alpine, bookworm]
exclude:
- php-type: apache
php-base: alpine
# v2 uses trixie as the Debian base; bookworm retained for v1
- variant: v2
php-base: bookworm
include:
# v2 builds on trixie for Debian images
- variant: v2
php-version: '8.4'
php-type: fpm
php-base: trixie
- variant: v2
php-version: '8.4'
php-type: cli
php-base: trixie
- variant: v2
php-version: '8.3'
php-type: fpm
php-base: trixie
- variant: v2
php-version: '8.3'
php-type: cli
php-base: trixie
- variant: v2
php-version: '8.2'
php-type: fpm
php-base: trixie
- variant: v2
php-version: '8.2'
php-type: cli
php-base: trixie
name: ${{ matrix.variant }}-${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get latest s6-overlay version
id: s6-version
run: |
S6_OVERLAY_VERSION="$(curl -s https://api.github.com/repos/just-containers/s6-overlay/releases/latest | jq -r .tag_name)"
echo "version=${S6_OVERLAY_VERSION}" >> $GITHUB_OUTPUT
echo "✅ Latest s6-overlay version: ${S6_OVERLAY_VERSION}"
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: amd64,arm64,arm
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set build variables
id: vars
run: |
VERSION="${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base }}"
TAG_BASE="php-docker:${VERSION}"
if [ "${{ matrix.variant }}" = "v2" ]; then
TAG="${TAG_BASE}-v2"
DOCKERFILE="Dockerfile.v2"
else
TAG="${TAG_BASE}"
DOCKERFILE="Dockerfile.v1"
fi
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
echo "TAG=${TAG}" >> $GITHUB_OUTPUT
echo "DOCKERFILE=${DOCKERFILE}" >> $GITHUB_OUTPUT
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_OUTPUT
echo "CACHE_SCOPE=${{ matrix.variant }}-${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base }}" >> $GITHUB_OUTPUT
- name: Build test image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ steps.vars.outputs.DOCKERFILE }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ steps.vars.outputs.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ steps.vars.outputs.CACHE_SCOPE }}
build-args: |
VERSION=${{ steps.vars.outputs.VERSION }}
PHPVERSION=${{ matrix.php-version }}
BASEOS=${{ matrix.php-base }}
S6_OVERLAY_VERSION=${{ steps.s6-version.outputs.version }}
BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }}
VCS_REF=${{ github.sha }}
tags: test-${{ steps.vars.outputs.TAG }}
- name: Smoke tests - PHP version
run: |
echo "::group::Testing PHP version"
if ! docker run --rm test-${{ steps.vars.outputs.TAG }} php -v | tee php-version.txt; then
echo "::error::Failed to run php -v"
docker logs test-${{ steps.vars.outputs.TAG }} 2>&1 || true
exit 1
fi
if ! grep -q "${{ matrix.php-version }}" php-version.txt; then
echo "::error::PHP version mismatch - expected ${{ matrix.php-version }}"
cat php-version.txt
exit 1
fi
echo "✅ PHP version correct"
echo "::endgroup::"
- name: Smoke tests - Basic PHP CLI run
run: |
echo "::group::Testing basic PHP CLI execution"
SAPI=$(docker run --rm test-${{ steps.vars.outputs.TAG }} php -r "echo PHP_SAPI;" 2>&1)
if [ $? -ne 0 ]; then
echo "::error::Failed to execute PHP CLI test"
echo "$SAPI"
exit 1
fi
echo "✅ PHP CLI runs successfully (SAPI: $SAPI)"
echo "::endgroup::"
- name: Smoke tests - Extensions
run: |
echo "::group::Testing PHP extensions"
if ! docker run --rm test-${{ steps.vars.outputs.TAG }} php -m | tee extensions.txt; then
echo "::error::Failed to list PHP extensions"
docker logs test-${{ steps.vars.outputs.TAG }} 2>&1 || true
exit 1
fi
# Core extensions that should be present
REQUIRED_EXTS="gd json mysqli zip"
MISSING_EXTS=""
for ext in $REQUIRED_EXTS; do
if ! grep -qi "$ext" extensions.txt; then
MISSING_EXTS="$MISSING_EXTS $ext"
echo "::error::Missing extension: $ext"
else
echo "✅ Extension $ext found"
fi
done
if [ -n "$MISSING_EXTS" ]; then
echo "::error::Missing required extensions:$MISSING_EXTS"
echo "Available extensions:"
cat extensions.txt
exit 1
fi
echo "::endgroup::"
- name: Smoke tests - Entrypoint quick-run
run: |
echo "::group::Testing entrypoint/init quick-run"
OUTPUT=$(docker run --rm test-${{ steps.vars.outputs.TAG }} php -r "echo 'entrypoint-ok';" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "::error::Entrypoint test failed with exit code $EXIT_CODE"
echo "$OUTPUT"
exit 1
fi
if ! echo "$OUTPUT" | grep -q "entrypoint-ok"; then
echo "::error::Entrypoint did not produce expected output"
echo "Output: $OUTPUT"
exit 1
fi
echo "✅ Entrypoint executes successfully"
echo "::endgroup::"
- name: Smoke tests - Directory permissions
run: |
echo "::group::Testing directory permissions"
DIRS_TO_CHECK="/tmp /var/www"
for dir in $DIRS_TO_CHECK; do
if ! docker run --rm test-${{ steps.vars.outputs.TAG }} sh -c "test -d $dir && [ -w $dir ]" 2>&1; then
echo "::warning::Directory $dir either doesn't exist or is not writable"
else
echo "✅ Directory $dir exists and is writable"
fi
done
echo "::endgroup::"
- name: Smoke tests - v2 specific (s6-overlay)
if: matrix.variant == 'v2'
run: |
echo "::group::Testing s6-overlay presence and PID1 behavior"
# Check s6-overlay directory
if ! docker run --rm test-${{ steps.vars.outputs.TAG }} sh -c "test -d /etc/s6-overlay" 2>&1; then
echo "::error::s6-overlay directory not found at /etc/s6-overlay"
docker run --rm test-${{ steps.vars.outputs.TAG }} ls -la /etc/ 2>&1 || true
exit 1
fi
echo "✅ s6-overlay directory exists"
# Check init binary
if ! docker run --rm test-${{ steps.vars.outputs.TAG }} sh -c "test -f /init" 2>&1; then
echo "::error::s6 init binary not found at /init"
docker run --rm test-${{ steps.vars.outputs.TAG }} ls -la / 2>&1 || true
exit 1
fi
echo "✅ s6 init binary exists"
# Check for s6-overlay services directory
if ! docker run --rm test-${{ steps.vars.outputs.TAG }} sh -c "test -d /etc/s6-overlay/s6-rc.d || test -d /etc/services.d" 2>&1; then
echo "::warning::s6 services directory not found (expected /etc/s6-overlay/s6-rc.d or /etc/services.d)"
else
echo "✅ s6 services directory found"
fi
echo "::endgroup::"
- name: Smoke tests - FPM specific
if: matrix.php-type == 'fpm'
run: |
echo "::group::Testing PHP-FPM"
if ! docker run --rm test-${{ steps.vars.outputs.TAG }} php-fpm --version 2>&1 | tee fpm-version.txt; then
echo "::error::Failed to run php-fpm --version"
cat fpm-version.txt || true
exit 1
fi
echo "✅ PHP-FPM version check passed"
echo "::endgroup::"
- name: Summary
run: |
echo "::notice::✅ Build and tests passed for ${{ matrix.variant }} - ${{ steps.vars.outputs.TAG }}"
publish:
needs: build-and-test
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'schedule')
# runs-on: ubuntu-latest
runs-on: arc-s2-runner
strategy:
fail-fast: false
matrix:
variant: [v1, v2]
php-version: ['8.4', '8.3', '8.2']
php-type: [fpm, cli, apache]
php-base: [alpine, bookworm]
exclude:
- php-type: apache
php-base: alpine
# v2 uses trixie as the Debian base; bookworm retained for v1
- variant: v2
php-base: bookworm
include:
# v2 builds on trixie for Debian images
- variant: v2
php-version: '8.4'
php-type: fpm
php-base: trixie
- variant: v2
php-version: '8.4'
php-type: cli
php-base: trixie
- variant: v2
php-version: '8.4'
php-type: apache
php-base: trixie
- variant: v2
php-version: '8.3'
php-type: fpm
php-base: trixie
- variant: v2
php-version: '8.3'
php-type: cli
php-base: trixie
- variant: v2
php-version: '8.3'
php-type: apache
php-base: trixie
- variant: v2
php-version: '8.2'
php-type: fpm
php-base: trixie
- variant: v2
php-version: '8.2'
php-type: cli
php-base: trixie
- variant: v2
php-version: '8.2'
php-type: apache
php-base: trixie
name: publish-${{ matrix.variant }}-${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get latest s6-overlay version
id: s6-version
run: |
S6_OVERLAY_VERSION="$(curl -s https://api.github.com/repos/just-containers/s6-overlay/releases/latest | jq -r .tag_name)"
echo "version=${S6_OVERLAY_VERSION}" >> $GITHUB_OUTPUT
echo "✅ Latest s6-overlay version: ${S6_OVERLAY_VERSION}"
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: amd64,arm64,arm
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Quay.io
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_ROBOT_TOKEN }}
- name: Set publish variables
id: vars
run: |
VERSION="${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base }}"
if [ "${{ matrix.variant }}" = "v2" ]; then
TAG_SUFFIX="-v2"
DOCKERFILE="Dockerfile.v2"
else
TAG_SUFFIX=""
DOCKERFILE="Dockerfile.v1"
fi
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
echo "TAG_SUFFIX=${TAG_SUFFIX}" >> $GITHUB_OUTPUT
echo "DOCKERFILE=${DOCKERFILE}" >> $GITHUB_OUTPUT
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_OUTPUT
echo "DOCKERHUB_TAG=docker.io/${{ secrets.DOCKERHUB_USERNAME }}/php-docker:${VERSION}${TAG_SUFFIX}" >> $GITHUB_OUTPUT
echo "GHCR_TAG=ghcr.io/kingpin/php-docker:${VERSION}${TAG_SUFFIX}" >> $GITHUB_OUTPUT
echo "QUAY_TAG=quay.io/kingpinx1/php-docker:${VERSION}${TAG_SUFFIX}" >> $GITHUB_OUTPUT
echo "CACHE_SCOPE=${{ matrix.variant }}-${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base }}" >> $GITHUB_OUTPUT
- name: Build and push multi-arch image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ steps.vars.outputs.DOCKERFILE }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
provenance: mode=max
cache-from: type=gha,scope=${{ steps.vars.outputs.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ steps.vars.outputs.CACHE_SCOPE }}
build-args: |
VERSION=${{ steps.vars.outputs.VERSION }}
PHPVERSION=${{ matrix.php-version }}
BASEOS=${{ matrix.php-base }}
S6_OVERLAY_VERSION=${{ steps.s6-version.outputs.version }}
BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }}
VCS_REF=${{ github.sha }}
tags: |
${{ steps.vars.outputs.DOCKERHUB_TAG }}
${{ steps.vars.outputs.GHCR_TAG }}
${{ steps.vars.outputs.QUAY_TAG }}
labels: |
com.sumguy.php-docker.php.variant=${{ matrix.php-type }}
com.sumguy.php-docker.image.variant=${{ matrix.variant }}
com.sumguy.php-docker.build_id=${{ github.run_id }}
com.sumguy.php-docker.build_url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
com.sumguy.php-docker.built_by=github-actions/docker-ci
- name: Create bookworm compatibility tag for v2 trixie images
if: matrix.variant == 'v2' && matrix.php-base == 'trixie'
run: |
echo "::group::Creating bookworm compatibility tags for trixie-built v2 image"
# Replace 'trixie' with 'bookworm' in tag names to maintain backward compatibility
BOOKWORM_VERSION="${{ matrix.php-version }}-${{ matrix.php-type }}-bookworm"
# Create manifest aliases pointing trixie-built images to bookworm tags
docker buildx imagetools create -t \
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/php-docker:${BOOKWORM_VERSION}-v2 \
${{ steps.vars.outputs.DOCKERHUB_TAG }}
docker buildx imagetools create -t \
ghcr.io/kingpin/php-docker:${BOOKWORM_VERSION}-v2 \
${{ steps.vars.outputs.GHCR_TAG }}
docker buildx imagetools create -t \
quay.io/kingpinx1/php-docker:${BOOKWORM_VERSION}-v2 \
${{ steps.vars.outputs.QUAY_TAG }}
echo "✅ Created bookworm compatibility tags pointing to trixie image"
echo "::endgroup::"
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: image
image-ref: ${{ steps.vars.outputs.DOCKERHUB_TAG }}
format: 'sarif'
severity: 'CRITICAL,HIGH'
output: 'trivy-results-${{ matrix.variant }}-${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base }}.sarif'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results-${{ matrix.variant }}-${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base }}.sarif'