Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
51 changes: 28 additions & 23 deletions .github/workflows/docker-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,37 @@ concurrency:
cancel-in-progress: true

jobs:
setup:
runs-on: ubuntu-latest
outputs:
php-versions: ${{ steps.matrix.outputs.php-versions }}
s6-version: ${{ steps.s6.outputs.version }}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The job output names php-versions and s6-version contain hyphens, which makes them awkward/unsafe to reference via dot-notation in GitHub Actions expressions (it can be parsed as subtraction). Rename these outputs to use _ (e.g., php_versions, s6_version) or switch all references to bracket notation (e.g., needs.setup.outputs['php-versions']).

Copilot uses AI. Check for mistakes.
steps:
- name: Determine PHP versions to test
id: matrix
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo 'php-versions=["8.4","8.2"]' >> $GITHUB_OUTPUT
echo "::notice::PR detected — testing PHP 8.4 + 8.2 only (skipping 8.3)"
else
echo 'php-versions=["8.4","8.3","8.2"]' >> $GITHUB_OUTPUT
fi
Comment on lines +41 to +47
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR-only matrix reduction to 8.4 + 8.2 is currently applied only to matrix.php-version, but any hard-coded matrix.include entries can still introduce 8.3 jobs even on pull_request runs. To get the intended CI savings, generate the full matrix (including the trixie v2 extras) in the setup job, or make the include list conditional on the same PR check.

Copilot uses AI. Check for mistakes.

- name: Get latest s6-overlay version
id: s6
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
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step fetches the latest s6-overlay tag without -f/error handling or authentication. If the API call is rate-limited or fails, jq -r .tag_name can return an empty/null value that then propagates into the Docker build args. Consider using curl -fSL (and validating the tag is non-empty) and/or authenticating with Authorization: Bearer ${{ github.token }} to reduce flakiness.

Suggested change
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
set -euo pipefail
RAW_RESPONSE="$(curl -fSLs \
-H "Authorization: Bearer ${{ github.token }}" \
"https://api.github.com/repos/just-containers/s6-overlay/releases/latest")"
S6_OVERLAY_VERSION="$(printf '%s\n' "${RAW_RESPONSE}" | jq -r '.tag_name')"
if [ -z "${S6_OVERLAY_VERSION}" ] || [ "${S6_OVERLAY_VERSION}" = "null" ]; then
echo "Error: Failed to determine latest s6-overlay version from GitHub API." >&2
exit 1
fi
echo "version=${S6_OVERLAY_VERSION}" >> "$GITHUB_OUTPUT"

Copilot uses AI. Check for mistakes.
echo "✅ Latest s6-overlay version: ${S6_OVERLAY_VERSION}"

build-and-test:
needs: setup
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
variant: [v1, v2]
php-version: ['8.4', '8.3', '8.2']
php-version: ${{ fromJson(needs.setup.outputs.php-versions) }}
php-type: [fpm, cli, apache]
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This expression references a setup output key containing a hyphen (php-versions). With dot-notation (needs.setup.outputs.php-versions) GitHub Actions can interpret this as subtraction and fail expression evaluation. Use bracket notation (needs.setup.outputs['php-versions']) or rename the output to an identifier-safe name (e.g., php_versions).

Copilot uses AI. Check for mistakes.
php-base: [alpine, bookworm]
exclude:
Expand Down Expand Up @@ -88,18 +112,6 @@ jobs:
- 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

Expand Down Expand Up @@ -138,7 +150,7 @@ jobs:
VERSION=${{ steps.vars.outputs.VERSION }}
PHPVERSION=${{ matrix.php-version }}
BASEOS=${{ matrix.php-base }}
S6_OVERLAY_VERSION=${{ steps.s6-version.outputs.version }}
S6_OVERLAY_VERSION=${{ needs.setup.outputs.s6-version }}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs.setup.outputs.s6-version references an output key with a hyphen; with dot-notation this can be parsed incorrectly and break the build args. Use bracket notation (needs.setup.outputs['s6-version']) or rename the output to an identifier-safe name (e.g., s6_version).

