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
7 changes: 3 additions & 4 deletions .github/workflows/legacy-v5.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ jobs:
TAG_PREFIX="rc${GITHUB_RUN_ID}-" \
docker buildx bake \
--set "*.platform=linux/${{ matrix.builder.arch }}" \
--set "*.output=type=docker" \
--load --progress=quiet \
--progress=quiet \
${BAKE_GROUP//.}
- name: Display tags built
run: |
Expand All @@ -57,7 +56,7 @@ jobs:
TAG_PREFIX="rc${GITHUB_RUN_ID}-" \
docker buildx bake \
--print --progress=quiet \
${BAKE_GROUP//.} | jq -r '.target[].tags[]');
${BAKE_GROUP//.} | jq -r '.target[].tags // [] | .[]');
for TAG in $TAGS; do docker push --quiet ${TAG}; done;

merge-and-publish:
Expand Down Expand Up @@ -113,7 +112,7 @@ jobs:
TAG_PREFIX="rc${GITHUB_RUN_ID}-" \
docker buildx bake \
--print --progress=quiet \
${BAKE_GROUP//.} | jq '.target[].tags[]'
${BAKE_GROUP//.} | jq '.target[].tags // [] | .[]'
- name: Create multiarch manifests
run: |
# Get all targets from the bake group
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/purge-cache.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Purge the cache of the layers

# Runs before the weekly build of v5, so that it starts from an empty cache and takes the updates of the packages
on:
schedule:
- cron: '12 3 * * 0'
workflow_dispatch:

jobs:
purge-cache:
runs-on: ubuntu-24.04
permissions:
packages: write
steps:
- name: Login to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Delete the versions of the cache
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CACHE_REPO="ghcr.io/${{ github.repository_owner }}/php-cache"
# GitHub refuses to delete the last version of a package: the "keep" tag (an empty image) stays
docker buildx imagetools create -t "${CACHE_REPO}:keep" docker.io/library/hello-world:latest
OWNER_TYPE=$(gh api "users/${{ github.repository_owner }}" --jq 'if .type == "Organization" then "orgs" else "users" end')
VERSIONS="${OWNER_TYPE}/${{ github.repository_owner }}/packages/container/php-cache/versions"
gh api --paginate "${VERSIONS}" --jq '.[] | select(.metadata.container.tags | index("keep") | not) | .id' \
| xargs -r -I {} gh api --silent -X DELETE "${VERSIONS}/{}"
48 changes: 24 additions & 24 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ jobs:
fail-fast: false
matrix:
php_version: ['8.5', '8.4', '8.3', '8.2']
variant: ['apache','cli','fpm']
builder: [ {arch: "amd64", os: "ubuntu-24.04"}, {arch: "arm64", os: "ubuntu-24.04-arm"}]
runs-on: ${{ matrix.builder.os }}
name: Build&test ${{ matrix.php_version }}-${{ matrix.variant }} ${{ matrix.builder.arch }}
name: Build&test ${{ matrix.php_version }} ${{ matrix.builder.arch }}
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -32,36 +31,37 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
with:
# The layers of the targets built from another target are only found in the registry cache from BuildKit 0.35
driver-opts: image=moby/buildkit:v0.35.0-rc1
- name: Build
run: |
PHP_VERSION="${{ matrix.php_version }}"
# Get last cached successful builds
TAGS=$(REPO="ghcr.io/${{ github.repository_owner }}/php" TAG_PREFIX="" docker buildx bake php${PHP_VERSION//.}-${{ matrix.variant }}-all --progress=quiet --print | jq -r '.target[].tags[]')
for TAG in $TAGS; do
docker pull --quiet ${TAG} || echo ""
done;
# Build the current builds
# The pull requests only read the cache of v5
CACHE_REPO="ghcr.io/${{ github.repository_owner }}/php-cache" \
CACHE_WRITE="${{ github.event_name != 'pull_request' && '1' || '0' }}" \
REPO="ghcr.io/${{ github.repository_owner }}/php" \
ARCH_SUFFIX="-${{ matrix.builder.arch }}" \
TAG_PREFIX="rc${GITHUB_RUN_ID}-" \
docker buildx bake \
--set "*.platform=linux/${{ matrix.builder.arch }}" \
--set "*.output=type=docker" \
--load --progress=quiet \
php${PHP_VERSION//.}-${{ matrix.variant }}-all
--progress=quiet \
php${PHP_VERSION//.}
- name: Display tags built
run: |
docker image ls --filter="reference=ghcr.io/${{ github.repository_owner }}/php" --format "{{.CreatedAt}}\t{{.Size}}\t{{.Repository}}:{{.Tag}}"
- name: Test
run: |
PHP_VERSION="${{ matrix.php_version }}"
REPO="ghcr.io/${{ github.repository_owner }}/php" \
TAG_PREFIX="rc${GITHUB_RUN_ID}-" \
PHP_VERSION="${{ matrix.php_version }}" \
PHP_VERSION="${PHP_VERSION}" \
ARCH_SUFFIX="-${{ matrix.builder.arch }}" \
BRANCH="v5" \
VARIANT="${{ matrix.variant }}" \
PLATFORM="linux/${{ matrix.builder.arch }}" \
./tests-suite/bash_unit -f tap ./tests-suite/*.sh
VARIANTS="apache cli fpm" \
NODE_VERSIONS="$(docker buildx bake --print --progress=quiet php${PHP_VERSION//.} | jq -r '[.target[].args.NODE_VERSION // empty] | unique | join(" ")')" \
./tests-suite/run_tests.sh
- name: Publish specific arch version
if: > # Only from the main repo
github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' ||
Expand All @@ -73,20 +73,20 @@ jobs:
TAG_PREFIX="rc${GITHUB_RUN_ID}-" \
docker buildx bake \
--print --progress=quiet \
php${PHP_VERSION//.}-${{ matrix.variant }}-all | jq -r '.target[].tags[]');
for TAG in $TAGS; do docker push --quiet ${TAG}; done;
php${PHP_VERSION//.} | jq -r '.target[].tags // [] | .[]');
# 4 images at the same time, with retries: ghcr.io sometimes answers "unknown blob"
printf '%s\n' $TAGS | xargs -P 4 -I {} bash -c 'for i in 1 2 3; do docker push --quiet "{}" && exit 0; sleep 5; done; exit 1'

merge-and-publish:
if: ${{ github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
needs:
- build
runs-on: ubuntu-latest
name: Merge and publish ${{ matrix.php_version }}-${{ matrix.variant }}
name: Merge and publish ${{ matrix.php_version }}
strategy:
fail-fast: false
matrix:
php_version: ['8.5', '8.4', '8.3', '8.2']
variant: ['apache','cli','fpm']
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -111,26 +111,26 @@ jobs:
- name: Fetch minor version of php
run: |
# Pull amd64 slim image to get PHP version
docker pull --quiet ghcr.io/${{ github.repository_owner }}/php:rc${GITHUB_RUN_ID}-${{ matrix.php_version }}-v5-slim-${{ matrix.variant }}-amd64
docker pull --quiet ghcr.io/${{ github.repository_owner }}/php:rc${GITHUB_RUN_ID}-${{ matrix.php_version }}-v5-slim-cli-amd64
# Retrieve minor
PHP_PATCH_MINOR=`docker run --rm ghcr.io/${{ github.repository_owner }}/php:rc${GITHUB_RUN_ID}-${{ matrix.php_version }}-v5-slim-${{ matrix.variant }}-amd64 php -v | head -n1 | grep -P '\d+\.\d+\.\d+' -o | head -n1`
PHP_PATCH_MINOR=`docker run --rm ghcr.io/${{ github.repository_owner }}/php:rc${GITHUB_RUN_ID}-${{ matrix.php_version }}-v5-slim-cli-amd64 php -v | head -n1 | grep -P '\d+\.\d+\.\d+' -o | head -n1`
echo "PHP_PATCH_MINOR=${PHP_PATCH_MINOR}"
echo "PHP_PATCH_MINOR=${PHP_PATCH_MINOR}" >> $GITHUB_ENV
- name: Get list of images for this variant
- name: Get list of images for this PHP version
id: get-images
run: |
PHP_VERSION="${{ matrix.php_version }}"
REPO="ghcr.io/${{ github.repository_owner }}/php" \
TAG_PREFIX="rc${GITHUB_RUN_ID}-" \
docker buildx bake \
--print --progress=quiet \
php${PHP_VERSION//.}-${{ matrix.variant }}-all | jq '.target[].tags[]'
php${PHP_VERSION//.} | jq '.target[].tags // [] | .[]'
- name: Create multiarch manifests
run: |
PHP_VERSION="${{ matrix.php_version }}"

# Get all targets from the bake group
TARGETS=$(docker buildx bake php${PHP_VERSION//.}-${{ matrix.variant }}-all --progress=quiet --print | jq -r ".group[\"php${PHP_VERSION//.}-${{ matrix.variant }}-all\"].targets[]")
# Get all the published targets of the bake group
TARGETS=$(docker buildx bake php${PHP_VERSION//.} --progress=quiet --print | jq -r '.target | to_entries[] | select(.value.tags) | .key')

for TARGET in $TARGETS; do
TAG_ORI=$(REPO="ghcr.io/${{ github.repository_owner }}/php" TAG_PREFIX="rc${GITHUB_RUN_ID}-" docker buildx bake ${TARGET} --progress=quiet --print | jq -r ".target | to_entries[] | select(.key == \"${TARGET}\") | .value.tags[0]")
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

* **2026-11-05**
* The *apache* and *fpm* images stop right away when a `docker stop` is requested during a long startup step (e.g. a `STARTUP_COMMAND_*`, or the configuration of the extensions on a loaded host): they were killed after the stop timeout (exit code 137 after 10 s)
* Apache `mod_cgid` (`APACHE_EXTENSION_CGID=1`) starts in the *slim-apache* image and with the built-in Apache of the *fpm* variant: its socket directory was missing
* The images of a PHP version are built from common layers: the extensions are installed once for the 3 variants. The *fat* images no longer contain the layers of the *slim* image of their variant, their content is unchanged
* The NodeJS images are tested by the CI

* **2026-11-03**
* The `php` command is about 20 ms faster on each call: it detects the changes of the `PHP_*` environment variables without starting PHP a first time. The configuration is now also regenerated when a `PHP_*` variable is removed
Expand Down
19 changes: 5 additions & 14 deletions Dockerfile.apache
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,20 @@
ARG INSTALL_CRON=1
ARG INSTALL_COMPOSER=1
ARG INSTALL_LOCALES=1
ARG PHP_VERSION="8.4"
ARG GLOBAL_VERSION="v5"
ARG REPO="thecodingmachine/php"
ARG TAG_PREFIX=""

ARG FROM_IMAGE="${REPO}:${TAG_PREFIX}${PHP_VERSION}-${GLOBAL_VERSION}-slim-apache"
FROM ${FROM_IMAGE}
# The slim Dockerfile of the variant applied on Dockerfile.extensions (see docker-bake.hcl)
FROM slim-with-extensions
LABEL authors="Mistral Oz <m.oz@thecodingmachine.com>, Julien Neuhart <j.neuhart@thecodingmachine.com>, David Négrier <d.negrier@thecodingmachine.com>"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

ARG TARGETOS
ARG TARGETARCH
ARG BLACKFIRE_VERSION=2

# |--------------------------------------------------------------------------
# | Main PHP extensions
# |--------------------------------------------------------------------------
# |
# | Installs the main PHP extensions
# |

# The packages of the variant enable all the extensions installed before them: enable only the ones of the slim image
USER root
RUN cd /usr/local/lib/thecodingmachine-php/extensions/current/ && ./install_all.sh && ./disable_all.sh
RUN /usr/bin/real_php /usr/local/bin/setup_extensions.php | bash && \
cd /usr/local/lib/thecodingmachine-php/extensions/current/ && ./disable_all.sh
USER docker

# |--------------------------------------------------------------------------
Expand Down
164 changes: 164 additions & 0 deletions Dockerfile.base
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#syntax=docker/dockerfile-upstream:1
# DO NOT EDIT THIS FILE : Make yours changes in /utils/Dockerfile.*.blueprint)
FROM ubuntu:24.04
LABEL authors="Mistral Oz <m.oz@thecodingmachine.com>, Julien Neuhart <j.neuhart@thecodingmachine.com>, David Négrier <d.negrier@thecodingmachine.com>"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Fixes some weird terminal issues such as broken clear / CTRL+L
#ENV TERM=linux

# Ensure apt doesn't ask questions when installing stuff
ENV DEBIAN_FRONTEND=noninteractive

ARG PHP_VERSION
ARG TARGETOS
ARG TARGETARCH
ARG BLACKFIRE_VERSION=2

ENV TARGETOS=${TARGETOS}
ENV TARGETARCH=${TARGETARCH}
ENV BLACKFIRE_VERSION=${BLACKFIRE_VERSION}
ENV PHP_VERSION=${PHP_VERSION}

# |--------------------------------------------------------------------------
# | Main PHP extensions
# |--------------------------------------------------------------------------
# |
# | Installs the main PHP extensions
# |

# Install php an other packages
RUN apt update \
&& apt upgrade -y \
&& apt install -y --no-install-recommends curl ca-certificates \
&& curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb \
&& dpkg -i /tmp/debsuryorg-archive-keyring.deb \
&& . /etc/os-release \
&& echo "deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg] https://packages.sury.org/php/ ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/php.list \
&& apt update \
&& apt install -y --no-install-recommends \
nano \
sudo \
git \
iproute2 \
procps \
curl \
unzip \
ca-certificates \
openssh-client \
&& apt clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
# Note: From PHP 8.5, opcache is built-in and php${PHP_VERSION}-opcache package doesn't exist
RUN OPCACHE_PACKAGE="" \
&& if [ "${PHP_VERSION}" != "8.5" ]; then \
OPCACHE_PACKAGE="php${PHP_VERSION}-opcache"; \
fi \
&& apt update \
&& apt install -y --no-install-recommends \
php${PHP_VERSION}-cli \
php${PHP_VERSION}-curl \
php${PHP_VERSION}-mbstring \
${OPCACHE_PACKAGE} \
php${PHP_VERSION}-readline \
php${PHP_VERSION}-xml \
php${PHP_VERSION}-zip \
&& apt clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*

# |--------------------------------------------------------------------------
# | User
# |--------------------------------------------------------------------------
# |
# | Define a default user with sudo rights.
# |

RUN userdel ubuntu && rm -rf /home/ubuntu
RUN useradd -ms /bin/bash docker && adduser docker sudo
# Users in the sudoers group can sudo as root without password.
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/sudo

# |--------------------------------------------------------------------------
# | Default php.ini file
# |--------------------------------------------------------------------------
# |
# | For some reasons, the official image has a php.ini-production.cli file
# | but no php.ini-development.cli file. Let's create this one.
# |

RUN cp /usr/lib/php/${PHP_VERSION}/php.ini-development /usr/lib/php/${PHP_VERSION}/php.ini-development.cli && \
sed -i 's/^disable_functions/;disable_functions/g' /usr/lib/php/${PHP_VERSION}/php.ini-development.cli && \
sed -i 's/^memory_limit = .*/memory_limit = -1/g' /usr/lib/php/${PHP_VERSION}/php.ini-development.cli

#ADD https://raw.githubusercontent.com/php/php-src/PHP-${PHP_VERSION}/php.ini-production /usr/local/etc/php/php.ini-production
#ADD https://raw.githubusercontent.com/php/php-src/PHP-${PHP_VERSION}/php.ini-development /usr/local/etc/php/php.ini-development
#RUN chmod 644 /usr/local/etc/php/php.ini-*

ENV TEMPLATE_PHP_INI=development

# Let's remove the default CLI php.ini file (it will be copied from TEMPLATE_PHP_INI)
RUN rm /etc/php/${PHP_VERSION}/cli/php.ini

# |--------------------------------------------------------------------------
# | Composer
# |--------------------------------------------------------------------------
# |
# | Installs Composer to easily manage your PHP dependencies.
# |

#ENV COMPOSER_ALLOW_SUPERUSER 1

RUN curl -sS --retry 5 --retry-delay 2 https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer &&\
chmod +x /usr/local/bin/composer
COPY utils/select_composer_version.sh /usr/local/bin/select_composer_version.sh

# TODO: utils.php in /usr/local/bin... bof!
COPY utils/utils.php /usr/local/bin/utils.php
RUN mv /usr/bin/php /usr/bin/real_php
COPY utils/php_proxy.sh /usr/bin/php
COPY utils/php_env_fingerprint.sh /usr/local/bin/php_env_fingerprint.sh
RUN touch /opt/php_env_fingerprint
COPY utils/generate_conf.php /usr/local/bin/generate_conf.php
COPY utils/setup_extensions.php /usr/local/bin/setup_extensions.php

# |--------------------------------------------------------------------------
# | Default PHP extensions to be enabled
# | By default, enable all the extensions that are enabled on a base Ubuntu install
# |--------------------------------------------------------------------------
ENV PHP_EXTENSION_CALENDAR=1 \
PHP_EXTENSION_CTYPE=1 \
PHP_EXTENSION_CURL=1 \
PHP_EXTENSION_DOM=1 \
PHP_EXTENSION_EXIF=1 \
PHP_EXTENSION_FILEINFO=1 \
PHP_EXTENSION_FTP=1 \
PHP_EXTENSION_GETTEXT=1 \
PHP_EXTENSION_ICONV=1 \
PHP_EXTENSION_JSON=1 \
PHP_EXTENSION_MBSTRING=1 \
PHP_EXTENSION_OPCACHE=1 \
PHP_EXTENSION_PDO=1 \
PHP_EXTENSION_PHAR=1 \
PHP_EXTENSION_POSIX=1 \
PHP_EXTENSION_READLINE=1 \
PHP_EXTENSION_SHMOP=1 \
PHP_EXTENSION_SIMPLEXML=1 \
PHP_EXTENSION_SOCKETS=1 \
PHP_EXTENSION_SYSVMSG=1 \
PHP_EXTENSION_SYSVSEM=1 \
PHP_EXTENSION_SYSVSHM=1 \
PHP_EXTENSION_TOKENIZER=1 \
PHP_EXTENSION_WDDX=1 \
PHP_EXTENSION_XML=1 \
PHP_EXTENSION_XMLREADER=1 \
PHP_EXTENSION_XMLWRITER=1 \
PHP_EXTENSION_XSL=1 \
PHP_EXTENSION_ZIP=1

USER docker
RUN composer global require bamarni/symfony-console-autocomplete && \
rm -rf ~/.composer/cache

USER root

COPY extensions/ /usr/local/lib/thecodingmachine-php/extensions
RUN ln -s ${PHP_VERSION} /usr/local/lib/thecodingmachine-php/extensions/current
Loading