diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml
index 38e90fe..1c71c67 100644
--- a/.github/workflows/build-images.yml
+++ b/.github/workflows/build-images.yml
@@ -23,13 +23,22 @@ concurrency:
jobs:
build:
- runs-on: ubuntu-24.04 # ubuntu-latest
+ name: Build ${{ matrix.target.platform }} ${{ matrix.variant }} ${{ matrix.php_version }}
+ runs-on: ${{ matrix.target.runner }}
timeout-minutes: 10
strategy:
+ fail-fast: false
matrix:
- php_version: ['8.4', '8.3', '8.1', '8.0', '7.4']
+ php_version: ['8.5' , '8.4', '8.3', '8.1', '8.0', '7.4']
variant: ['apache', 'apache-chrome']
+ target:
+ - platform: linux/amd64
+ runner: ubuntu-24.04 # ubuntu-latest
+ artifact_suffix: linux-amd64
+ - platform: linux/arm64
+ runner: ubuntu-24.04-arm # ubuntu-latest
+ artifact_suffix: linux-arm64
exclude:
- php_version: '7.4'
variant: 'apache-chrome'
@@ -38,17 +47,13 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v6
- - name: Github Short SHA
- run: |
- echo "GITHUB_SHA_SHORT=$(echo $GITHUB_SHA | cut -c 1-7)" >> $GITHUB_ENV
-
- name: Set variables
run: |
if [ "${{ matrix.variant }}" == "apache" ]; then
- echo "DOCKER_TAG=${{ matrix.php_version }}" >> $GITHUB_ENV
+ echo "DOCKER_TAG=${{ github.event.number }}_${{ matrix.php_version }}" >> $GITHUB_ENV
echo "DOCKER_FILE=Dockerfile" >> $GITHUB_ENV
elif [ "${{ matrix.variant }}" == "apache-chrome" ]; then
- echo "DOCKER_TAG=${{ matrix.php_version }}-headless" >> $GITHUB_ENV
+ echo "DOCKER_TAG=${{ github.event.number }}_${{ matrix.php_version }}-headless" >> $GITHUB_ENV
echo "DOCKER_FILE=Dockerfile-headless" >> $GITHUB_ENV
else
echo "Invalid variant: ${{ matrix.variant }}"
@@ -58,21 +63,94 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- - name: Login to Docker Registry
- run: echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u ${{ secrets.DOCKER_HUB_USER }} --password-stdin
-
- name: Login to GitHub Container Registry
- run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
+ run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
- - name: Build and push
+ - name: Build and push by digest
+ id: build
uses: docker/build-push-action@v7
with:
context: .
- file: ./${{ matrix.php_version }}/${{ env.DOCKER_FILE }}
- tags:
- ghcr.io/${{ github.repository }}:${{ env.DOCKER_TAG }},
- ghcr.io/${{ github.repository }}:${{ env.GITHUB_SHA_SHORT }}
- push: true
- platforms: linux/amd64,linux/arm64
+ file: ./aio/${{ env.DOCKER_FILE }}
+ build-args: |
+ PHP_VERSION=${{ matrix.php_version }}
+ platforms: ${{ matrix.target.platform }}
+ outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true
+ provenance: mode=min
cache-from: type=gha
cache-to: type=gha,mode=max
+
+ - name: Export digest
+ run: |
+ mkdir -p "/tmp/digests/${{ env.DOCKER_TAG }}"
+ DIGEST="${{ steps.build.outputs.digest }}"
+ touch "/tmp/digests/${{ env.DOCKER_TAG }}/${DIGEST#sha256:}"
+
+ - name: Upload digest
+ uses: actions/upload-artifact@v7
+ with:
+ name: digests-${{ env.DOCKER_TAG }}-${{ matrix.target.artifact_suffix }}
+ path: /tmp/digests
+ if-no-files-found: error
+ retention-days: 1
+
+ manifest:
+ name: Create multi-arch manifests
+ runs-on: ubuntu-24.04-arm
+ timeout-minutes: 10
+ needs: build
+
+ steps:
+ - name: Github Short SHA
+ run: |
+ echo "GITHUB_SHA_SHORT=$(echo "$GITHUB_SHA" | cut -c 1-7)" >> "$GITHUB_ENV"
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Login to Docker Registry
+ run: echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u ${{ secrets.DOCKER_HUB_USER }} --password-stdin
+
+ - name: Login to GitHub Container Registry
+ run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
+
+ - name: Download digests
+ uses: actions/download-artifact@v8
+ with:
+ pattern: digests-*
+ path: /tmp/digests
+ merge-multiple: true
+
+ - name: Create multi-arch manifests
+ run: |
+ set -euo pipefail
+
+ for digest_dir in /tmp/digests/*; do
+ if [ ! -d "$digest_dir" ]; then
+ continue
+ fi
+
+ docker_tag="$(basename "$digest_dir")"
+ digest_count="$(find "$digest_dir" -maxdepth 1 -type f | wc -l | tr -d ' ')"
+
+ if [ "$digest_count" -lt 2 ]; then
+ echo "Expected at least 2 digests for ${docker_tag}, found ${digest_count}"
+ find "$digest_dir" -maxdepth 1 -type f -print
+ exit 1
+ fi
+
+ refs=""
+ for digest_file in "$digest_dir"/*; do
+ digest="$(basename "$digest_file")"
+ refs="${refs} ghcr.io/${{ github.repository }}@sha256:${digest}"
+ done
+
+ echo "Creating manifest for ghcr.io/${{ github.repository }}:${docker_tag}"
+
+ docker buildx imagetools create \
+ -t "ghcr.io/${{ github.repository }}:${docker_tag}" \
+ -t "ghcr.io/${{ github.repository }}:${GITHUB_SHA_SHORT}-${docker_tag}" \
+ ${refs}
+
+ docker buildx imagetools inspect "ghcr.io/${{ github.repository }}:${docker_tag}"
+ done
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
new file mode 100644
index 0000000..922d0ec
--- /dev/null
+++ b/.github/workflows/build-test.yml
@@ -0,0 +1,78 @@
+name: Test Build Images on PR
+
+permissions:
+ contents: read
+
+on:
+ workflow_dispatch: {}
+ pull_request:
+ types: [opened, synchronize]
+ paths-ignore:
+ - "README.md"
+ - "docker-compose.yml"
+ - "examples/**"
+ - ".run/**"
+ - ".circleci/**"
+
+concurrency:
+ group: "${{ github.workflow }}-${{ github.ref }}"
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Build ${{ matrix.target.platform }} ${{ matrix.variant }} ${{ matrix.php_version }}
+ runs-on: ${{ matrix.target.runner }}
+ timeout-minutes: 10
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php_version: ['8.5' , '8.4', '8.3', '8.1', '8.0', '7.4']
+ variant: ['apache', 'apache-chrome']
+ target:
+ - platform: linux/amd64
+ runner: ubuntu-24.04 # ubuntu-latest
+ artifact_suffix: linux-amd64
+ - platform: linux/arm64
+ runner: ubuntu-24.04-arm # ubuntu-latest
+ artifact_suffix: linux-arm64
+ exclude:
+ - php_version: '7.4'
+ variant: 'apache-chrome'
+
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v6
+
+ - name: Set variables
+ run: |
+ if [ "${{ matrix.variant }}" == "apache" ]; then
+ echo "DOCKER_TAG=pr_${{ github.event.number }}_${{ matrix.php_version }}" >> $GITHUB_ENV
+ echo "DOCKER_FILE=Dockerfile" >> $GITHUB_ENV
+ elif [ "${{ matrix.variant }}" == "apache-chrome" ]; then
+ echo "DOCKER_TAG=pr_${{ github.event.number }}_${{ matrix.php_version }}-headless" >> $GITHUB_ENV
+ echo "DOCKER_FILE=Dockerfile-headless" >> $GITHUB_ENV
+ else
+ echo "Invalid variant: ${{ matrix.variant }}"
+ exit 1
+ fi
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Login to GitHub Container Registry
+ run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
+
+ - name: Build and push by digest
+ id: build
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: ./aio/${{ env.DOCKER_FILE }}
+ build-args: |
+ PHP_VERSION=${{ matrix.php_version }}
+ platforms: ${{ matrix.target.platform }}
+ push: false
+ provenance: mode=min
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
diff --git a/README.md b/README.md
index 3c66c34..7956683 100644
--- a/README.md
+++ b/README.md
@@ -2,20 +2,20 @@
## Why
-The images formed from this repo is to help minimize the extra steps needed to get up and running with a PHP Image with apache to serve traffic. All docker images are built in a CI pipeline and run every 2 days to ensure all dependencies are up-to-date with the source they are installed from.
+The images formed from this repo are to help minimize the extra steps needed to get up and running with a PHP Image with apache to serve traffic. All docker images are built in a CI pipeline and run every 2 days to ensure all dependencies are up to date with the source they are installed from.
-After using this docker image in other pipelines I was able to cut build times down by 4-5 minutes per build.
+After using this docker image in other pipelines, I was able to cut build times down by 4-5 minutes per build.
+
+_Images built also support ARM/ARM64 architectures_
## Changes
-This image is the PHP 7.4 (and 8.0, 8.1) packaged with the following changes:
-- PHP (version 7.4 or 8.0)
+This image is the PHP+Apache Image packaged with the following changes:
+- PHP (version 7.4 or 8.0–8.5)
- Apache
-- OPCache (and configured)
+- OPCache (configured)
- OpenSSL
- ZIP (with libzip-dev and php extension)
-- git
-- iputils
- Redis Extension
- GD and imagick Extensions
- PDO
@@ -25,15 +25,16 @@ This image is the PHP 7.4 (and 8.0, 8.1) packaged with the following changes:
- Apache Signatures Off
- Apache Allow htaccess overrides
- Apache Logs Updated to include the remote ips
+- Apache Server Status (configured on :8282/healthz and :8282/server-status)
---
# Images
-## [Github Images](https://github.com/nhalstead/php-apache-core/pkgs/container/php-apache-core)
+## [GitHub Images](https://github.com/nhalstead/php-apache-core/pkgs/container/php-apache-core)
> ghcr.io/nhalstead/php-apache-core
-> #### Automatically built on Wednesday
+> #### Automatically built on Wednesday by GitHub Actions
| PHP Version | Tag: Apache | Tag: Apache + Chrome |
|:------------|:------------|:---------------------|
@@ -46,7 +47,7 @@ This image is the PHP 7.4 (and 8.0, 8.1) packaged with the following changes:
## [Docker Hub Images](https://hub.docker.com/r/nhalstead00/php-apache-core)
> nhalstead00/php-apache-core
-> #### Automatically built every 2 days
+> #### Automatically built every 2 days by CircleCI (will be replaced with GitHub Actions in the future)
| PHP Version | Tag: Apache | Tag: Apache + Chrome |
|:------------|:------------|:---------------------|
@@ -54,5 +55,3 @@ This image is the PHP 7.4 (and 8.0, 8.1) packaged with the following changes:
| 8.1 | 8.1-latest | 8.1-headless-latest |
| 8.0 | 8.0-latest | 8.0-headless-latest |
| 7.4 | 7.4-latest | |
-
-
diff --git a/aio/Dockerfile b/aio/Dockerfile
new file mode 100644
index 0000000..012cf79
--- /dev/null
+++ b/aio/Dockerfile
@@ -0,0 +1,51 @@
+# Normal Operations for setup of Apache and ENV Vars
+# Allow building different PHP versions from build args (CI uses PHP_VERSION)
+ARG PHP_VERSION=8.3
+ARG PHP_TAG=${PHP_VERSION}-apache
+FROM php:${PHP_TAG} AS base
+
+# Configure OPCache
+ENV PHP_OPCACHE_VALIDATE_TIMESTAMPS=0
+ENV PHP_OPCACHE_MAX_ACCELERATED_FILES=10000
+ENV PHP_OPCACHE_MEMORY_CONSUMPTION=192
+ENV PHP_OPCACHE_MAX_WASTED_PERCENTAGE=10
+
+COPY common/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
+
+RUN apt-get update -y; \
+ apt-get install -y openssl zip unzip curl libpng-dev libzip-dev libmagickwand-dev --no-install-recommends; \
+ docker-php-ext-install -j"$(nproc)" gd zip pdo mysqli pdo_mysql opcache; \
+ pecl install redis imagick; \
+ docker-php-ext-enable -j"$(nproc)" redis imagick; \
+ apt autoremove -y; \
+ curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \
+ rm -rf /var/lib/apt/lists/*;
+
+FROM base
+
+# Configure Apache: combine multiple small RUNs into one layer to reduce image size
+# Copy scripts and config files first so they can be used in the same layer
+COPY common/remoteip-env /usr/sbin
+COPY common/remoteip-clear /usr/sbin
+COPY common/opcache-preload /usr/sbin
+COPY common/opcache-status /usr/sbin
+COPY common/apache-status /usr/sbin
+COPY common/status.conf /etc/apache2/sites-enabled/000-status.conf
+COPY common/healthz.html /var/www/html/healthz.html
+
+RUN set -eux; \
+ a2enmod rewrite remoteip status; \
+ touch /etc/apache2/conf-available/remoteip.conf; \
+ ln -s /etc/apache2/conf-available/remoteip.conf /etc/apache2/conf-enabled/remoteip.conf || true; \
+ chmod +x /usr/sbin/remoteip-* /usr/sbin/opcache-* /usr/sbin/apache-status || true; \
+ sed -ri -e 's!AllowOverride None!AllowOverride All!g' /etc/apache2/apache2.conf; \
+ sed -ri -e 's!Listen 80!Listen 8181!g' /etc/apache2/ports.conf; \
+ sed -ri -e 's!VirtualHost \*:80!VirtualHost \*:8181!g' /etc/apache2/sites-enabled/000-default.conf; \
+ echo "ServerSignature off" >> /etc/apache2/apache2.conf; \
+ echo "ServerTokens Prod" >> /etc/apache2/apache2.conf; \
+ printf '\n\n Listen 8282\n\n' >> /etc/apache2/ports.conf; \
+ sed -ri -e 's!#LoadModule remoteip_module!LoadModule remoteip_module!g' /etc/apache2/apache2.conf; \
+ sed -ri -e 's/LogFormat .* combined/LogFormat "%a %{c}a %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\" %I %O" combined/g' /etc/apache2/apache2.conf
+
+EXPOSE 8181
+EXPOSE 8282
\ No newline at end of file
diff --git a/aio/Dockerfile-headless b/aio/Dockerfile-headless
new file mode 100644
index 0000000..17092ba
--- /dev/null
+++ b/aio/Dockerfile-headless
@@ -0,0 +1,66 @@
+# Normal Operations for setup of Apache and ENV Vars
+# Allow building different PHP versions from build args (CI uses PHP_VERSION)
+ARG PHP_VERSION=8.3
+ARG PHP_TAG=${PHP_VERSION}-apache
+FROM php:${PHP_TAG} AS base
+
+# Configure OPCache
+ENV PHP_OPCACHE_VALIDATE_TIMESTAMPS=0
+ENV PHP_OPCACHE_MAX_ACCELERATED_FILES=10000
+ENV PHP_OPCACHE_MEMORY_CONSUMPTION=192
+ENV PHP_OPCACHE_MAX_WASTED_PERCENTAGE=10
+
+COPY common/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
+
+RUN apt-get update -y; \
+ apt-get install -y openssl zip unzip curl libpng-dev libzip-dev libmagickwand-dev --no-install-recommends; \
+ docker-php-ext-install -j"$(nproc)" gd zip pdo mysqli pdo_mysql opcache; \
+ pecl install redis imagick; \
+ docker-php-ext-enable -j"$(nproc)" redis imagick; \
+ apt autoremove -y; \
+ curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \
+ rm -rf /var/lib/apt/lists/*;
+
+FROM base AS browser
+
+# As needed for Chrome to Run
+# https://github.com/beganovich/snappdf#downloading-local-chromium
+# https://github.com/beganovich/snappdf?tab=readme-ov-file#headless-chrome-doesnt-launch-on-unix
+RUN apt-get update -y; \
+ apt-get install -y ca-certificates fonts-liberation libnss3 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release xdg-utils --no-install-recommends; \
+ rm -rf /var/lib/apt/lists/*;
+
+FROM browser
+
+# Configure Apache and combine many small RUN steps into a single layer to
+# reduce image size and improve caching.
+# This groups enabling modules, adding configs, making files executable and adjusting Apache settings.
+COPY common/remoteip-env /usr/sbin
+COPY common/remoteip-clear /usr/sbin
+COPY common/opcache-preload /usr/sbin
+COPY common/opcache-status /usr/sbin
+COPY common/apache-status /usr/sbin
+COPY common/status.conf /etc/apache2/sites-enabled/000-status.conf
+COPY common/healthz.html /var/www/html/healthz.html
+
+RUN set -eux; \
+ a2enmod rewrite remoteip status; \
+ touch /etc/apache2/conf-available/remoteip.conf; \
+ ln -s /etc/apache2/conf-available/remoteip.conf /etc/apache2/conf-enabled/remoteip.conf || true; \
+ chmod +x /usr/sbin/remoteip-* /usr/sbin/opcache-* /usr/sbin/apache-status || true; \
+ sed -ri -e 's!AllowOverride None!AllowOverride All!g' /etc/apache2/apache2.conf; \
+ sed -ri -e 's!Listen 80!Listen 8181!g' /etc/apache2/ports.conf; \
+ sed -ri -e 's!VirtualHost \*:80!VirtualHost \*:8181!g' /etc/apache2/sites-enabled/000-default.conf; \
+ echo "ServerSignature off" >> /etc/apache2/apache2.conf; \
+ echo "ServerTokens Prod" >> /etc/apache2/apache2.conf; \
+ printf '\n\n Listen 8282\n\n' >> /etc/apache2/ports.conf; \
+ sed -ri -e 's!#LoadModule remoteip_module!LoadModule remoteip_module!g' /etc/apache2/apache2.conf; \
+ sed -ri -e 's/LogFormat .* combined/LogFormat "%a %{c}a %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\" %I %O" combined/g' /etc/apache2/apache2.conf
+
+EXPOSE 8181
+EXPOSE 8282
+
+
+
+
+
diff --git a/common/apache-status b/common/apache-status
new file mode 100644
index 0000000..35bce69
--- /dev/null
+++ b/common/apache-status
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+URL="127.0.0.1:8282/server-status?auto"
+
+if command -v curl >/dev/null 2>&1; then
+ curl -fsS "$URL"
+elif command -v wget >/dev/null 2>&1; then
+ wget -q -O - "$URL"
+else
+ echo "Error: neither curl nor wget is installed" >&2
+ exit 1
+fi
diff --git a/common/healthz.html b/common/healthz.html
new file mode 100644
index 0000000..a0aba93
--- /dev/null
+++ b/common/healthz.html
@@ -0,0 +1 @@
+OK
\ No newline at end of file
diff --git a/common/status.conf b/common/status.conf
new file mode 100644
index 0000000..57f50da
--- /dev/null
+++ b/common/status.conf
@@ -0,0 +1,18 @@
+
+
+ SetHandler server-status
+ Require all granted
+ # Order deny,allow
+ # Deny from all
+ # Allow from 127.0.0.1 # Allow access only from localhost
+
+
+ DocumentRoot /var/www/html
+
+ Alias /healthz /var/www/html/healthz.html
+
+ ForceType text/plain
+
+
+ CustomLog ${APACHE_LOG_DIR}/access.log combined
+
\ No newline at end of file