Suggested change
S6_OVERLAY_VERSION=${{ needs.setup.outputs.s6-version }}
S6_OVERLAY_VERSION=${{ needs.setup.outputs['s6-version'] }}

Copilot uses AI. Check for mistakes.
BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }}
VCS_REF=${{ github.sha }}
tags: test-${{ steps.vars.outputs.TAG }}
Expand Down Expand Up @@ -295,7 +307,7 @@ jobs:
echo "::notice::✅ Build and tests passed for ${{ matrix.variant }} - ${{ steps.vars.outputs.TAG }}"

publish:
needs: build-and-test
needs: [setup, build-and-test]
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'schedule')
runs-on: ubuntu-latest
strategy:
Expand Down Expand Up @@ -356,13 +368,6 @@ jobs:
- 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:
Expand Down Expand Up @@ -429,7 +434,7 @@ jobs:
VERSION=${{ steps.vars.outputs.VERSION }}
PHPVERSION=${{ matrix.php-version }}
BASEOS=${{ matrix.php-base }}
S6_OVERLAY_VERSION=${{ steps.s6-version.outputs.version }}
S6_OVERLAY_VERSION=${{ needs.setup.outputs.s6-version }}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here: needs.setup.outputs.s6-version uses an output key with a hyphen, which can break expression parsing. Use bracket notation (needs.setup.outputs['s6-version']) or rename the output key.

Suggested change
S6_OVERLAY_VERSION=${{ needs.setup.outputs.s6-version }}
S6_OVERLAY_VERSION=${{ needs.setup.outputs['s6-version'] }}

Copilot uses AI. Check for mistakes.
BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }}
VCS_REF=${{ github.sha }}
tags: |
Expand Down
55 changes: 14 additions & 41 deletions Dockerfile.v1
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,30 @@ ARG BASEOS
# Set environment variables
ENV DEBIAN_FRONTEND=noninteractive

COPY extras/retry.sh /usr/local/bin/retry
RUN chmod +x /usr/local/bin/retry

# Install dependencies based on the base OS
RUN if [ "$BASEOS" = "bookworm" ]; then \
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$BASEOS \
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked,id=aptlists-$BASEOS \
--mount=type=cache,target=/var/cache/apk,sharing=locked,id=apk-$BASEOS \
if [ "$BASEOS" = "bookworm" ]; then \
Comment on lines +13 to +15
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache mount for /var/cache/apk won’t be used while the Alpine branch installs with apk add --no-cache (which disables the package cache). Either drop the apk cache mount, or switch to a cache-friendly apk invocation so this mount actually improves rebuild times.

Copilot uses AI. Check for mistakes.
echo 'deb http://deb.debian.org/debian bookworm main' > /etc/apt/sources.list && \
apt-get update && \
apt-get -y upgrade && \
apt-get install -y --no-install-recommends curl git zip unzip ghostscript imagemagick optipng gifsicle pngcrush jpegoptim libjpeg-turbo-progs pngquant webp && \
rm -rf /var/lib/apt/lists/*; \
apt-get install -y --no-install-recommends curl git zip unzip ghostscript imagemagick optipng gifsicle pngcrush jpegoptim libjpeg-turbo-progs pngquant webp; \
elif [ "$BASEOS" = "alpine" ]; then \
apk update && \
apk add --no-cache curl git zip unzip ghostscript imagemagick optipng gifsicle pngcrush jpegoptim libjpeg-turbo libjpeg-turbo-utils pngquant libwebp-tools; \
fi

# Add all needed PHP extensions with retry logic for transient network failures
RUN for ATTEMPT in 1 2 3; do \
if curl -sSLf -o /usr/local/bin/install-php-extensions \
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions; then \
break; \
else \
if [ $ATTEMPT -lt 3 ]; then \
case $ATTEMPT in \
1) SLEEP_TIME=5 ;; \
2) SLEEP_TIME=10 ;; \
esac; \
echo "Download attempt $ATTEMPT failed, retrying in ${SLEEP_TIME}s..."; \
sleep $SLEEP_TIME; \
rm -f /usr/local/bin/install-php-extensions; \
else \
echo "Failed to download install-php-extensions after 3 attempts"; \
exit 1; \
fi; \
fi; \
done && \
# Download and install PHP extensions with retry for transient failures
RUN retry 3 curl -sSLf -o /usr/local/bin/install-php-extensions \
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
chmod +x /usr/local/bin/install-php-extensions && \
for ATTEMPT in 1 2 3; do \
if install-php-extensions amqp bcmath bz2 calendar ctype exif intl imagick imap json mbstring ldap mcrypt memcached mongodb \
mysqli opcache pdo_mysql pdo_pgsql pgsql redis snmp soap sockets tidy timezonedb uuid vips xsl yaml zip zstd @composer; then \
break; \
else \
if [ $ATTEMPT -lt 3 ]; then \
case $ATTEMPT in \
1) SLEEP_TIME=5 ;; \
2) SLEEP_TIME=10 ;; \
esac; \
echo "Extension installation attempt $ATTEMPT failed, retrying in ${SLEEP_TIME}s..."; \
sleep $SLEEP_TIME; \
else \
echo "Failed to install PHP extensions after 3 attempts"; \
exit 1; \
fi; \
fi; \
done
retry 3 install-php-extensions \
amqp bcmath bz2 calendar ctype exif intl imagick imap json mbstring ldap mcrypt memcached mongodb \
mysqli opcache pdo_mysql pdo_pgsql pgsql redis snmp soap sockets tidy timezonedb uuid vips xsl yaml zip zstd @composer

# Enable Apache rewrite mod, if applicable
RUN if command -v a2enmod; then a2enmod rewrite; fi
Expand Down
52 changes: 13 additions & 39 deletions Dockerfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ ARG VERSION
# Set environment variables
ENV DEBIAN_FRONTEND=noninteractive

COPY extras/retry.sh /usr/local/bin/retry
RUN chmod +x /usr/local/bin/retry

# OCI standard labels
LABEL org.opencontainers.image.title="php-docker" \
org.opencontainers.image.description="PHP runtime with s6-overlay and curated extensions" \
Expand All @@ -26,7 +29,10 @@ LABEL org.opencontainers.image.title="php-docker" \

# Install build dependencies, PHP extensions, runtime libraries, and s6-overlay
# Then clean up build-only packages in a single layer to minimize image size
RUN if [ "$BASEOS" = "trixie" ] || [ "$BASEOS" = "bookworm" ]; then \
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$BASEOS \
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked,id=aptlists-$BASEOS \
--mount=type=cache,target=/var/cache/apk,sharing=locked,id=apk-$BASEOS \
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache mount for /var/cache/apk likely won’t have any effect because the Alpine branch uses apk add --no-cache, which avoids writing to the apk cache directory. Either remove the apk cache mount (to reduce complexity) or adjust the apk install flags to take advantage of the cache mount.

Suggested change
--mount=type=cache,target=/var/cache/apk,sharing=locked,id=apk-$BASEOS \

Copilot uses AI. Check for mistakes.
if [ "$BASEOS" = "trixie" ] || [ "$BASEOS" = "bookworm" ]; then \
apt-get update && \
apt-get -y upgrade && \
# Install build tools, dev packages, and runtime libraries together
Expand Down Expand Up @@ -129,25 +135,8 @@ RUN if [ "$BASEOS" = "trixie" ] || [ "$BASEOS" = "bookworm" ]; then \
libxpm libxpm-dev; \
fi && \
# Download PHP extension installer with retry
for ATTEMPT in 1 2 3; do \
if curl -sSLf -o /usr/local/bin/install-php-extensions \
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions; then \
break; \
else \
if [ $ATTEMPT -lt 3 ]; then \
case $ATTEMPT in \
1) SLEEP_TIME=5 ;; \
2) SLEEP_TIME=10 ;; \
esac; \
echo "Download attempt $ATTEMPT failed, retrying in ${SLEEP_TIME}s..."; \
sleep $SLEEP_TIME; \
rm -f /usr/local/bin/install-php-extensions; \
else \
echo "Failed to download install-php-extensions after 3 attempts"; \
exit 1; \
fi; \
fi; \
done && \
retry 3 curl -sSLf -o /usr/local/bin/install-php-extensions \
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
chmod +x /usr/local/bin/install-php-extensions && \
# Install PHP extensions
install-php-extensions \
Expand All @@ -173,25 +162,10 @@ RUN if [ "$BASEOS" = "trixie" ] || [ "$BASEOS" = "bookworm" ]; then \
*) S6_ARCH="x86_64" ;; \
esac && \
echo "Downloading s6-overlay ${S6_OVERLAY_VERSION} for ${S6_ARCH}" && \
for ATTEMPT in 1 2 3; do \
if wget -O /tmp/s6-overlay-noarch.tar.xz https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz && \
wget -O /tmp/s6-overlay-${S6_ARCH}.tar.xz https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz; then \
break; \
else \
if [ $ATTEMPT -lt 3 ]; then \
case $ATTEMPT in \
1) SLEEP_TIME=5 ;; \
2) SLEEP_TIME=10 ;; \
esac; \
echo "Download attempt $ATTEMPT failed, retrying in ${SLEEP_TIME}s..."; \
sleep $SLEEP_TIME; \
rm -f /tmp/s6-overlay-*.tar.xz; \
else \
echo "Failed to download s6-overlay after 3 attempts"; \
exit 1; \
fi; \
fi; \
done && \
retry 3 wget -O /tmp/s6-overlay-noarch.tar.xz \
https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz && \
retry 3 wget -O /tmp/s6-overlay-${S6_ARCH}.tar.xz \
https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz && \
tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \
tar -C / -Jxpf /tmp/s6-overlay-${S6_ARCH}.tar.xz && \
rm /tmp/s6-overlay-noarch.tar.xz /tmp/s6-overlay-${S6_ARCH}.tar.xz && \
Expand Down
21 changes: 21 additions & 0 deletions extras/retry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh
# Retry a command with exponential backoff
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "exponential backoff", but the implemented sleep (SLEEP_TIME=$((5 * ATTEMPT))) is linear. Either update the comment to match the behavior, or adjust the sleep formula to be exponential so the documentation isn’t misleading.

Suggested change
# Retry a command with exponential backoff
# Retry a command with linear backoff

Copilot uses AI. Check for mistakes.
# Usage: retry <max_attempts> <command...>
# Example: retry 3 curl -sSLf -o /tmp/file https://example.com/file

MAX=$1
shift

for ATTEMPT in $(seq 1 "$MAX"); do
if "$@"; then
exit 0
fi
if [ "$ATTEMPT" -lt "$MAX" ]; then
SLEEP_TIME=$((5 * ATTEMPT))
echo "Attempt $ATTEMPT/$MAX failed, retrying in ${SLEEP_TIME}s..."
sleep "$SLEEP_TIME"
fi
done

echo "Failed after $MAX attempts: $*"
exit 1
2 changes: 1 addition & 1 deletion extras/test-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ build_v1() {
echo "Building v1: ${IMAGE_NAME}:${tag}"
echo " VERSION=${version}, PHPVERSION=${phpversion}, BASEOS=${baseos}"

docker build -f Dockerfile.v1 \
DOCKER_BUILDKIT=1 docker build -f Dockerfile.v1 \
--build-arg VERSION="${version}" \
--build-arg PHPVERSION="${phpversion}" \
--build-arg BASEOS="${baseos}" \
Expand Down
Loading