From 40fb22ac448718e2e625b7fbb30487fc87496c50 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 7 Feb 2024 17:46:25 -0600 Subject: [PATCH 001/304] Removed comma trimming from package install --- .../bin/docker-php-serversideup-dep-install-alpine | 10 +--------- .../bin/docker-php-serversideup-dep-install-debian | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine b/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine index 97c0eb018..20940cc6f 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine +++ b/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine @@ -24,22 +24,14 @@ if [ "$NAME" != "Alpine Linux" ] || [ $# -eq 0 ]; then exit 0 fi -############ -# Functions -############ -convert_comma_delimited_to_space_separated() { - echo $1 | tr ',' ' ' -} - ############ # Main ############ -DEP_PACKAGES=$(convert_comma_delimited_to_space_separated "$@") +DEP_PACKAGES="$@" echo "🤖 Installing: $DEP_PACKAGES" apk update apk add --no-cache $DEP_PACKAGES - echo "🧼 Cleaning up installation of: $DEP_PACKAGES" rm -rf /var/cache/apk/* diff --git a/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian b/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian index a38c0d880..07b155658 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian +++ b/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian @@ -25,22 +25,14 @@ if [ "$NAME" != "Debian GNU/Linux" ] || [ $# -eq 0 ]; then exit 0 fi -############ -# Functions -############ -convert_comma_delimited_to_space_separated() { - echo $1 | tr ',' ' ' -} - ############ # Main ############ -DEP_PACKAGES=$(convert_comma_delimited_to_space_separated "$@") +DEP_PACKAGES="$@" echo "🤖 Installing: $DEP_PACKAGES" apt-get update apt-get install -y $DEP_PACKAGES - echo "🧼 Cleaning up installation of: $DEP_PACKAGES" apt-get clean rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* From fde0dff4845403b24682542efc22d2d10af7a918 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 8 Feb 2024 10:46:32 -0600 Subject: [PATCH 002/304] Moved Matrix job generation to a dedicated script --- .../service_docker-build-and-publish.yml | 7 ++-- scripts/generate-matrix.sh | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100755 scripts/generate-matrix.sh diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index a556f36f0..5b32f1ab7 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -26,7 +26,7 @@ jobs: setup-matrix: runs-on: ubuntu-22.04 outputs: - php-version-map-json: ${{ steps.get-php-versions.outputs.php-version-map-json }} + php-version-map-json: ${{ steps.generate-matrix.outputs.php-version-map-json }} steps: - name: Check out code uses: actions/checkout@v4 @@ -46,9 +46,10 @@ jobs: fi - name: Assemble PHP versions into the matrix. 😎 - id: get-php-versions + id: generate-matrix run: | - MATRIX_JSON=$(yq -o=json scripts/conf/php-versions.yml | jq -c '{include: [(.php_variations[] | {name, supported_os: (.supported_os // ["alpine", "bullseye", "bookworm"])} ) as $variation | .php_versions[] | .minor_versions[] | .patch_versions[] as $patch | .base_os[] as $os | select($variation.supported_os | if length == 0 then . else . | index($os.name) end) | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name}]}') + chmod +x ./scripts/generate_matrix.sh + MATRIX_JSON=$(./scripts/generate_matrix.sh) echo "php-version-map-json=${MATRIX_JSON}" >> $GITHUB_OUTPUT echo "${MATRIX_JSON}" | jq '.' diff --git a/scripts/generate-matrix.sh b/scripts/generate-matrix.sh new file mode 100755 index 000000000..85a290006 --- /dev/null +++ b/scripts/generate-matrix.sh @@ -0,0 +1,33 @@ +#!/bin/bash +################################################### +# Usage: generate-matrix.sh +################################################### +# This script is used to generate the GitHub Actions +# matrix for the PHP versions and OS combinations. +# +# 👉 REQUIRED FILES +# - PHP_VERSIONS_FILE must be valid and set to a valid file path +# (defaults to scripts/conf/php-versions.yml) + +set -euo pipefail + +# Path to the PHP versions configuration file +PHP_VERSIONS_FILE="${PHP_VERSIONS_FILE:-"scripts/conf/php-versions.yml"}" + +# Generate and output the MATRIX_JSON +yq -o=json "$PHP_VERSIONS_FILE" | + jq -c '{ + include: [ + (.php_variations[] | + {name, supported_os: (.supported_os // ["alpine", "bullseye", "bookworm"]), excluded_minor_versions: (.excluded_minor_versions // [])} + ) as $variation | + .php_versions[] | + .minor_versions[] | + # Check if the minor version is not in the excluded list for the variation + select([.minor] | inside($variation.excluded_minor_versions | map(.)) | not) | + .patch_versions[] as $patch | + .base_os[] as $os | + select($variation.supported_os | if length == 0 then . else . | index($os.name) end) | + {patch_version: $patch, base_os: $os.name, php_variation: $variation.name} + ] + }' \ No newline at end of file From 6e9f0d6737a442535b82e8c58ae62c16ef0d75cf Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 8 Feb 2024 10:46:48 -0600 Subject: [PATCH 003/304] Added first draft of FrankenPHP --- scripts/conf/php-versions-base-config.yml | 7 ++ src/variations/frankenphp/Dockerfile | 99 +++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/variations/frankenphp/Dockerfile diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 84dd721a5..0f263ece6 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -50,6 +50,13 @@ php_variations: - bullseye - bookworm - name: fpm-nginx + - name: frankenphp + supported_os: + - bookworm + excluded_minor_versions: + - "7.4" + - "8.0" + - "8.1" - name: unit supported_os: # PHP doesn't include "embed SAPI" on Alpine (https://github.com/docker-library/php/pull/1355#issuecomment-1352087633) - bullseye diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile new file mode 100644 index 000000000..fd833b810 --- /dev/null +++ b/src/variations/frankenphp/Dockerfile @@ -0,0 +1,99 @@ +ARG BASE_OS_VERSION='bookworm' +ARG PHP_VERSION='8.3' +ARG PHP_VARIATION='frankenphp' +ARG GO_VERSION='1.21' +ARG BASE_IMAGE="php:${PHP_VERSION}-zts-${BASE_OS_VERSION}" + +########## +# FrankenPHP: Common Image +########## +FROM ${BASE_IMAGE} as common + +# copy our scripts +COPY --chmod=755 src/common/ / + +########## +# Set GoLang Base Image +########## +FROM golang:${GO_VERSION}-${BASE_OS_VERSION} as golang-base + +########## +# FrankenPHP: Build +########## +FROM common as build +ARG FRANKEN_PHP_VERSION='1.1.0' + +COPY --from=golang-base /usr/local/go /usr/local/go + +ENV PATH /usr/local/go/bin:$PATH + +RUN docker-php-serversideup-dep-install-debian \ + libargon2-dev \ + libbrotli-dev \ + libcurl4-openssl-dev \ + libonig-dev \ + libreadline-dev \ + libsodium-dev \ + libsqlite3-dev \ + libssl-dev \ + libxml2-dev \ + zlib1g-dev \ + && \ + curl -L https://github.com/dunglas/frankenphp/archive/refs/tags/v${FRANKEN_PHP_VERSION}.tar.gz | tar -xz -C /tmp/ && \ + cd /tmp/frankenphp-${FRANKEN_PHP_VERSION}/caddy/frankenphp && \ + CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -o /usr/local/bin/frankenphp + +########## +# FrankenPHP: Main Image +########## +FROM common + +COPY --from=build /usr/local/bin/frankenphp /usr/local/bin/frankenphp + +LABEL org.opencontainers.image.title="serversideup/php (${PHP_VARIATION})" \ + org.opencontainers.image.description="Supercharge your PHP experience. Based off the offical PHP images, serversideup/php includes pre-configured PHP extensions and settings for enhanced performance and security. Optimized for Laravel and WordPress." \ + org.opencontainers.image.url="https://serversideup.net/open-source/docker-php/" \ + org.opencontainers.image.source="https://github.com/serversideup/docker-php" \ + org.opencontainers.image.documentation="https://serversideup.net/open-source/docker-php/docs/" \ + org.opencontainers.image.vendor="ServerSideUp" \ + org.opencontainers.image.authors="Jay Rogers (@jaydrogers)" \ + org.opencontainers.image.version="${REPOSITORY_BUILD_VERSION}" \ + org.opencontainers.image.licenses="GPL-3.0-or-later" + +ENV APP_BASE_DIR=/var/www/html \ + COMPOSER_ALLOW_SUPERUSER=1 \ + COMPOSER_HOME=/composer \ + COMPOSER_MAX_PARALLEL_HTTP=24 \ + DISABLE_DEFAULT_CONFIG=false \ + LOG_OUTPUT_LEVEL=warn \ + PHP_DATE_TIMEZONE="UTC" \ + PHP_DISPLAY_ERRORS=Off \ + PHP_DISPLAY_STARTUP_ERRORS=Off \ + PHP_ERROR_LOG="/dev/stderr" \ + PHP_ERROR_REPORTING="22527" \ + PHP_MAX_EXECUTION_TIME="99" \ + PHP_MAX_INPUT_TIME="-1" \ + PHP_MEMORY_LIMIT="256M" \ + PHP_OPEN_BASEDIR="" \ + PHP_POST_MAX_SIZE="100M" \ + PHP_SESSION_COOKIE_SECURE=false \ + PHP_UPLOAD_MAX_FILE_SIZE="100M" + +# install pecl extensions & dependencies +RUN docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE}" && \ + docker-php-serversideup-dep-install-debian "${DEPENDENCY_PACKAGES_DEBIAN}" && \ + docker-php-serversideup-install-php-ext-installer && \ + \ + # Make composer cache directory + mkdir -p "${COMPOSER_HOME}" && \ + chown -R www-data:www-data "${COMPOSER_HOME}" && \ + \ + # Install default PHP extensions + install-php-extensions "${DEPENDENCY_PHP_EXTENSIONS}" + +# install composer from Composer's official Docker image +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +WORKDIR ${APP_BASE_DIR} + +ENTRYPOINT ["docker-php-serversideup-entrypoint"] \ No newline at end of file From 137daa91acb33f52190377bf774e9d1215a58231 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 8 Feb 2024 10:49:04 -0600 Subject: [PATCH 004/304] Add BUILDKIT_PROGRESS environment variable --- .github/workflows/service_docker-build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 5b32f1ab7..7804af66a 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -135,7 +135,7 @@ jobs: PHP_VERSION=${{ matrix.patch_version }} PHP_VARIATION=${{ matrix.php_variation }} REPOSITORY_BUILD_VERSION=${{ env.REPOSITORY_BUILD_VERSION }} - + BUILDKIT_PROGRESS=plain platforms: | linux/amd64 linux/arm/v7 From cf876a3ca3b1d8ebf38e13756a3188de691e08d0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 8 Feb 2024 10:51:43 -0600 Subject: [PATCH 005/304] Fix typo in script name --- .github/workflows/service_docker-build-and-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 7804af66a..e66b12f93 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -48,8 +48,8 @@ jobs: - name: Assemble PHP versions into the matrix. 😎 id: generate-matrix run: | - chmod +x ./scripts/generate_matrix.sh - MATRIX_JSON=$(./scripts/generate_matrix.sh) + chmod +x ./scripts/generate-matrix.sh + MATRIX_JSON=$(./scripts/generate-matrix.sh) echo "php-version-map-json=${MATRIX_JSON}" >> $GITHUB_OUTPUT echo "${MATRIX_JSON}" | jq '.' From 4c5bd04d64030f8e2580684720a45d9ae445a65e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 8 Feb 2024 11:18:40 -0600 Subject: [PATCH 006/304] Changed name to development images --- .github/workflows/action_publish-images-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action_publish-images-dev.yml b/.github/workflows/action_publish-images-dev.yml index e5ee2eebb..38f4c22be 100644 --- a/.github/workflows/action_publish-images-dev.yml +++ b/.github/workflows/action_publish-images-dev.yml @@ -1,4 +1,4 @@ -name: Docker Publish (Beta Images) +name: Docker Publish (Dev Images) on: workflow_dispatch: From ac18d72de145d642a23bf40a5d28336727397b27 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 9 Feb 2024 16:13:40 -0600 Subject: [PATCH 007/304] Working local PHP --- src/variations/frankenphp/Dockerfile | 58 ++++++---------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index fd833b810..2bf3418e6 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -1,54 +1,17 @@ ARG BASE_OS_VERSION='bookworm' ARG PHP_VERSION='8.3' -ARG PHP_VARIATION='frankenphp' -ARG GO_VERSION='1.21' -ARG BASE_IMAGE="php:${PHP_VERSION}-zts-${BASE_OS_VERSION}" - -########## -# FrankenPHP: Common Image -########## -FROM ${BASE_IMAGE} as common - -# copy our scripts -COPY --chmod=755 src/common/ / - -########## -# Set GoLang Base Image -########## -FROM golang:${GO_VERSION}-${BASE_OS_VERSION} as golang-base - -########## -# FrankenPHP: Build -########## -FROM common as build ARG FRANKEN_PHP_VERSION='1.1.0' - -COPY --from=golang-base /usr/local/go /usr/local/go - -ENV PATH /usr/local/go/bin:$PATH - -RUN docker-php-serversideup-dep-install-debian \ - libargon2-dev \ - libbrotli-dev \ - libcurl4-openssl-dev \ - libonig-dev \ - libreadline-dev \ - libsodium-dev \ - libsqlite3-dev \ - libssl-dev \ - libxml2-dev \ - zlib1g-dev \ - && \ - curl -L https://github.com/dunglas/frankenphp/archive/refs/tags/v${FRANKEN_PHP_VERSION}.tar.gz | tar -xz -C /tmp/ && \ - cd /tmp/frankenphp-${FRANKEN_PHP_VERSION}/caddy/frankenphp && \ - CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -o /usr/local/bin/frankenphp +ARG PHP_VARIATION='frankenphp' +ARG BASE_IMAGE="dunglas/frankenphp:${FRANKEN_PHP_VERSION}-php${PHP_VERSION}-${BASE_OS_VERSION}" ########## -# FrankenPHP: Main Image +# CLI: Main Image ########## -FROM common +FROM ${BASE_IMAGE} -COPY --from=build /usr/local/bin/frankenphp /usr/local/bin/frankenphp +ARG DEPENDENCY_PACKAGES_ALPINE='shadow' +ARG DEPENDENCY_PACKAGES_DEBIAN='zip' +ARG DEPENDENCY_PHP_EXTENSIONS='pcntl pdo_mysql pdo_pgsql redis zip' LABEL org.opencontainers.image.title="serversideup/php (${PHP_VARIATION})" \ org.opencontainers.image.description="Supercharge your PHP experience. Based off the offical PHP images, serversideup/php includes pre-configured PHP extensions and settings for enhanced performance and security. Optimized for Laravel and WordPress." \ @@ -60,7 +23,7 @@ LABEL org.opencontainers.image.title="serversideup/php (${PHP_VARIATION})" \ org.opencontainers.image.version="${REPOSITORY_BUILD_VERSION}" \ org.opencontainers.image.licenses="GPL-3.0-or-later" -ENV APP_BASE_DIR=/var/www/html \ +ENV APP_BASE_DIR=/app \ COMPOSER_ALLOW_SUPERUSER=1 \ COMPOSER_HOME=/composer \ COMPOSER_MAX_PARALLEL_HTTP=24 \ @@ -79,6 +42,9 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" +# copy our scripts +COPY --chmod=755 src/common/ / + # install pecl extensions & dependencies RUN docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE}" && \ docker-php-serversideup-dep-install-debian "${DEPENDENCY_PACKAGES_DEBIAN}" && \ @@ -96,4 +62,4 @@ COPY --from=composer:2 /usr/bin/composer /usr/bin/composer WORKDIR ${APP_BASE_DIR} -ENTRYPOINT ["docker-php-serversideup-entrypoint"] \ No newline at end of file +# ENTRYPOINT ["docker-php-serversideup-entrypoint"] \ No newline at end of file From 9a64385ca8ed83eca64235353bb59f730841d21e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 9 Feb 2024 16:31:03 -0600 Subject: [PATCH 008/304] Working entrypoint --- src/variations/frankenphp/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 2bf3418e6..352fe6ea7 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -62,4 +62,6 @@ COPY --from=composer:2 /usr/bin/composer /usr/bin/composer WORKDIR ${APP_BASE_DIR} -# ENTRYPOINT ["docker-php-serversideup-entrypoint"] \ No newline at end of file +ENTRYPOINT ["docker-php-serversideup-entrypoint"] + +CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] \ No newline at end of file From 5e5581103ab0dc0f7ea15f7290d1d620d1c587b5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 3 Apr 2024 12:13:39 -0500 Subject: [PATCH 009/304] Added OPcache settings --- src/variations/frankenphp/Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 352fe6ea7..ad299856e 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -37,6 +37,11 @@ ENV APP_BASE_DIR=/app \ PHP_MAX_EXECUTION_TIME="99" \ PHP_MAX_INPUT_TIME="-1" \ PHP_MEMORY_LIMIT="256M" \ + PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ + PHP_OPCACHE_MAX_ACCELERATED_FILES="10000" \ + PHP_OPCACHE_MEMORY_CONSUMPTION="128" \ + PHP_OPCACHE_REVALIDATE_FREQ="2" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_SESSION_COOKIE_SECURE=false \ From 1efb5831106dc43218ecb2675a2f7f792d61c63c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 13 May 2024 09:20:15 -0500 Subject: [PATCH 010/304] chore: Add SHOW_WELCOME_MESSAGE flag to Dockerfile --- src/variations/frankenphp/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index ad299856e..7c613f9bd 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -45,7 +45,8 @@ ENV APP_BASE_DIR=/app \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_SESSION_COOKIE_SECURE=false \ - PHP_UPLOAD_MAX_FILE_SIZE="100M" + PHP_UPLOAD_MAX_FILE_SIZE="100M" \ + SHOW_WELCOME_MESSAGE=true # copy our scripts COPY --chmod=755 src/common/ / From 09ba2c31ee53031468f743e08921b371f9835774 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 13 May 2024 09:20:30 -0500 Subject: [PATCH 011/304] chore: Add opcache to DEPENDENCY_PHP_EXTENSIONS in Dockerfile --- src/variations/frankenphp/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 7c613f9bd..5d34e53e2 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -11,7 +11,7 @@ FROM ${BASE_IMAGE} ARG DEPENDENCY_PACKAGES_ALPINE='shadow' ARG DEPENDENCY_PACKAGES_DEBIAN='zip' -ARG DEPENDENCY_PHP_EXTENSIONS='pcntl pdo_mysql pdo_pgsql redis zip' +ARG DEPENDENCY_PHP_EXTENSIONS='opcache pcntl pdo_mysql pdo_pgsql redis zip' LABEL org.opencontainers.image.title="serversideup/php (${PHP_VARIATION})" \ org.opencontainers.image.description="Supercharge your PHP experience. Based off the offical PHP images, serversideup/php includes pre-configured PHP extensions and settings for enhanced performance and security. Optimized for Laravel and WordPress." \ From 5e2dcf202eaa0159644fa88dbce0642982458a15 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 13 May 2024 09:20:49 -0500 Subject: [PATCH 012/304] chore: Update FRANKEN_PHP_VERSION to 1.1.4 in Dockerfile --- src/variations/frankenphp/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 5d34e53e2..eca03f786 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -1,6 +1,6 @@ ARG BASE_OS_VERSION='bookworm' ARG PHP_VERSION='8.3' -ARG FRANKEN_PHP_VERSION='1.1.0' +ARG FRANKEN_PHP_VERSION='1.1.4' ARG PHP_VARIATION='frankenphp' ARG BASE_IMAGE="dunglas/frankenphp:${FRANKEN_PHP_VERSION}-php${PHP_VERSION}-${BASE_OS_VERSION}" From d1bcbc65b14ccebadd46dccfe3b11c8deabd94a3 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 13 May 2024 12:34:08 -0500 Subject: [PATCH 013/304] Moved matrix to dedicated script --- .github/workflows/service_docker-build-and-publish.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 9fb8f4c99..41fe94e2e 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -52,9 +52,8 @@ jobs: - name: Assemble PHP versions into the matrix. 😎 id: get-php-versions run: | - MATRIX_JSON=$(yq -o=json scripts/conf/php-versions.yml | jq -c '{include: [(.php_variations[] | {name, supported_os: (.supported_os // ["alpine", "bullseye", "bookworm"])} ) as $variation | .php_versions[] | .minor_versions[] | .patch_versions[] as $patch | .base_os[] as $os | select($variation.supported_os | if length == 0 then . else . | index($os.name) end) | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name}]} | {include: (.include | sort_by(.patch_version | split(".") | map(tonumber) | . as $nums | ($nums[0]*10000 + $nums[1]*100 + $nums[2])) | reverse)}') - echo "php-version-map-json=${MATRIX_JSON}" >> $GITHUB_OUTPUT - echo "${MATRIX_JSON}" | jq '.' + chmod +x ./scripts/generate-matrix.sh + MATRIX_JSON=$(./scripts/generate-matrix.sh) - name: Upload the php-versions.yml file uses: actions/upload-artifact@v4 From df97ef1e26085282375e5efb15e39d20b9c5b947 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 13 May 2024 16:14:32 -0500 Subject: [PATCH 014/304] Add sorting --- scripts/generate-matrix.sh | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/scripts/generate-matrix.sh b/scripts/generate-matrix.sh index 85a290006..9111dbbde 100755 --- a/scripts/generate-matrix.sh +++ b/scripts/generate-matrix.sh @@ -16,18 +16,19 @@ PHP_VERSIONS_FILE="${PHP_VERSIONS_FILE:-"scripts/conf/php-versions.yml"}" # Generate and output the MATRIX_JSON yq -o=json "$PHP_VERSIONS_FILE" | - jq -c '{ - include: [ - (.php_variations[] | - {name, supported_os: (.supported_os // ["alpine", "bullseye", "bookworm"]), excluded_minor_versions: (.excluded_minor_versions // [])} - ) as $variation | - .php_versions[] | - .minor_versions[] | - # Check if the minor version is not in the excluded list for the variation - select([.minor] | inside($variation.excluded_minor_versions | map(.)) | not) | - .patch_versions[] as $patch | - .base_os[] as $os | - select($variation.supported_os | if length == 0 then . else . | index($os.name) end) | - {patch_version: $patch, base_os: $os.name, php_variation: $variation.name} - ] - }' \ No newline at end of file +jq -c '{ + include: [ + (.php_variations[] | + {name, supported_os: (.supported_os // ["alpine", "bullseye", "bookworm"]), excluded_minor_versions: (.excluded_minor_versions // [])} + ) as $variation | + .php_versions[] | + .minor_versions[] | + # Check if the minor version is not in the excluded list for the variation + select([.minor] | inside($variation.excluded_minor_versions | map(.)) | not) | + .patch_versions[] as $patch | + .base_os[] as $os | + select($variation.supported_os | if length == 0 then . else . | index($os.name) end) | + {patch_version: $patch, base_os: $os.name, php_variation: $variation.name} + ] +} | +{include: (.include | sort_by(.patch_version | split(".") | map(tonumber) | . as $nums | ($nums[0]*10000 + $nums[1]*100 + $nums[2])) | reverse)}' From 2bb76cea57d80803841b49fcf8c36450de17538e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 31 Jul 2024 11:11:52 -0500 Subject: [PATCH 015/304] chore: Update FRANKEN_PHP_VERSION to 1.2.2 in Dockerfile --- src/variations/frankenphp/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index eca03f786..98dddedcc 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -1,6 +1,6 @@ ARG BASE_OS_VERSION='bookworm' ARG PHP_VERSION='8.3' -ARG FRANKEN_PHP_VERSION='1.1.4' +ARG FRANKEN_PHP_VERSION='1.2.2' ARG PHP_VARIATION='frankenphp' ARG BASE_IMAGE="dunglas/frankenphp:${FRANKEN_PHP_VERSION}-php${PHP_VERSION}-${BASE_OS_VERSION}" From 0ab9bd3267ac9d5145eadfa71a9ab8ca0efccd62 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 31 Jul 2024 11:21:24 -0500 Subject: [PATCH 016/304] Update `APP_BASE_DIR` default value to "/var/www/html" and add a new option for FrankenPHP with value "/app" --- .../docs/7.reference/1.environment-variable-specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index fc42c7e1d..d9990ac32 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -19,7 +19,7 @@ We like to customize our images on a per app basis using environment variables. `APACHE_START_SERVERS`
*Default: "2"*|Sets the number of child server processes created on startup.(Official docs)|fpm-apache `APACHE_THREAD_LIMIT`
*Default: "64"*|Set the maximum configured value for ThreadsPerChild for the lifetime of the Apache httpd process. (Official docs)|fpm-apache `APACHE_THREADS_PER_CHILD`
*Default: "25"*|This directive sets the number of threads created by each child process. (Official docs)|fpm-apache -`APP_BASE_DIR`
*Default: "/var/www/html"*|Change this only if you mount your application to a different directory within the container. ℹ️ Be sure to change `NGINX_WEBROOT`, `APACHE_DOCUMENT_ROOT`, `UNIT_WEBROOT`, etc if it applies to your use case as well.|all +`APP_BASE_DIR`
*Default: "/var/www/html"*
*FrankenPHP: "/app"*|Change this only if you mount your application to a different directory within the container. ℹ️ Be sure to change `NGINX_WEBROOT`, `APACHE_DOCUMENT_ROOT`, `UNIT_WEBROOT`, etc if it applies to your use case as well.|all `AUTORUN_ENABLED`
*Default: "false"*|Enable or disable all automations. It's advised to set this to `false` in certain CI environments (especially during a composer install). If this is set to `false`, all `AUTORUN_*` behaviors will also be disabled.| all `AUTORUN_LARAVEL_CONFIG_CACHE`
*Default: "true"*|Automatically run "php artisan config:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_EVENT_CACHE`
*Default: "true"*|Automatically run "php artisan event:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all @@ -33,7 +33,7 @@ We like to customize our images on a per app basis using environment variables. `COMPOSER_HOME`
*Default: "/composer"*|The COMPOSER_HOME variable allows you to change the Composer home directory. This is a hidden, global (per-user on the machine) directory that is shared between all projects.|all `COMPOSER_MAX_PARALLEL_HTTP`
*Default: "24"*|Set to an integer to configure how many files can be downloaded in parallel. Composer ships with 12 by default and must be between 1 and 50. If your proxy has issues with concurrency maybe you want to lower this. Increasing it should generally not result in performance gains.|all `DISABLE_DEFAULT_CONFIG`
*Default: "false"*|Get full customization of the image and disable all default configurations and automations.| all -`HEALTHCHECK_PATH`
*Default: "/healthcheck"*|Set the path for the health check endpoint. (Official docs)|all (except `cli`) +`HEALTHCHECK_PATH`
*Default: "/healthcheck"*|Set the path for the health check endpoint. (Official docs)|all (except `cli` and `frankenphp`) `LOG_OUTPUT_LEVEL`
*Default: "warn"*|Set your container output different verbosity levels: debug, info, off |all `NGINX_FASTCGI_BUFFERS`
*Default: "8 8k"*|Sets the number and size of the buffers used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx `NGINX_FASTCGI_BUFFER_SIZE`
*Default: "8k"*|Sets the size of the buffer used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx From 775f319ab0e87cb80bab07729573a02c6f842fe5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 5 Mar 2025 14:43:59 -0600 Subject: [PATCH 017/304] Bump PHP Extension Installer to version 2.7.27 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index 5685cd921..6f071246a 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.7.0" +PHP_EXT_INSTALLER_VERSION="2.7.27" ############ # Main From 39ee9b8f608a6da727ae56f5cc34ae4ea698dc2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=BCske?= Date: Wed, 5 Mar 2025 22:16:44 +0100 Subject: [PATCH 018/304] Add PHP_FPM_PM_MAX_REQUESTS as environment variable (#513) Co-authored-by: Jay Rogers --- .../docs/7.reference/1.environment-variable-specification.md | 1 + .../usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf | 4 ++++ src/variations/fpm-apache/Dockerfile | 1 + src/variations/fpm-nginx/Dockerfile | 1 + src/variations/fpm/Dockerfile | 1 + 5 files changed, 8 insertions(+) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 597464177..510ad729d 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -49,6 +49,7 @@ We like to customize our images on a per app basis using environment variables. `PHP_FPM_PM_MAX_SPARE_SERVERS`
*Default: "3"*|The desired maximum number of idle server processes. Used only when pm is set to dynamic. (Official docs)|fpm* `PHP_FPM_PM_MIN_SPARE_SERVERS`
*Default: "1"*|The desired minimum number of idle server processes. Used only when pm is set to dynamic. (Official docs)|fpm* `PHP_FPM_PM_START_SERVERS`
*Default: "2"*|The number of child processes created on startup. Used only when pm is set to dynamic. (Official docs)|fpm* +`PHP_FPM_PM_MAX_REQUESTS`
*Default: "0"*|The number of requests each child process should execute before respawning. This can be useful to work around memory leaks in 3rd party libraries. (Official docs)|fpm* `PHP_FPM_POOL_NAME`
*Default: "www"*|Set the name of your PHP-FPM pool (helpful when running multiple sites on a single server).|fpm* `PHP_FPM_PROCESS_CONTROL_TIMEOUT`
*Default: "10s"*|Set the timeout for the process control commands. (Official docs)|fpm* `PHP_MAX_EXECUTION_TIME`
*Default: "99"*|Set the maximum time in seconds a script is allowed to run before it is terminated by the parser. (Official docs)|all diff --git a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf index e8d3e0cff..10e74a3e2 100644 --- a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf +++ b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf @@ -141,6 +141,10 @@ pm.min_spare_servers = ${PHP_FPM_PM_MIN_SPARE_SERVERS} ; Note: Mandatory when pm is set to 'dynamic' pm.max_spare_servers = ${PHP_FPM_PM_MAX_SPARE_SERVERS} +; The number of requests each child process should execute before respawning. +; This can be useful to work around memory leaks in 3rd party libraries. For +; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. +pm.max_requests = ${PHP_FPM_PM_MAX_REQUESTS} ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Healthcheck settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index e6c4cdcf3..5a07f5c35 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -63,6 +63,7 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ + PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index a8197b6df..034b008d7 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -87,6 +87,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ + PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index e97261604..f6771af72 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -38,6 +38,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ + PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ From dd8698d8b1a11f807779f924faef262a6cabbf06 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 5 Mar 2025 15:21:38 -0600 Subject: [PATCH 019/304] Alphabatized variables --- .../docs/7.reference/1.environment-variable-specification.md | 2 +- .../usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf | 1 + src/variations/fpm-apache/Dockerfile | 2 +- src/variations/fpm-nginx/Dockerfile | 2 +- src/variations/fpm/Dockerfile | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 510ad729d..0e2144190 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -46,10 +46,10 @@ We like to customize our images on a per app basis using environment variables. `PHP_ERROR_REPORTING`
*Default: "22527"*|Set PHP error reporting level. Must be a number. Use this tool for help. (Official docs)|all `PHP_FPM_PM_CONTROL`
*Defaults:
fpm: dynamic
fpm-apache: ondemand
fpm-nginx: ondemand*|Choose how the process manager will control the number of child processes. (Official docs)|fpm* `PHP_FPM_PM_MAX_CHILDREN`
*Default: "20"*|The number of child processes to be created when pm is set to static and the maximum number of child processes to be created when pm is set to dynamic. (Official docs)|fpm* +`PHP_FPM_PM_MAX_REQUESTS`
*Default: "0"*|The number of requests each child process should execute before respawning. This can be useful to work around memory leaks in 3rd party libraries. (Official docs)|fpm* `PHP_FPM_PM_MAX_SPARE_SERVERS`
*Default: "3"*|The desired maximum number of idle server processes. Used only when pm is set to dynamic. (Official docs)|fpm* `PHP_FPM_PM_MIN_SPARE_SERVERS`
*Default: "1"*|The desired minimum number of idle server processes. Used only when pm is set to dynamic. (Official docs)|fpm* `PHP_FPM_PM_START_SERVERS`
*Default: "2"*|The number of child processes created on startup. Used only when pm is set to dynamic. (Official docs)|fpm* -`PHP_FPM_PM_MAX_REQUESTS`
*Default: "0"*|The number of requests each child process should execute before respawning. This can be useful to work around memory leaks in 3rd party libraries. (Official docs)|fpm* `PHP_FPM_POOL_NAME`
*Default: "www"*|Set the name of your PHP-FPM pool (helpful when running multiple sites on a single server).|fpm* `PHP_FPM_PROCESS_CONTROL_TIMEOUT`
*Default: "10s"*|Set the timeout for the process control commands. (Official docs)|fpm* `PHP_MAX_EXECUTION_TIME`
*Default: "99"*|Set the maximum time in seconds a script is allowed to run before it is terminated by the parser. (Official docs)|all diff --git a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf index 10e74a3e2..500808839 100644 --- a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf +++ b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf @@ -145,6 +145,7 @@ pm.max_spare_servers = ${PHP_FPM_PM_MAX_SPARE_SERVERS} ; This can be useful to work around memory leaks in 3rd party libraries. For ; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. pm.max_requests = ${PHP_FPM_PM_MAX_REQUESTS} + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Healthcheck settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 5a07f5c35..8d1a31bd0 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -60,10 +60,10 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_ERROR_REPORTING="22527" \ PHP_FPM_PM_CONTROL=dynamic \ PHP_FPM_PM_MAX_CHILDREN="20" \ + PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ - PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index 034b008d7..5ce225211 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -84,10 +84,10 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_ERROR_REPORTING="22527" \ PHP_FPM_PM_CONTROL=dynamic \ PHP_FPM_PM_MAX_CHILDREN="20" \ + PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ - PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index f6771af72..d54352ff9 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -35,10 +35,10 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_ERROR_REPORTING="22527" \ PHP_FPM_PM_CONTROL=dynamic \ PHP_FPM_PM_MAX_CHILDREN="20" \ + PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ - PHP_FPM_PM_MAX_REQUESTS="0" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ From 4612d3da3c698bad9d46c8df0bac0e3a39aada6e Mon Sep 17 00:00:00 2001 From: RadeJR <33696623+RadeJR@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:29:32 +0100 Subject: [PATCH 020/304] Add environment variable: PHP_MAX_INPUT_VARS (#500) * Added configurability for max_input_vars and default value * A-z variables --------- Co-authored-by: Ilija Radojkovic Co-authored-by: Jay Rogers --- .../docs/7.reference/1.environment-variable-specification.md | 3 ++- .../usr/local/etc/php/conf.d/serversideup-docker-php.ini | 4 ++-- src/variations/cli/Dockerfile | 1 + src/variations/fpm-apache/Dockerfile | 1 + src/variations/fpm-nginx/Dockerfile | 1 + src/variations/fpm/Dockerfile | 1 + src/variations/unit/Dockerfile | 1 + 7 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 510ad729d..02b936331 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -54,6 +54,7 @@ We like to customize our images on a per app basis using environment variables. `PHP_FPM_PROCESS_CONTROL_TIMEOUT`
*Default: "10s"*|Set the timeout for the process control commands. (Official docs)|fpm* `PHP_MAX_EXECUTION_TIME`
*Default: "99"*|Set the maximum time in seconds a script is allowed to run before it is terminated by the parser. (Official docs)|all `PHP_MAX_INPUT_TIME`
*Default: "-1"*|This sets the maximum time in seconds a script is allowed to parse input data, like POST and GET. Timing begins at the moment PHP is invoked at the server and ends when execution begins. The default setting is -1, which means that max_execution_time is used instead. Set to 0 to allow unlimited time. This directive is hardcoded to -1 for the CLI SAPI by PHP. (Official docs)|all +`PHP_MAX_INPUT_VARS`
*Default: "1000"*|Set the limits for number of input variables (e.g., POST, GET, or COOKIE variables) that PHP will process in a single request. (Official docs)|all `PHP_MEMORY_LIMIT`
*Default: "256M"*|Set the maximum amount of memory in bytes that a script is allowed to allocate. (Official docs)|all `PHP_OPCACHE_ENABLE`
*Default: "0" (to keep developers sane)*|Enable or disable OPcache. ⚠️ This will set **both values** for `opcache.enable` and `opcache.enable_cli`. (Official docs)|all `PHP_OPCACHE_INTERNED_STRINGS_BUFFER`
*Default: "8"*|The amount of memory used to store interned strings, in megabytes. (Official docs)|all @@ -78,4 +79,4 @@ We like to customize our images on a per app basis using environment variables. `UNIT_PROCESSES_MAX`
*Default: "20"*|The maximum number of application processes that can be started. (Official Docs)| unit `UNIT_PROCESSES_SPARE`
*Default: "2"*|Minimum number of idle processes that Unit tries to maintain for an app. (Official Docs)| unit `UNIT_WEBROOT`
*Default: "/var/www/html/public"*|Base directory of the app’s file structure. All URI paths are relative to it. (Official Docs)| unit -`UNIT_MAX_BODY_SIZE`
*Default: "104857600"* (100MB) | Sets maximum number of bytes in the body of a client’s request. (Official docs) | unit \ No newline at end of file +`UNIT_MAX_BODY_SIZE`
*Default: "104857600"* (100MB) | Sets maximum number of bytes in the body of a client’s request. (Official docs) | unit diff --git a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini index 1a99a587b..a5da09e1e 100644 --- a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini +++ b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini @@ -423,7 +423,7 @@ max_input_time = ${PHP_MAX_INPUT_TIME} ;max_input_nesting_level = 64 ; How many GET/POST/COOKIE input variables may be accepted -;max_input_vars = 1000 +max_input_vars = ${PHP_MAX_INPUT_VARS} ; How many multipart body parts (combined input variable and file uploads) may ; be accepted. @@ -1972,4 +1972,4 @@ opcache.revalidate_freq=${PHP_OPCACHE_REVALIDATE_FREQ} ;ffi.enable=preload ; List of headers files to preload, wildcard patterns allowed. -;ffi.preload= \ No newline at end of file +;ffi.preload= diff --git a/src/variations/cli/Dockerfile b/src/variations/cli/Dockerfile index 3896b3fde..63a926b3a 100644 --- a/src/variations/cli/Dockerfile +++ b/src/variations/cli/Dockerfile @@ -35,6 +35,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_ERROR_REPORTING="22527" \ PHP_MAX_EXECUTION_TIME="99" \ PHP_MAX_INPUT_TIME="-1" \ + PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 5a07f5c35..13750f2c7 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -68,6 +68,7 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ PHP_MAX_INPUT_TIME="-1" \ + PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index 034b008d7..e38a9cf63 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -92,6 +92,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ PHP_MAX_INPUT_TIME="-1" \ + PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index f6771af72..9bb89a734 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -43,6 +43,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ PHP_MAX_INPUT_TIME="-1" \ + PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ diff --git a/src/variations/unit/Dockerfile b/src/variations/unit/Dockerfile index 88aa98f08..d1e2723d4 100644 --- a/src/variations/unit/Dockerfile +++ b/src/variations/unit/Dockerfile @@ -91,6 +91,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_ERROR_REPORTING="22527" \ PHP_MAX_EXECUTION_TIME="99" \ PHP_MAX_INPUT_TIME="-1" \ + PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ From 7625ec643921f327a384eebdf0ca0a8c73d9ab82 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 5 Mar 2025 16:42:32 -0600 Subject: [PATCH 021/304] Refactored "set-file-permissions" and "set-id" scripts. Fixes #502 --- ...cker-php-serversideup-set-file-permissions | 99 ++++++++++++++++--- .../local/bin/docker-php-serversideup-set-id | 6 ++ 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 4139a9f9e..63afb9cde 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -1,5 +1,5 @@ #!/bin/sh -set -e +set -eu ################################################### # Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP --service SERVICE @@ -44,6 +44,12 @@ if [ -z "$OWNER" ] || [ -z "$SERVICE" ]; then usage fi +# Check for root privileges +if [ "$(id -u)" -ne 0 ]; then + echo "${script_name}: This script must be run as root within the container. Be sure to set \"USER root\" in your Dockerfile before running this script." + exit 1 +fi + # Detect the operating system using /etc/os-release if [ -f "/etc/os-release" ]; then . /etc/os-release @@ -70,19 +76,54 @@ case "$OS" in debian) case "$SERVICE" in cli) - DIRS="/var/www/ $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; fpm) - DIRS="/var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; apache) - DIRS="/run /etc/apache2 /etc/ssl/private /var/log/apache2 /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /run + /var/www + /var/log/apache2 + /etc/apache2 + /etc/ssl/private + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; nginx) - DIRS="/run /etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx/ /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /run + /var/www + /var/log/nginx + /var/cache/nginx + /etc/nginx + /etc/ssl/private + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; unit) - DIRS="/var/log/unit /var/run/unit /etc/unit /etc/ssl/private/ /var/lib/unit/ /var/www/ $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + /var/log/unit + /var/run/unit + /var/lib/unit + /etc/unit + /etc/ssl/private + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) echo "$script_name: Unsupported service: $SERVICE" @@ -93,16 +134,40 @@ case "$OS" in alpine) case "$SERVICE" in cli) - DIRS="/var/www/ $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; fpm) - DIRS="/var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; apache) - DIRS="/etc/apache2 /etc/ssl/private /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + /etc/apache2 + /etc/ssl/private + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; nginx) - DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/lib/nginx /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + /etc/nginx + /var/log/nginx + /var/lib/nginx + /etc/ssl/private + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) echo "$script_name: Unsupported SERVICE: $SERVICE" @@ -117,4 +182,16 @@ case "$OS" in esac # Change ownership of the directories -change_ownership $DIRS \ No newline at end of file +# Split DIRS into an array and properly quote each path +for dir in ${DIRS}; do + if [ -e "${dir}" ]; then + chown -R "${OWNER}" "${dir}" || { + echo "${script_name}: Failed to change ownership of ${dir}" + exit 1 + } + echo "${script_name}: Ownership of ${dir} changed to ${OWNER}." + else + echo "${script_name}: Directory not found: ${dir}" + exit 1 + fi +done \ No newline at end of file diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-id b/src/common/usr/local/bin/docker-php-serversideup-set-id index 908507d7c..3da78d266 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-id +++ b/src/common/usr/local/bin/docker-php-serversideup-set-id @@ -17,6 +17,12 @@ if [ "$#" -ne 2 ]; then exit 1 fi +# Check for root privileges +if [ "$(id -u)" -ne 0 ]; then + echo "${script_name}: This script must be run as root within the container. Be sure to set \"USER root\" in your Dockerfile before running this script." + exit 1 +fi + username="$1" uid_gid="$2" From 792f1cb2904ed8edb96e003aa87a94ddd2793a47 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 6 Mar 2025 10:12:20 -0600 Subject: [PATCH 022/304] Added cursor rules --- .cursor/rules | 11 ++++ .../local/bin/docker-php-serversideup-s6-init | 51 +++++++++++++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 .cursor/rules diff --git a/.cursor/rules b/.cursor/rules new file mode 100644 index 000000000..7a824e847 --- /dev/null +++ b/.cursor/rules @@ -0,0 +1,11 @@ +You are a highly skilled PHP system administrator tasked with maintaining open source PHP Docker images for Laravel applications. You possess deep knowledge of best practices of Docker, PHP, Laravel, GitHub Actions, and shell scripting.Your goal is to assist in creating production-ready Docker images that follow best practices for security, performance, and developer experience using the guidelines below. + +1. Development Guidelines: + + - Follow the best practices for security, performance, and developer experience. + - Write clean, maintainable and technically accurate code. + - All entrypoint scripts for the Docker images must be POSIX compliant and able to be executed with /bin/sh. + - Bash scripts must be compatible with Linux, WSL2, and MacOS (Bash v3). + - Never use an approach you're not confident about. If you're unsure about something, ask for clarity. + +This project is open source and the code is available on GitHub, so be sure to follow best practices to make it easy for others to understand, modify, and contribute to the project. \ No newline at end of file diff --git a/src/s6/usr/local/bin/docker-php-serversideup-s6-init b/src/s6/usr/local/bin/docker-php-serversideup-s6-init index a837f4a7f..e5628884a 100644 --- a/src/s6/usr/local/bin/docker-php-serversideup-s6-init +++ b/src/s6/usr/local/bin/docker-php-serversideup-s6-init @@ -7,6 +7,51 @@ set -e # This script is used to take scripts from "/etc/entrypoint.d" and move them # to the S6 Overlay structure. -echo "⚠️ CAUTION: This script is deprecated and will be removed in a future release. Please remove any references to this script from your Dockerfile." -echo "For the latest information on how to add your own start up scripts, please refer to the documentation:" -echo "https://serversideup.net/open-source/docker-php/docs/customizing-the-image/adding-your-own-start-up-scripts" +S6_HOME="/etc/s6-overlay" + +for file in /etc/entrypoint.d/*.sh; do + [ -e "$file" ] || continue # Skip if no files match + + # Get the base name of the file + base=$(basename "$file" .sh) + + # Proceed only if the script does not exist + if [ ! -e "${S6_HOME}/scripts/${base}" ]; then + # Create the service directory for that file + mkdir -p "${S6_HOME}/s6-rc.d/${base}" + + # Set service type to "oneshot" + echo "oneshot" > "${S6_HOME}/s6-rc.d/${base}/type" + + # Set the "up" script + echo "${S6_HOME}/scripts/${base}" > "${S6_HOME}/s6-rc.d/${base}/up" + + # Place empty file in contents.d + touch "${S6_HOME}/s6-rc.d/user/contents.d/${base}" + + # Ensure the ${S6_HOME}/scripts/ directory exists + mkdir -p "${S6_HOME}/scripts" + + # Link the script in the script directory + ln -s "${file}" "${S6_HOME}/scripts/${base}" + + # Ensure the script has the correct file header for S6 + sed -i '1s%^#!/bin/sh$%#!/command/with-contenv sh%' "${file}" + + # Find the script that should be the dependency based on alphabetical order + previous_base=$(find "${S6_HOME}/s6-rc.d/" -maxdepth 1 -type d -name '[0-9]*' | \ + sort | \ + grep -B1 "${base}" | \ + head -n 1 | \ + xargs basename) + + # Check if the previous script is not the current script and set as dependency + if [ "$previous_base" != "$base" ] && [ -n "$previous_base" ]; then + echo "$previous_base" >> "${S6_HOME}/s6-rc.d/${base}/dependencies" + fi + + # Set the previous file for the next loop + previous_base="$base" + fi + +done \ No newline at end of file From 6ff7b0cc2c4363c2811ce448411ddb9741696dc7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 6 Mar 2025 10:14:44 -0600 Subject: [PATCH 023/304] Updated rules --- .cursor/rules | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.cursor/rules b/.cursor/rules index 7a824e847..6b0da61c6 100644 --- a/.cursor/rules +++ b/.cursor/rules @@ -1,6 +1,16 @@ -You are a highly skilled PHP system administrator tasked with maintaining open source PHP Docker images for Laravel applications. You possess deep knowledge of best practices of Docker, PHP, Laravel, GitHub Actions, and shell scripting.Your goal is to assist in creating production-ready Docker images that follow best practices for security, performance, and developer experience using the guidelines below. +You are a highly skilled PHP system administrator tasked with maintaining open source PHP Docker images for Laravel applications. Your goal is to assist in creating production-ready Docker images that follow best practices for security, performance, and developer experience using the guidelines below. -1. Development Guidelines: +1. Skills you posses deep knowledge and best practices of: + - Docker + - PHP + - Laravel + - GitHub Actions + - Shell scripting + - S6 Overlay + - Nginx + - Apache + +2. Development Guidelines: - Follow the best practices for security, performance, and developer experience. - Write clean, maintainable and technically accurate code. From 47634511f1aecbd50dfad55399ffcbb05d4bc3a8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 6 Mar 2025 10:15:16 -0600 Subject: [PATCH 024/304] Add NGINX Unit and PHP-FPM to system administrator skills --- .cursor/rules | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.cursor/rules b/.cursor/rules index 6b0da61c6..a54d85222 100644 --- a/.cursor/rules +++ b/.cursor/rules @@ -8,7 +8,9 @@ You are a highly skilled PHP system administrator tasked with maintaining open s - Shell scripting - S6 Overlay - Nginx - - Apache + - Apache + - NGINX Unit + - PHP-FPM 2. Development Guidelines: From 09e5a2f96c51375dd26b05d86b5ae0103ff9a003 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 6 Mar 2025 11:21:25 -0600 Subject: [PATCH 025/304] Enhance S6 Overlay initialization script with robust error handling and improved script management. References #479 --- .../3.adding-your-own-start-up-scripts.md | 47 ++++++++++++++-- .../docs/7.reference/2.command-reference.md | 5 ++ .../local/bin/docker-php-serversideup-s6-init | 55 +++++++++++++------ 3 files changed, 85 insertions(+), 22 deletions(-) diff --git a/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md b/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md index b57c73ac2..2314e936d 100644 --- a/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md +++ b/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md @@ -37,6 +37,11 @@ Anything in the `/etc/entrypoint.d` directory are scripts that are intended to r Instead, learn about [using S6 overlay](/docs/guide/using-s6-overlay) so your services can be properly initialized and monitored. See the [S6 Overylay project](https://github.com/just-containers/s6-overlay) for more details on how to write your own S6 service. +## Don't use `exit 0` in your script +If you use `exit 0` in your script, it will stop the execution of the rest of the entrypoint scripts. We recommend using `return 0` instead. [See this discussion](https://github.com/serversideup/docker-php/issues/481#issuecomment-2463082306) for more details on why. + +Long story short, we don't use subshells to execute your scripts, so `exit 0` will not work as expected. We do this because we want to ensure your script has access to the environment variables that are set in the entrypoint scripts. + ## Example: Create a custom entrypoint script In this example, let's create a `99-my-script.sh` so it executes after all the other default scripts. @@ -104,11 +109,6 @@ services: In the above file, we're building our image using the `Dockerfile` in the current directory. We're also mounting our current directory to `/var/www/html` in the container. -## Don't use `exit 0` in your script -If you use `exit 0` in your script, it will stop the execution of the rest of the entrypoint scripts. We recommend using `return 0` instead. [See this discussion](https://github.com/serversideup/docker-php/issues/481#issuecomment-2463082306) for more details on why. - -Long story short, we don't use subshells to execute your scripts, so `exit 0` will not work as expected. We do this because we want to ensure your script has access to the environment variables that are set in the entrypoint scripts. - ## Running our example When we run `docker compose up`, we should see the following output: @@ -135,4 +135,39 @@ example-project | 2023/12/05 19:52:38 [info] 67#67 OpenSSL 3.0.11 19 Sep 2023, ``` :: -You can see our `👋 Hello, world!` is executing *after* the initialization of `10-init-unit.sh`. \ No newline at end of file +You can see our `👋 Hello, world!` is executing *after* the initialization of `10-init-unit.sh`. + +## Advanced Scenarios: S6 Overlay dependencies +If you want to customize an image that uses S6 Overlay (`fpm-nginx` or `fpm-apache`), you may have an advanced scenario where you have a custom S6 service that needs to be executed after one of our entrypoint scripts. In order to do this, you'll need to move all our scripts from the `/etc/entrypoint.d` directory to the `/etc/s6-overlay/scripts` directory. This would be a very time consuming scenario if you did this manually, but thankfully you can use our `docker-php-serversideup-s6-init` script to do this for you. + +::code-panel +--- +label: "Example Dockerfile" +--- +```dockerfile +FROM serversideup/php:8.4-fpm-nginx + +# Set the user to root for our build steps +USER root + +# If you have your own one-shot scripts, copy them to the entrypoint.d directory +COPY --chmod=755 ./entrypoint.d/ /etc/entrypoint.d/ + +# Copy our entrypoint scripts into the S6 Overlay scripts directory +RUN docker-php-serversideup-s6-init + +# If you have your own long running services, copy them to the s6 directory +COPY --chmod=755 ./my-s6-service/ /etc/s6-overlay/s6-rc.d/my-s6-service/ + +# Drop back to the non-root user +USER www-data +``` +:: + +In the above file, we're copying our "one-shot" scripts to the `/etc/entrypoint.d` directory and our long running services to the `/etc/s6-overlay` directory. One-shot scripts are scripts that are intended to run quickly and then move on. Long running services are services that are intended to run for a long time and need to be monitored and restarted if they crash. + +The magic happens when we run `docker-php-serversideup-s6-init`. This script will move all our scripts from the `/etc/entrypoint.d` directory to the `/etc/s6-overlay/scripts` directory and set the correct dependencies for our S6 services. + +You can now reference our script names as dependencies in your own S6 service. + +[Learn more about writing your own S6 services →](https://github.com/just-containers/s6-overlay) diff --git a/docs/content/docs/7.reference/2.command-reference.md b/docs/content/docs/7.reference/2.command-reference.md index 14942b9ba..a1b4e437c 100644 --- a/docs/content/docs/7.reference/2.command-reference.md +++ b/docs/content/docs/7.reference/2.command-reference.md @@ -88,6 +88,11 @@ docker-php-serversideup-set-id www-data 1000:1000 ``` :: +## docker-php-serversideup-s6-init +This command is used to copy our entrypoint scripts into the S6 Overlay scripts directory. This is useful if you're using S6 Overlay and want to ensure your scripts are executed in the correct order. + +[Learn more about using S6 Overlay dependencies →](/docs/customizing-the-image/adding-your-own-start-up-scripts#advanced-scenarios-s6-overlay-dependencies) + ## docker-php-serversideup-s6-install This is a command used at build time to install a specific version of S6 Overlay. diff --git a/src/s6/usr/local/bin/docker-php-serversideup-s6-init b/src/s6/usr/local/bin/docker-php-serversideup-s6-init index e5628884a..d61307140 100644 --- a/src/s6/usr/local/bin/docker-php-serversideup-s6-init +++ b/src/s6/usr/local/bin/docker-php-serversideup-s6-init @@ -8,50 +8,73 @@ set -e # to the S6 Overlay structure. S6_HOME="/etc/s6-overlay" +ENTRYPOINT_DIR="/etc/entrypoint.d" -for file in /etc/entrypoint.d/*.sh; do +# Sanity checks +if [ ! -d "$ENTRYPOINT_DIR" ]; then + echo "Error: $ENTRYPOINT_DIR directory not found" + exit 1 +fi + +if [ ! -d "$S6_HOME" ]; then + echo "Error: S6_HOME directory ($S6_HOME) not found" + exit 1 +fi + +# Check for root privileges +if [ "$(id -u)" -ne 0 ]; then + echo "$(basename "$0"): This script must be run as root within the container. Be sure to set \"USER root\" in your Dockerfile before running this script." + exit 1 +fi + +for file in "$ENTRYPOINT_DIR"/*.sh; do [ -e "$file" ] || continue # Skip if no files match # Get the base name of the file - base=$(basename "$file" .sh) + script_name=$(basename "$file" .sh) # Proceed only if the script does not exist - if [ ! -e "${S6_HOME}/scripts/${base}" ]; then + if [ ! -e "${S6_HOME}/scripts/${script_name}" ]; then # Create the service directory for that file - mkdir -p "${S6_HOME}/s6-rc.d/${base}" + mkdir -p "${S6_HOME}/s6-rc.d/${script_name}" # Set service type to "oneshot" - echo "oneshot" > "${S6_HOME}/s6-rc.d/${base}/type" + echo "oneshot" > "${S6_HOME}/s6-rc.d/${script_name}/type" # Set the "up" script - echo "${S6_HOME}/scripts/${base}" > "${S6_HOME}/s6-rc.d/${base}/up" + echo "${S6_HOME}/scripts/${script_name}" > "${S6_HOME}/s6-rc.d/${script_name}/up" # Place empty file in contents.d - touch "${S6_HOME}/s6-rc.d/user/contents.d/${base}" + touch "${S6_HOME}/s6-rc.d/user/contents.d/${script_name}" # Ensure the ${S6_HOME}/scripts/ directory exists mkdir -p "${S6_HOME}/scripts" - # Link the script in the script directory - ln -s "${file}" "${S6_HOME}/scripts/${base}" + # Move the script to the S6 Overlay scripts directory + mv "${file}" "${S6_HOME}/scripts/${script_name}" # Ensure the script has the correct file header for S6 - sed -i '1s%^#!/bin/sh$%#!/command/with-contenv sh%' "${file}" + sed -i '1s%^#!/bin/sh$%#!/command/with-contenv sh%' "${S6_HOME}/scripts/${script_name}" # Find the script that should be the dependency based on alphabetical order - previous_base=$(find "${S6_HOME}/s6-rc.d/" -maxdepth 1 -type d -name '[0-9]*' | \ - sort | \ - grep -B1 "${base}" | \ + previous_script_name=$(find "${S6_HOME}/s6-rc.d/" -maxdepth 1 -type d -name '[0-9]*' | \ + sort -V | \ + grep -B1 "${script_name}" | \ head -n 1 | \ xargs basename) # Check if the previous script is not the current script and set as dependency - if [ "$previous_base" != "$base" ] && [ -n "$previous_base" ]; then - echo "$previous_base" >> "${S6_HOME}/s6-rc.d/${base}/dependencies" + if [ "$previous_script_name" != "$script_name" ] && [ -n "$previous_script_name" ]; then + dependencies_file="${S6_HOME}/s6-rc.d/${script_name}/dependencies" + touch "$dependencies_file" + echo "$previous_script_name" >> "$dependencies_file" + chmod 644 "$dependencies_file" fi # Set the previous file for the next loop - previous_base="$base" + previous_script_name="$script_name" + else + echo "Skipping ${script_name} because it already exists at ${S6_HOME}/scripts/${script_name}" fi done \ No newline at end of file From 883501f29eccf937f3a75542d39a1a5c987be493 Mon Sep 17 00:00:00 2001 From: Rob Lyons <39706150+aSeriousDeveloper@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:38:41 +0000 Subject: [PATCH 026/304] Add additional OPCache environment variables (#510) * Expose opcache.validate_timestamps to .env * Add PHP_OPCACHE_VALIDATE_TIMESTAMPS to CLI ENV * Add PHP_OPCACHE_VALIDATE_TIMESTAMPS to Apache ENV * Add PHP_OPCACHE_VALIDATE_TIMESTAMPS to Nginx Unit ENV * Add PHP_OPCACHE_VALIDATE_TIMESTAMPS to Nginx ENV * Add PHP_OPCACHE_VALIDATE_TIMESTAMPS to FPM ENV * Include PHP_OPCACHE_VALIDATE_TIMESTAMPS in docs * match capitalisation * Add PHP_OPCACHE_SAVE_COMMENTS environment variable for OPcache configuration * Add PHP_OPCACHE_FORCE_RESTART_TIMEOUT environment variable for OPcache configuration * Add PHP_OPCACHE_JIT_BUFFER_SIZE environment variable for OPcache JIT configuration * Add PHP_OPCACHE_JIT environment variable for OPcache JIT configuration --------- Co-authored-by: Jay Rogers --- .../1.environment-variable-specification.md | 5 +++++ .../local/etc/php/conf.d/serversideup-docker-php.ini | 10 ++++++++-- src/variations/cli/Dockerfile | 5 +++++ src/variations/fpm-apache/Dockerfile | 5 +++++ src/variations/fpm-nginx/Dockerfile | 5 +++++ src/variations/fpm/Dockerfile | 5 +++++ src/variations/unit/Dockerfile | 5 +++++ 7 files changed, 38 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 17e35fc3c..8a7f60792 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -57,10 +57,15 @@ We like to customize our images on a per app basis using environment variables. `PHP_MAX_INPUT_VARS`
*Default: "1000"*|Set the limits for number of input variables (e.g., POST, GET, or COOKIE variables) that PHP will process in a single request. (Official docs)|all `PHP_MEMORY_LIMIT`
*Default: "256M"*|Set the maximum amount of memory in bytes that a script is allowed to allocate. (Official docs)|all `PHP_OPCACHE_ENABLE`
*Default: "0" (to keep developers sane)*|Enable or disable OPcache. ⚠️ This will set **both values** for `opcache.enable` and `opcache.enable_cli`. (Official docs)|all +`PHP_OPCACHE_FORCE_RESTART_TIMEOUT`
*Default: "180"*|The number of seconds to wait for a scheduled restart to begin if the cache isn't active, in seconds. If the timeout is hit, then OPcache assumes that something is wrong and will kill the processes holding locks on the cache to permit a restart. (Official docs)|all `PHP_OPCACHE_INTERNED_STRINGS_BUFFER`
*Default: "8"*|The amount of memory used to store interned strings, in megabytes. (Official docs)|all +`PHP_OPCACHE_JIT`
*Default: "off"*|Enable or disable the JIT compiler. (Official docs)|all +`PHP_OPCACHE_JIT_BUFFER_SIZE`
*Default: "0"*|The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. (Official docs)|all `PHP_OPCACHE_MAX_ACCELERATED_FILES`
*Default: "10000"*|The maximum number of keys (scripts) in the OPcache hash table. (Official docs)|all `PHP_OPCACHE_MEMORY_CONSUMPTION`
*Default: "128"*|The amount of memory used by the OPcache engine, in megabytes. (Official docs)|all `PHP_OPCACHE_REVALIDATE_FREQ`
*Default: "2"*|How often the OPcache checks for updates to cached files (in seconds). (Official docs)|all +`PHP_OPCACHE_SAVE_COMMENTS`
*Default: "1"*|Remove comments from OPcache to minify a bit further. Note: any code that depends on PHPDoc annotations can break from this. (Official docs)|all +`PHP_OPCACHE_VALIDATE_TIMESTAMPS`
*Default: "1"*|Whether OPcache checks for changes to files, or requires reload of PHP to revalidate OPcache. (Official docs)|all `PHP_OPEN_BASEDIR`
*Default: "None"* |Limit the files that can be accessed by PHP to the specified directory-tree, including the file itself. `open_basedir` is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed. (Official docs)| all `PHP_POST_MAX_SIZE`
*Default: "100M"*|Sets max size of post data allowed. (Official docs)|all `PHP_SESSION_COOKIE_SECURE`
*Default: 1 (true)*|Specifies whether cookies should only be sent over secure connections. (Official docs)|all diff --git a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini index a5da09e1e..2b2d0a642 100644 --- a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini +++ b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini @@ -1810,7 +1810,7 @@ opcache.max_accelerated_files=${PHP_OPCACHE_MAX_ACCELERATED_FILES} ; When disabled, you must reset the OPcache manually or restart the ; webserver for changes to the filesystem to take effect. -;opcache.validate_timestamps=1 +opcache.validate_timestamps=${PHP_OPCACHE_VALIDATE_TIMESTAMPS} ; How often (in seconds) to check file timestamps for changes to the shared ; memory storage allocation. ("1" means validate once per second, but only @@ -1822,7 +1822,13 @@ opcache.revalidate_freq=${PHP_OPCACHE_REVALIDATE_FREQ} ; If disabled, all PHPDoc comments are dropped from the code to reduce the ; size of the optimized code. -;opcache.save_comments=1 +opcache.save_comments=${PHP_OPCACHE_SAVE_COMMENTS} + +; Configure the JIT compiler. +opcache.jit=${PHP_OPCACHE_JIT} + +; The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. +opcache.jit_buffer_size=${PHP_OPCACHE_JIT_BUFFER_SIZE} ; If enabled, compilation warnings (including notices and deprecations) will ; be recorded and replayed each time a file is included. Otherwise, compilation diff --git a/src/variations/cli/Dockerfile b/src/variations/cli/Dockerfile index 63a926b3a..68b49e477 100644 --- a/src/variations/cli/Dockerfile +++ b/src/variations/cli/Dockerfile @@ -38,10 +38,15 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ + PHP_OPCACHE_JIT="off" \ + PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ PHP_OPCACHE_MAX_ACCELERATED_FILES="10000" \ PHP_OPCACHE_MEMORY_CONSUMPTION="128" \ PHP_OPCACHE_REVALIDATE_FREQ="2" \ + PHP_OPCACHE_SAVE_COMMENTS="1" \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_SESSION_COOKIE_SECURE=false \ diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 52e82a294..3d7d62d74 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -71,10 +71,15 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ + PHP_OPCACHE_JIT="off" \ + PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ PHP_OPCACHE_MAX_ACCELERATED_FILES="10000" \ PHP_OPCACHE_MEMORY_CONSUMPTION="128" \ PHP_OPCACHE_REVALIDATE_FREQ="2" \ + PHP_OPCACHE_SAVE_COMMENTS="1" \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_SESSION_COOKIE_SECURE=false \ diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index 5360b39ca..9b65782b1 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -95,10 +95,15 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ + PHP_OPCACHE_JIT="off" \ + PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ PHP_OPCACHE_MAX_ACCELERATED_FILES="10000" \ PHP_OPCACHE_MEMORY_CONSUMPTION="128" \ PHP_OPCACHE_REVALIDATE_FREQ="2" \ + PHP_OPCACHE_SAVE_COMMENTS="1" \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_SESSION_COOKIE_SECURE=false \ diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index 76a6a96ec..66cc6543f 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -46,10 +46,15 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ + PHP_OPCACHE_JIT="off" \ + PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ PHP_OPCACHE_MAX_ACCELERATED_FILES="10000" \ PHP_OPCACHE_MEMORY_CONSUMPTION="128" \ PHP_OPCACHE_REVALIDATE_FREQ="2" \ + PHP_OPCACHE_SAVE_COMMENTS="1" \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_SESSION_COOKIE_SECURE=Off \ diff --git a/src/variations/unit/Dockerfile b/src/variations/unit/Dockerfile index d1e2723d4..601c84ea8 100644 --- a/src/variations/unit/Dockerfile +++ b/src/variations/unit/Dockerfile @@ -94,10 +94,15 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ + PHP_OPCACHE_JIT="off" \ + PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ PHP_OPCACHE_MAX_ACCELERATED_FILES="10000" \ PHP_OPCACHE_MEMORY_CONSUMPTION="128" \ PHP_OPCACHE_REVALIDATE_FREQ="2" \ + PHP_OPCACHE_SAVE_COMMENTS="1" \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_SESSION_COOKIE_SECURE=false \ From 8f22dbfa939b0fb8a55caf55b49d2498445625f0 Mon Sep 17 00:00:00 2001 From: Rob Lyons <39706150+aSeriousDeveloper@users.noreply.github.com> Date: Tue, 11 Mar 2025 18:02:06 +0000 Subject: [PATCH 027/304] Refactor Laravel Autorun Script, adjust entrypoint behavior, contianer info improvements (#511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 50-laravel-automations.sh * Update Command Reference * Mention optimize in automations documentation * Attempt to use --except when running artisan optimize * Update docs to mention new function of optimize * Mention --except in env spec * POSIX /bin/sh compatibility, refactor into functions * Remove erroneous concatenate * Fix typo Co-authored-by: Paul * typo fix Co-authored-by: Erik Gaal * typo fix Co-authored-by: Erik Gaal * typo fix Co-authored-by: Erik Gaal * Improve entrypoint script management and error handling - Update entrypoint script to use subshell for script execution - Enhance logging and error reporting for initialization scripts - Remove "Don't use exit 0" section from documentation - Modify exit handling in initialization scripts to preserve environment * Update shell script compatibility guidelines Clarify shell script compatibility requirements for different Linux distributions and operating systems, specifying separate guidelines for /bin/sh and /bin/bash scripts * Enhance container info script with more detailed system information - Improve welcome message layout and readability - Add memory and upload limit information - Include Docker CMD in runtime section - Replace `return 0` with `exit 0` for script termination - Update links and formatting for better user experience * Export Docker CMD for use in initialization scripts - Add DOCKER_CMD environment variable to capture the original Docker command - Enable easier access to the original command in subsequent initialization scripts * Major refactor of Laravel automations script - Add `AUTORUN_DEBUG` environment variable for detailed Laravel automation logging - Implement comprehensive Laravel version detection and compatibility checks - Improve optimization and migration scripts with granular control - Update documentation to reflect new automation features * Update docs to reflect change in optimize process * Fix Joel's Twitter profile 😃 * Update Laravel Automations documentation - Improve description and clarity of Laravel Automations script - Add comprehensive table of environment variables and their functions - Update links to Laravel documentation to version 12.x - Enhance explanations for each Laravel artisan command - Refactor content to provide more detailed and user-friendly information * Add debugging section for Laravel Automations script - Document troubleshooting steps for AUTORUN script - Explain how to enable debug output with environment variables - Provide best practices for preventing AUTORUN script issues * Improve NGINX health check logging and debug output - Add conditional debug logging for NGINX + PHP-FPM health checks - Display more informative message when service is not yet online - Only show HTTP status code when debug mode is enabled * Fix warning about /run not being correct permissions on Alpine * Update Laravel Automations documentation with optimize command details - Add explanation for disabling the `optimize` command - Highlight the benefits of using the `optimize` command - Improve formatting of section headers for better readability --------- Co-authored-by: Paul Co-authored-by: Erik Gaal Co-authored-by: Jay Rogers --- .cursor/rules | 3 +- README.md | 2 +- .../docs/4.laravel/1.laravel-automations.md | 74 +++- .../3.adding-your-own-start-up-scripts.md | 5 - .../1.environment-variable-specification.md | 2 + .../etc/entrypoint.d/0-container-info.sh | 60 +-- .../etc/entrypoint.d/1-log-output-level.sh | 2 +- .../entrypoint.d/50-laravel-automations.sh | 381 +++++++++++++----- .../bin/docker-php-serversideup-entrypoint | 23 +- ...cker-php-serversideup-set-file-permissions | 2 + .../entrypoint.d/10-init-webserver-config.sh | 6 +- .../etc/s6-overlay/s6-rc.d/nginx/data/check | 8 +- 12 files changed, 417 insertions(+), 151 deletions(-) diff --git a/.cursor/rules b/.cursor/rules index a54d85222..e869b89bd 100644 --- a/.cursor/rules +++ b/.cursor/rules @@ -17,7 +17,8 @@ You are a highly skilled PHP system administrator tasked with maintaining open s - Follow the best practices for security, performance, and developer experience. - Write clean, maintainable and technically accurate code. - All entrypoint scripts for the Docker images must be POSIX compliant and able to be executed with /bin/sh. - - Bash scripts must be compatible with Linux, WSL2, and MacOS (Bash v3). + - Any /bin/sh scripts must be compatible with Debian and Alpine Linux. + - For any /bin/bash scripts, these should work with MacOS, Linux, and WSL2. - Never use an approach you're not confident about. If you're unsure about something, ask for clarity. This project is open source and the code is available on GitHub, so be sure to follow best practices to make it easy for others to understand, modify, and contribute to the project. \ No newline at end of file diff --git a/README.md b/README.md index cfdb1a332..eecd140ce 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ We'd like to specifically thank a few folks for taking the time for being a soun Please check out all of their work: - [Chris Fidao](https://twitter.com/fideloper) -- [Joel Clermont](https://twitter.com/joelclermont) +- [Joel Clermont](https://x.com/jclermont) - [Patricio](https://twitter.com/PatricioOnCode) ## About Us diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md index f0431c727..9ce8782ff 100644 --- a/docs/content/docs/4.laravel/1.laravel-automations.md +++ b/docs/content/docs/4.laravel/1.laravel-automations.md @@ -5,43 +5,85 @@ layout: docs --- # Laravel Automations -`serversideup/php` has a "Laravel Automations" script that helps you automate certain steps during your deployments. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. +`serversideup/php` has a "Laravel Automations" script that helps automate common tasks to maintain your Laravel application and improve it's performance. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. + +## What the script does ::note -`AUTORUN_ENABLED` must be set to `true` to enable the script. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. +In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once the main part of the script is enabled, you can control the individual tasks by setting the corresponding environment variables to `true` or `false`. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. :: -## What the script does -This script executes on container start up and performs the following tasks: +| Environment Variable | Laravel Command | Description | +| -------------------- | -------------- | ----------- | +| `AUTORUN_LARAVEL_STORAGE_LINK` | `php artisan storage:link` | Creates a symbolic link from `public/storage` to `storage/app/public`. | +| `AUTORUN_LARAVEL_MIGRATION` | `php artisan migrate` | Runs migrations. | +| `AUTORUN_LARAVEL_OPTIMIZE` | `php artisan optimize` | Optimizes the application. | +| `AUTORUN_LARAVEL_CONFIG_CACHE` | `php artisan config:cache` | Caches the configuration files into a single file. | +| `AUTORUN_LARAVEL_ROUTE_CACHE` | `php artisan route:cache` | Caches the routes. | +| `AUTORUN_LARAVEL_VIEW_CACHE` | `php artisan view:cache` | Caches the views. | +| `AUTORUN_LARAVEL_EVENT_CACHE` | `php artisan event:cache` | Creates a manifest of all your application's events and listeners. | + +## php artisan storage:link +Creates a symbolic link from `public/storage` to `storage/app/public`. + +[Read more about storage links →](https://laravel.com/docs/12.x/filesystem#the-public-disk) -### php artisan migrate +## php artisan migrate Before running migrations, we ensure the database is online and ready to accept connections. By default, we will wait 30 seconds before timing out. -You can enable the [`--isolated`](https://laravel.com/docs/11.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. +You can enable the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. **Special Notes for Isolated Migrations:** - Requires Laravel v9.38.0+ - Your database must support database locks (meaning SQLite is not supported) -### php artisan storage:link -This command creates a symbolic link from `public/storage` to `storage/app/public`. +[Read more about migrations →](https://laravel.com/docs/12.x/migrations#running-migrations) + +## php artisan optimize +Laravel comes with an artisan command called `optimize`, which will optimize the application by caching the configuration, routes, views, and events all in one command. + +You can disable any cache features by setting the corresponding environment variable to `false` (for example, `AUTORUN_LARAVEL_CONFIG_CACHE` would disable configuration caching). + +If your application is running Laravel v11.38.0 or higher, we will utilize the `optimize --except` parameter to exclude any cache features you have disabled. Otherwise, we will run the individual optimizations separately. + +It's possible to disable the `optimize` command by setting `AUTORUN_LARAVEL_OPTIMIZE` to `false`, but the major advantage of using the `optimize` command is other dependencies may hook into this action and run other commands. -### php artisan config:cache +[Read more about optimizing Laravel →](https://laravel.com/docs/12.x/deployment#optimization) + +## php artisan config:cache This command caches all configuration files into a single file, which can then be quickly loaded by Laravel. Once the configuration is cache, the `.env` file will no longer be loaded. -[Read more about configuration caching →](https://laravel.com/docs/11.x/configuration#configuration-caching) +[Read more about configuration caching →](https://laravel.com/docs/12.x/configuration#configuration-caching) -### php artisan route:cache +## php artisan route:cache This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. -[Read more about route caching →](https://laravel.com/docs/11.x/routing#route-caching) +[Read more about route caching →](https://laravel.com/docs/12.x/routing#route-caching) -### php artisan view:cache +## php artisan view:cache This command caches all of the views in your application, which can greatly decrease the time it takes to render your views. -[Read more about view caching →](https://laravel.com/docs/11.x/views#optimizing-views) +[Read more about view caching →](https://laravel.com/docs/12.x/views#optimizing-views) -### php artisan event:cache +## php artisan event:cache This command creates a manifest of all your application's events and listeners, which can greatly speed up the process of registering them with Laravel. -[Read more about event caching →](https://laravel.com/docs/11.x/events#event-discovery-in-production) \ No newline at end of file +[Read more about event caching →](https://laravel.com/docs/12.x/events#event-discovery-in-production) + +## Debugging the AUTORUN script +It's very important to understand the nature of how containerized environments work when debugging the AUTORUN script. In some cases, some users may become frustrated when they push an update but their changes are never deployed. + +In most cases, this is due to a bug in their application code that causes a migration or some other process to fail. + +::note +If a failure occurs in the Laravel Automations script, it will exit with a non-zero exit code -- preventing the container from starting. +:: + +If you are experiencing issues, you can enable the `AUTORUN_DEBUG` environment variable to get more detailed ouput of what could be going wrong. + +If you need even more information, you can set `LOG_OUTPUT_LEVEL` to `debug` to get **A TON** of output of what's exactly happening. + +### Preventing issues with the AUTORUN script +- Ensure you are running the latest version of `serversideup/php` +- Ensure you have dependencies installed before calling this script +- Use automated testing to catch issues before deploying \ No newline at end of file diff --git a/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md b/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md index 2314e936d..5121c977a 100644 --- a/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md +++ b/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md @@ -37,11 +37,6 @@ Anything in the `/etc/entrypoint.d` directory are scripts that are intended to r Instead, learn about [using S6 overlay](/docs/guide/using-s6-overlay) so your services can be properly initialized and monitored. See the [S6 Overylay project](https://github.com/just-containers/s6-overlay) for more details on how to write your own S6 service. -## Don't use `exit 0` in your script -If you use `exit 0` in your script, it will stop the execution of the rest of the entrypoint scripts. We recommend using `return 0` instead. [See this discussion](https://github.com/serversideup/docker-php/issues/481#issuecomment-2463082306) for more details on why. - -Long story short, we don't use subshells to execute your scripts, so `exit 0` will not work as expected. We do this because we want to ensure your script has access to the environment variables that are set in the entrypoint scripts. - ## Example: Create a custom entrypoint script In this example, let's create a `99-my-script.sh` so it executes after all the other default scripts. diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 8a7f60792..0e591a2bd 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -20,7 +20,9 @@ We like to customize our images on a per app basis using environment variables. `APACHE_THREAD_LIMIT`
*Default: "64"*|Set the maximum configured value for ThreadsPerChild for the lifetime of the Apache httpd process. (Official docs)|fpm-apache `APACHE_THREADS_PER_CHILD`
*Default: "25"*|This directive sets the number of threads created by each child process. (Official docs)|fpm-apache `APP_BASE_DIR`
*Default: "/var/www/html"*|Change this only if you mount your application to a different directory within the container. ℹ️ Be sure to change `NGINX_WEBROOT`, `APACHE_DOCUMENT_ROOT`, `UNIT_WEBROOT`, etc if it applies to your use case as well.|all +`AUTORUN_DEBUG`
*Default: "false"*|Enable debug mode for the Laravel automations. | all `AUTORUN_ENABLED`
*Default: "false"*|Enable or disable all automations. It's advised to set this to `false` in certain CI environments (especially during a composer install). If this is set to `false`, all `AUTORUN_*` behaviors will also be disabled.| all +`AUTORUN_LARAVEL_OPTIMIZE`
*Default: "false"*|Automatically run "php artisan optimize" on container, attempting to `--except` in Laravel > `v11.38.0` (Official docs)
ℹ️ Requires `AUTORUN_ENABLED = true` to run. | all `AUTORUN_LARAVEL_CONFIG_CACHE`
*Default: "true"*|Automatically run "php artisan config:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_EVENT_CACHE`
*Default: "true"*|Automatically run "php artisan event:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION`
*Default: "true"*|Automatically run `php artisan migrate --force` on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all diff --git a/src/common/etc/entrypoint.d/0-container-info.sh b/src/common/etc/entrypoint.d/0-container-info.sh index 28d52a018..a71de57ca 100644 --- a/src/common/etc/entrypoint.d/0-container-info.sh +++ b/src/common/etc/entrypoint.d/0-container-info.sh @@ -4,9 +4,22 @@ if [ "$SHOW_WELCOME_MESSAGE" = "false" ] || [ "$LOG_OUTPUT_LEVEL" = "off" ] || [ echo "👉 $0: Container info was display was skipped." fi # Skip the rest of the script - return 0 + exit 0 fi +# Get OPcache status +PHP_OPCACHE_STATUS=$(php -r 'echo ini_get("opcache.enable");') + +if [ "$PHP_OPCACHE_STATUS" = "1" ]; then + PHP_OPCACHE_MESSAGE="✅ Enabled" +else + PHP_OPCACHE_MESSAGE="❌ Disabled" +fi + +# Get memory limits +MEMORY_LIMIT=$(php -r 'echo ini_get("memory_limit");') +UPLOAD_LIMIT=$(php -r 'echo ini_get("upload_max_filesize");') + echo ' -------------------------------------------------------------------- ____ ____ _ _ _ _ @@ -17,32 +30,33 @@ echo ' |_| Brought to you by serversideup.net ---------------------------------------------------------------------' - -PHP_OPCACHE_STATUS=$(php -r 'echo ini_get("opcache.enable");') - -if [ "$PHP_OPCACHE_STATUS" = "1" ]; then - PHP_OPCACHE_MESSAGE="✅ Enabled" -else - PHP_OPCACHE_MESSAGE="❌ Disabled" -fi +-------------------------------------------------------------------- -echo ' -🙌 To support Server Side Up projects visit: -https://serversideup.net/sponsor +📚 Documentation: https://serversideup.net/php/docs +💬 Get Help: https://serversideup.net/php/community +🙌 Sponsor: https://serversideup.net/sponsor ------------------------------------- ℹ️ Container Information --------------------------------------' -echo " -OS: $(. /etc/os-release; echo "${PRETTY_NAME}") -Docker user: $(whoami) -Docker uid: $(id -u) -Docker gid: $(id -g) -OPcache: $PHP_OPCACHE_MESSAGE -PHP Version: $(php -r 'echo phpversion();') -Image Version: $(cat /etc/serversideup-php-version) -" +------------------------------------- +📦 Versions +• Image: '"$(cat /etc/serversideup-php-version)"' +• PHP: '"$(php -r 'echo phpversion();')"' +• OS: '"$(. /etc/os-release; echo "${PRETTY_NAME}")"' + +👤 Container User +• User: '"$(whoami)"' +• UID: '"$(id -u)"' +• GID: '"$(id -g)"' + +⚡ Performance +• OPcache: '"$PHP_OPCACHE_MESSAGE"' +• Memory Limit: '"$MEMORY_LIMIT"' +• Upload Limit: '"$UPLOAD_LIMIT"' + +🔄 Runtime +• Docker CMD: '"$DOCKER_CMD"' +' if [ "$PHP_OPCACHE_STATUS" = "0" ]; then echo "👉 [NOTICE]: Improve PHP performance by setting PHP_OPCACHE_ENABLE=1 (recommended for production)." diff --git a/src/common/etc/entrypoint.d/1-log-output-level.sh b/src/common/etc/entrypoint.d/1-log-output-level.sh index 44f6cd9a3..6f93c6a0d 100644 --- a/src/common/etc/entrypoint.d/1-log-output-level.sh +++ b/src/common/etc/entrypoint.d/1-log-output-level.sh @@ -5,7 +5,7 @@ if [ "$DISABLE_DEFAULT_CONFIG" = true ]; then if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then echo "👉 $script_name: DISABLE_DEFAULT_CONFIG does not equal \"false\", so debug mode will NOT be automatically set." fi - return 0 # Exit if DISABLE_DEFAULT_CONFIG is true + exit 0 # Exit if DISABLE_DEFAULT_CONFIG is true fi ####################################### diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index b6f217de7..5b33e2b85 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -1,6 +1,263 @@ #!/bin/sh script_name="laravel-automations" +# Global configurations +: "${DISABLE_DEFAULT_CONFIG:=false}" +: "${APP_BASE_DIR:=/var/www/html}" + +# Set default values for Laravel automations +: "${AUTORUN_ENABLED:=false}" +: "${AUTORUN_DEBUG:=false}" + +# Set default values for storage link +: "${AUTORUN_LARAVEL_STORAGE_LINK:=true}" + +# Set default values for optimizations +: "${AUTORUN_LARAVEL_OPTIMIZE:=true}" +: "${AUTORUN_LARAVEL_CONFIG_CACHE:=true}" +: "${AUTORUN_LARAVEL_ROUTE_CACHE:=true}" +: "${AUTORUN_LARAVEL_VIEW_CACHE:=true}" +: "${AUTORUN_LARAVEL_EVENT_CACHE:=true}" + +# Set default values for Migrations +: "${AUTORUN_LARAVEL_MIGRATION:=true}" +: "${AUTORUN_LARAVEL_MIGRATION_ISOLATION:=false}" +: "${AUTORUN_LARAVEL_MIGRATION_TIMEOUT:=30}" + +# Set default values for Laravel version +INSTALLED_LARAVEL_VERSION="" + +############################################################################ +# Sanity Checks +############################################################################ + +debug_log() { + if [ "$LOG_OUTPUT_LEVEL" = "debug" ] || [ "$AUTORUN_DEBUG" = "true" ]; then + echo "👉 DEBUG ($script_name): $1" >&2 + fi +} + +if [ "$DISABLE_DEFAULT_CONFIG" = "true" ] || [ "$AUTORUN_ENABLED" = "false" ]; then + debug_log "Skipping Laravel automations because DISABLE_DEFAULT_CONFIG is true or AUTORUN_ENABLED is false." + exit 0 +fi + +############################################################################ +# Functions +############################################################################ + +artisan_migrate() { + count=0 + timeout=$AUTORUN_LARAVEL_MIGRATION_TIMEOUT + + debug_log "Starting migrations (timeout: ${timeout}s, isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION)" + + echo "🚀 Clearing Laravel cache before attempting migrations..." + php "$APP_BASE_DIR/artisan" config:clear + + # Do not exit on error for this loop + set +e + echo "⚡️ Attempting database connection..." + while [ $count -lt "$timeout" ]; do + if [ "$AUTORUN_DEBUG" = "true" ]; then + # Show output when debug is enabled + test_db_connection + else + # Otherwise suppress output + test_db_connection > /dev/null 2>&1 + fi + status=$? + if [ $status -eq 0 ]; then + echo "✅ Database connection successful." + break + else + # Only log every 5 attempts to reduce noise + if [ $((count % 5)) -eq 0 ]; then + debug_log "Connection attempt $((count + 1))/$timeout failed (status: $status)" + fi + echo "Waiting on database connection, retrying... $((timeout - count)) seconds left" + count=$((count + 1)) + sleep 1 + fi + done + + # Re-enable exit on error + set -e + + if [ $count -eq "$timeout" ]; then + echo "❌ $script_name: Database connection failed after multiple attempts." + debug_log "Database connection timed out after $timeout seconds" + return 1 + fi + + if [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ] && laravel_version_is_at_least "9.38.0"; then + debug_log "Running migrations with --isolated flag" + echo "🚀 Running migrations: \"php artisan migrate --force --isolated\"..." + php "$APP_BASE_DIR/artisan" migrate --force --isolated + else + debug_log "Running standard migrations" + echo "🚀 Running migrations: \"php artisan migrate --force\"..." + php "$APP_BASE_DIR/artisan" migrate --force + fi +} + +artisan_storage_link() { + if [ -d "$APP_BASE_DIR/public/storage" ]; then + echo "✅ Storage already linked..." + else + echo "🔐 Running storage link: \"php artisan storage:link\"..." + php "$APP_BASE_DIR/artisan" storage:link + fi +} + +artisan_optimize() { + # Case 1: All optimizations are enabled - use simple optimize command + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && \ + [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ] && \ + [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ] && \ + [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "true" ] && \ + [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "true" ]; then + echo "🚀 Running optimize command: \"php artisan optimize\"..." + if ! php "$APP_BASE_DIR/artisan" optimize; then + echo "❌ Laravel optimize failed" + return 1 + fi + return 0 + fi + + # Case 2: AUTORUN_LARAVEL_OPTIMIZE is true but some optimizations disabled + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && laravel_version_is_at_least "11.38.0"; then + echo "🛠️ Preparing optimizations..." + except="" + + # Build except string for disabled optimizations + [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "false" ] && except="${except:+${except},}config" + [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "false" ] && except="${except:+${except},}routes" + [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "false" ] && except="${except:+${except},}views" + [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "false" ] && except="${except:+${except},}events" + + echo "🛠️ Running optimizations: \"php artisan optimize ${except:+--except=${except}}\"..." + if ! php "$APP_BASE_DIR/artisan" optimize ${except:+--except=${except}}; then + echo "$script_name: ❌ Laravel optimize failed" + return 1 + fi + return 0 + fi + + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && ! laravel_version_is_at_least "11.38.0"; then + echo "ℹ️ Granular optimizations require Laravel v11.38.0 or above, using individual optimizations instead..." + fi + + # Case 3: Individual optimizations when AUTORUN_LARAVEL_OPTIMIZE is false + has_error=0 + + if [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ]; then + echo "🚀 Caching config: \"php artisan config:cache\"..." + php "$APP_BASE_DIR/artisan" config:cache || has_error=1 + fi + + if [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ]; then + echo "🚀 Caching routes: \"php artisan route:cache\"..." + php "$APP_BASE_DIR/artisan" route:cache || has_error=1 + fi + + if [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "true" ]; then + echo "🚀 Caching views: \"php artisan view:cache\"..." + php "$APP_BASE_DIR/artisan" view:cache || has_error=1 + fi + + if [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "true" ]; then + echo "🚀 Caching events: \"php artisan event:cache\"..." + php "$APP_BASE_DIR/artisan" event:cache || has_error=1 + fi + + return $has_error +} + +get_laravel_version() { + # Return cached version if already set + if [ -n "$INSTALLED_LARAVEL_VERSION" ]; then + debug_log "Using cached Laravel version: $INSTALLED_LARAVEL_VERSION" + echo "$INSTALLED_LARAVEL_VERSION" + return 0 + fi + + debug_log "Detecting Laravel version..." + # Use 2>/dev/null to handle potential PHP warnings + artisan_version_output=$(php "$APP_BASE_DIR/artisan" --version 2>/dev/null) + + # Check if command was successful + if [ $? -ne 0 ]; then + echo "❌ $script_name: Failed to execute artisan command" >&2 + return 1 + fi + + # Extract version number using sed (POSIX compliant) + # Using a more strict pattern that matches "Laravel Framework X.Y.Z" + laravel_version=$(echo "$artisan_version_output" | sed -e 's/^Laravel Framework \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/') + + # Validate that we got a version number (POSIX compliant regex) + if echo "$laravel_version" | grep '^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null 2>&1; then + INSTALLED_LARAVEL_VERSION="$laravel_version" + debug_log "Detected Laravel version: $laravel_version" + echo "$laravel_version" + return 0 + else + echo "❌ $script_name: Failed to determine Laravel version" >&2 + return 1 + fi +} + +laravel_is_installed() { + if [ ! -f "$APP_BASE_DIR/artisan" ]; then + return 1 + fi + + if [ ! -d "$APP_BASE_DIR/vendor" ]; then + return 1 + fi + + return 0 +} + +laravel_version_is_at_least() { + required_version="$1" + + if [ -z "$required_version" ]; then + echo "❌ $script_name - Usage: laravel_version_is_at_least " >&2 + return 1 + fi + + # Validate required version format + if ! echo "$required_version" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then + echo "❌ $script_name - Invalid version requirement format: $required_version" >&2 + return 1 + fi + + current_version=$(get_laravel_version) + if [ $? -ne 0 ]; then + echo "❌ $script_name: Failed to get Laravel version" >&2 + return 1 + fi + + # normalize_version() takes a version string and ensures it has 3 parts + normalize_version() { + echo "$1" | awk -F. '{ print $1"."$2"."(NF>2?$3:0) }' + } + + normalized_current=$(normalize_version "$current_version") + normalized_required=$(normalize_version "$required_version") + + # Use sort -V to get the lower version, then compare it with required version + # This works in BusyBox because we only need to check the first line of output + lowest_version=$(printf '%s\n%s\n' "$normalized_required" "$normalized_current" | sort -V | head -n1) + if [ "$lowest_version" = "$normalized_required" ]; then + return 0 # Success: current version is >= required version + else + return 1 # Failure: current version is < required version + fi +} + test_db_connection() { php -r " require '$APP_BASE_DIR/vendor/autoload.php'; @@ -32,102 +289,38 @@ test_db_connection() { " } +############################################################################ +# Main +############################################################################ -# Set default values for Laravel automations -: "${AUTORUN_ENABLED:=false}" -: "${AUTORUN_LARAVEL_MIGRATION_TIMEOUT:=30}" - -if [ "$DISABLE_DEFAULT_CONFIG" = "false" ]; then - # Check to see if an Artisan file exists and assume it means Laravel is configured. - if [ -f "$APP_BASE_DIR/artisan" ] && [ "$AUTORUN_ENABLED" = "true" ]; then - echo "Checking for Laravel automations..." - ############################################################################ - # artisan migrate - ############################################################################ - if [ "${AUTORUN_LARAVEL_MIGRATION:=true}" = "true" ]; then - count=0 - timeout=$AUTORUN_LARAVEL_MIGRATION_TIMEOUT - - echo "🚀 Clearing Laravel cache before attempting migrations..." - php "$APP_BASE_DIR/artisan" config:clear - - # Do not exit on error for this loop - set +e - echo "⚡️ Attempting database connection..." - while [ $count -lt "$timeout" ]; do - test_db_connection > /dev/null 2>&1 - status=$? - if [ $status -eq 0 ]; then - echo "✅ Database connection successful." - break - else - echo "Waiting on database connection, retrying... $((timeout - count)) seconds left" - count=$((count + 1)) - sleep 1 - fi - done - - # Re-enable exit on error - set -e - - if [ $count -eq "$timeout" ]; then - echo "Database connection failed after multiple attempts." - return 1 - fi - - echo "🚀 Running migrations..." - if [ "${AUTORUN_LARAVEL_MIGRATION_ISOLATION:=false}" = "true" ]; then - php "$APP_BASE_DIR/artisan" migrate --force --isolated - else - php "$APP_BASE_DIR/artisan" migrate --force - fi - fi +if laravel_is_installed; then + debug_log "Laravel detected: v$(get_laravel_version)" + debug_log "Automation settings:" + debug_log "- Storage Link: $AUTORUN_LARAVEL_STORAGE_LINK" + debug_log "- Migrations: $AUTORUN_LARAVEL_MIGRATION" + debug_log "- Migrations Isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION" + debug_log "- Optimize: $AUTORUN_LARAVEL_OPTIMIZE" + debug_log "- Config Cache: $AUTORUN_LARAVEL_CONFIG_CACHE" + debug_log "- Route Cache: $AUTORUN_LARAVEL_ROUTE_CACHE" + debug_log "- View Cache: $AUTORUN_LARAVEL_VIEW_CACHE" + debug_log "- Event Cache: $AUTORUN_LARAVEL_EVENT_CACHE" - ############################################################################ - # artisan storage:link - ############################################################################ - if [ "${AUTORUN_LARAVEL_STORAGE_LINK:=true}" = "true" ]; then - if [ -d "$APP_BASE_DIR/public/storage" ]; then - echo "✅ Storage already linked..." - else - echo "🔐 Linking the storage..." - php "$APP_BASE_DIR/artisan" storage:link - fi - fi - ############################################################################ - # artisan config:cache - ############################################################################ - if [ "${AUTORUN_LARAVEL_CONFIG_CACHE:=true}" = "true" ]; then - echo "🚀 Caching Laravel config..." - php "$APP_BASE_DIR/artisan" config:cache - fi - - ############################################################################ - # artisan route:cache - ############################################################################ - if [ "${AUTORUN_LARAVEL_ROUTE_CACHE:=true}" = "true" ]; then - echo "🚀 Caching Laravel routes..." - php "$APP_BASE_DIR/artisan" route:cache - fi - - ############################################################################ - # artisan view:cache - ############################################################################ - if [ "${AUTORUN_LARAVEL_VIEW_CACHE:=true}" = "true" ]; then - echo "🚀 Caching Laravel views..." - php "$APP_BASE_DIR/artisan" view:cache - fi + echo "🤔 Checking for Laravel automations..." + if [ "$AUTORUN_LARAVEL_STORAGE_LINK" = "true" ]; then + artisan_storage_link + fi + + if [ "$AUTORUN_LARAVEL_MIGRATION" = "true" ]; then + artisan_migrate + fi - ############################################################################ - # artisan event:cache - ############################################################################ - if [ "${AUTORUN_LARAVEL_EVENT_CACHE:=true}" = "true" ]; then - echo "🚀 Caching Laravel events..." - php "$APP_BASE_DIR/artisan" event:cache - fi + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] || \ + [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ] || \ + [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ] || \ + [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "true" ] || \ + [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "true" ]; then + artisan_optimize fi else - if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then - echo "👉 $script_name: DISABLE_DEFAULT_CONFIG does not equal 'false', so automations will NOT be performed." - fi -fi + echo "👉 $script_name: Skipping Laravel automations because Laravel is not installed." +fi \ No newline at end of file diff --git a/src/common/usr/local/bin/docker-php-serversideup-entrypoint b/src/common/usr/local/bin/docker-php-serversideup-entrypoint index 879c4e447..b678f29ee 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-entrypoint +++ b/src/common/usr/local/bin/docker-php-serversideup-entrypoint @@ -32,6 +32,9 @@ fi export SERVERSIDEUP_DEFAULT_COMMAND export S6_INITIALIZED +# Export the CMD for use in initialization scripts +export DOCKER_CMD="$*" + ############################################### # Usage: docker-php-serversideup-entrypoint ############################################### @@ -42,14 +45,24 @@ export S6_INITIALIZED find /etc/entrypoint.d/ -type f -name '*.sh' | sort -V | while IFS= read -r f; do if [ -e "$f" ]; then if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then - echo "Executing $f" + echo "🔄 Executing initialization script: $f" + fi + + # Source the script in a subshell to contain exits while preserving environment + (. "$f") + exit_code=$? + + if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then + echo "📝 Script $f completed with exit code: $exit_code" fi - if ! . "$f"; then - echo "Error executing $f" >&2 - exit 1 + + # Only stop on actual errors (non-zero exit codes) + if [ $exit_code -ne 0 ]; then + echo "❌ Error: Initialization script $f failed with exit code $exit_code" >&2 + exit $exit_code fi else - echo "Warning: $f not found" >&2 + echo "⚠️ Warning: Initialization script $f not found" >&2 fi done diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 63afb9cde..9d4bd6909 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -150,6 +150,7 @@ case "$OS" in apache) DIRS=" /composer + /run /var/www /etc/apache2 /etc/ssl/private @@ -160,6 +161,7 @@ case "$OS" in nginx) DIRS=" /composer + /run /var/www /etc/nginx /var/log/nginx diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index e501bc66e..3151f8ab3 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -9,8 +9,10 @@ script_name="init-webserver-config" # Check if S6 is initialized if [ "$S6_INITIALIZED" != "true" ]; then - echo "ℹ️ [NOTICE]: Running custom command instead of web server configuration: '$*'" - return 0 + if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then + echo "👉 $script_name: S6 is not initialized, so web server configuration will NOT be performed." + fi + exit 0 fi ########## diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index 45a273210..e718322f8 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -10,8 +10,10 @@ if is_online; then echo "✅ NGINX + PHP-FPM is running correctly." exit 0 else - echo "❌ There seems to be a failure in checking the NGINX + PHP-FPM." - status_code=$(curl $curl_options -w "%{http_code}" "$healthcheck_url") - echo "HTTP Status Code: $status_code" + echo "👉 NGINX + PHP-FPM is not online. Waiting for it to start..." + if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then + status_code=$(curl $curl_options -w "%{http_code}" "$healthcheck_url") + echo "HTTP Status Code: $status_code" + fi exit 1 fi From c73193f812633052a5a584a8631e8181c9c16b55 Mon Sep 17 00:00:00 2001 From: Guillaume Briday <8252238+guillaumebriday@users.noreply.github.com> Date: Tue, 11 Mar 2025 18:05:10 +0000 Subject: [PATCH 028/304] Add Cache-Control header to assets and media files (#487) Co-authored-by: Jay Rogers --- .../fpm-nginx/etc/nginx/server-opts.d/performance.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/variations/fpm-nginx/etc/nginx/server-opts.d/performance.conf b/src/variations/fpm-nginx/etc/nginx/server-opts.d/performance.conf index 86f0d78e3..46c35f34a 100644 --- a/src/variations/fpm-nginx/etc/nginx/server-opts.d/performance.conf +++ b/src/variations/fpm-nginx/etc/nginx/server-opts.d/performance.conf @@ -12,7 +12,7 @@ location = /robots.txt { # assets, media location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ { - expires 7d; + add_header Cache-Control "public, max-age=31536000, immutable"; access_log off; log_not_found off; # Pass to PHP to ensure PHP apps can handle routes that end in these filetypes @@ -22,7 +22,7 @@ location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp # svg, fonts location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ { add_header Access-Control-Allow-Origin "*"; - expires 7d; + add_header Cache-Control "public, max-age=31536000, immutable"; access_log off; } @@ -31,4 +31,4 @@ gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; -gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; \ No newline at end of file +gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; From 44d5e8e81c54a9ec291b2a24ad2d525676290430 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 11 Mar 2025 13:30:06 -0500 Subject: [PATCH 029/304] Update Laravel Automations documentation with detailed environment variables and database connection checks --- .../docs/4.laravel/1.laravel-automations.md | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md index 9ce8782ff..24dd1a05c 100644 --- a/docs/content/docs/4.laravel/1.laravel-automations.md +++ b/docs/content/docs/4.laravel/1.laravel-automations.md @@ -13,15 +13,26 @@ layout: docs In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once the main part of the script is enabled, you can control the individual tasks by setting the corresponding environment variables to `true` or `false`. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. :: -| Environment Variable | Laravel Command | Description | +| Environment Variable | Default | Description | | -------------------- | -------------- | ----------- | -| `AUTORUN_LARAVEL_STORAGE_LINK` | `php artisan storage:link` | Creates a symbolic link from `public/storage` to `storage/app/public`. | -| `AUTORUN_LARAVEL_MIGRATION` | `php artisan migrate` | Runs migrations. | -| `AUTORUN_LARAVEL_OPTIMIZE` | `php artisan optimize` | Optimizes the application. | -| `AUTORUN_LARAVEL_CONFIG_CACHE` | `php artisan config:cache` | Caches the configuration files into a single file. | -| `AUTORUN_LARAVEL_ROUTE_CACHE` | `php artisan route:cache` | Caches the routes. | -| `AUTORUN_LARAVEL_VIEW_CACHE` | `php artisan view:cache` | Caches the views. | -| `AUTORUN_LARAVEL_EVENT_CACHE` | `php artisan event:cache` | Creates a manifest of all your application's events and listeners. | +| `AUTORUN_ENABLED` | `false` | Enables the Laravel Automations script.
**ℹ️ Note:** This must be set to `true` for the script to run. | +| `AUTORUN_DEBUG` | `false` | Enables a special debug mode, specifically for the Laravel Automations script. | +| `AUTORUN_LARAVEL_CONFIG_CACHE` | `true` | `php artisan config:cache`: Caches the configuration files into a single file. | +| `AUTORUN_LARAVEL_EVENT_CACHE` | `true` | `php artisan event:cache`: Creates a manifest of all your application's events and listeners. | +| `AUTORUN_LARAVEL_MIGRATION` | `true` | `php artisan migrate`: Runs migrations. | +| `AUTORUN_LARAVEL_MIGRATION_ISOLATION` | `false` | Run your migrations with the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag.
**ℹ️ Note:** Requires Laravel v9.38.0+ | +| `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` | `30` | Number of seconds to wait for database connection before timing out during migrations. | +| `AUTORUN_LARAVEL_OPTIMIZE` | `true` | `php artisan optimize`: Optimizes the application. | +| `AUTORUN_LARAVEL_ROUTE_CACHE` | `true` | `php artisan route:cache`: Caches the routes. | +| `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | +| `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | + +## Database Connection Checks +Before running migrations, the script performs database connection checks to ensure the database is ready. It will: +- Clear the Laravel configuration cache before attempting migrations +- Try to connect to the database for up to `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` seconds (default: 30) +- Automatically detect and handle SQLite databases differently +- Show detailed connection attempts when `AUTORUN_DEBUG` is enabled ## php artisan storage:link Creates a symbolic link from `public/storage` to `storage/app/public`. From 8abfd75a127503a6d7deb6f6899a7a25be59366b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 11 Mar 2025 13:37:58 -0500 Subject: [PATCH 030/304] Update Laravel Automations documentation for isolated migrations --- docs/content/docs/4.laravel/1.laravel-automations.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md index 24dd1a05c..a37ac392d 100644 --- a/docs/content/docs/4.laravel/1.laravel-automations.md +++ b/docs/content/docs/4.laravel/1.laravel-automations.md @@ -31,7 +31,6 @@ In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once th Before running migrations, the script performs database connection checks to ensure the database is ready. It will: - Clear the Laravel configuration cache before attempting migrations - Try to connect to the database for up to `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` seconds (default: 30) -- Automatically detect and handle SQLite databases differently - Show detailed connection attempts when `AUTORUN_DEBUG` is enabled ## php artisan storage:link @@ -46,7 +45,7 @@ You can enable the [`--isolated`](https://laravel.com/docs/12.x/migrations#runni **Special Notes for Isolated Migrations:** - Requires Laravel v9.38.0+ -- Your database must support database locks (meaning SQLite is not supported) +- Your application must be using the memcached, redis, dynamodb, database, file, or array cache driver as your application's default cache driver. In addition, all servers must be communicating with the same central cache server. [Read more about migrations →](https://laravel.com/docs/12.x/migrations#running-migrations) From 2ffee5f70ff5d66b8ad7a0a77e92d2a0f031df32 Mon Sep 17 00:00:00 2001 From: Italo Date: Tue, 11 Mar 2025 16:09:57 -0300 Subject: [PATCH 031/304] Add opt-int database seeding support (#404) * Adds opt-in seeding * Updated seeders to execute correctly. Configured defaults. * Add documentation for Laravel db:seed automation option --------- Co-authored-by: Jay Rogers --- .../docs/4.laravel/1.laravel-automations.md | 6 ++++++ .../entrypoint.d/50-laravel-automations.sh | 21 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md index a37ac392d..b1f7bee47 100644 --- a/docs/content/docs/4.laravel/1.laravel-automations.md +++ b/docs/content/docs/4.laravel/1.laravel-automations.md @@ -23,6 +23,7 @@ In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once th | `AUTORUN_LARAVEL_MIGRATION_ISOLATION` | `false` | Run your migrations with the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag.
**ℹ️ Note:** Requires Laravel v9.38.0+ | | `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` | `30` | Number of seconds to wait for database connection before timing out during migrations. | | `AUTORUN_LARAVEL_OPTIMIZE` | `true` | `php artisan optimize`: Optimizes the application. | +| `AUTORUN_LARAVEL_SEED` | `false` | `php artisan db:seed`: Runs the default seeder.
**ℹ️ Note:** Set to `true` to run the default seeder. If you want to run a custom seeder, set this to the name of the seeder class. | | `AUTORUN_LARAVEL_ROUTE_CACHE` | `true` | `php artisan route:cache`: Caches the routes. | | `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | | `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | @@ -65,6 +66,11 @@ This command caches all configuration files into a single file, which can then b [Read more about configuration caching →](https://laravel.com/docs/12.x/configuration#configuration-caching) +## php artisan db:seed +This command runs the default seeder. If you want to run a custom seeder, set `AUTORUN_LARAVEL_SEED` to the name of the seeder class. + +[Read more about seeding →](https://laravel.com/docs/12.x/seeding) + ## php artisan route:cache This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 5b33e2b85..f60af69d1 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -24,6 +24,9 @@ script_name="laravel-automations" : "${AUTORUN_LARAVEL_MIGRATION_ISOLATION:=false}" : "${AUTORUN_LARAVEL_MIGRATION_TIMEOUT:=30}" +# Set default values for seeders +: "${AUTORUN_LARAVEL_SEED:=false}" + # Set default values for Laravel version INSTALLED_LARAVEL_VERSION="" @@ -136,7 +139,7 @@ artisan_optimize() { [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "false" ] && except="${except:+${except},}views" [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "false" ] && except="${except:+${except},}events" - echo "🛠️ Running optimizations: \"php artisan optimize ${except:+--except=${except}}\"..." + echo "🚀 Running optimizations: \"php artisan optimize ${except:+--except=${except}}\"..." if ! php "$APP_BASE_DIR/artisan" optimize ${except:+--except=${except}}; then echo "$script_name: ❌ Laravel optimize failed" return 1 @@ -174,6 +177,18 @@ artisan_optimize() { return $has_error } +artisan_seed(){ + # Run the default seeder if "true", otherwise use value as custom seeder + if [ "${AUTORUN_LARAVEL_SEED}" = "true" ]; then + echo "🚀 Running default seeder: \"php artisan db:seed\"" + php "${APP_BASE_DIR}/artisan" db:seed --force + else + echo "🚀 Running custom seeder: \"php artisan db:seed --seeder=${AUTORUN_LARAVEL_SEED}\"" + echo "ℹ️ Your application must have a seeder class named \"${AUTORUN_LARAVEL_SEED}\" or this command will fail." + php "${APP_BASE_DIR}/artisan" db:seed --seeder="${AUTORUN_LARAVEL_SEED}" + fi +} + get_laravel_version() { # Return cached version if already set if [ -n "$INSTALLED_LARAVEL_VERSION" ]; then @@ -314,6 +329,10 @@ if laravel_is_installed; then artisan_migrate fi + if [ "$AUTORUN_LARAVEL_SEED" != "false" ]; then + artisan_seed + fi + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] || \ [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ] || \ [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ] || \ From d123d4ac5f56cb98e10d5dae5e611e1e9290ce4e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 11 Mar 2025 14:17:07 -0500 Subject: [PATCH 032/304] Bump PHP Extension Installer version to 2.7.28 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index 6f071246a..3de683981 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.7.27" +PHP_EXT_INSTALLER_VERSION="2.7.28" ############ # Main From f74e487cd750224d874038fd9587c025f69826ac Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 11 Mar 2025 14:40:10 -0500 Subject: [PATCH 033/304] Bump NGINX Unit version to 1.34.2 --- src/variations/unit/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/unit/Dockerfile b/src/variations/unit/Dockerfile index 601c84ea8..58fec5e2a 100644 --- a/src/variations/unit/Dockerfile +++ b/src/variations/unit/Dockerfile @@ -9,7 +9,7 @@ ARG BASE_IMAGE="php:${PHP_VERSION}-cli-${BASE_OS_VERSION}" FROM ${BASE_IMAGE} AS build ARG DEPENDENCY_PACKAGES_ALPINE='build-base curl tar git openssl-dev pcre2-dev shadow' ARG DEPENDENCY_PACKAGES_DEBIAN='ca-certificates git build-essential libssl-dev libpcre2-dev curl pkg-config' -ARG NGINX_UNIT_VERSION='1.33.0-1' +ARG NGINX_UNIT_VERSION='1.34.2' # copy our scripts COPY --chmod=755 src/common/ / From 19ca9c838dda776803788d87f8a9684393e06533 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 18 Apr 2025 10:13:30 -0500 Subject: [PATCH 034/304] Bump PHP Extension Installer version to 2.7.29 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index 3de683981..051e89aea 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.7.28" +PHP_EXT_INSTALLER_VERSION="2.7.29" ############ # Main From f784f8dc3837ac2990983b07dc63ea5575c6c8a5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 18 Apr 2025 10:23:24 -0500 Subject: [PATCH 035/304] Bump PHP Extension Installer version to 2.7.31 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index 051e89aea..50529650e 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.7.29" +PHP_EXT_INSTALLER_VERSION="2.7.31" ############ # Main From 4af92bd27bcffdb9eb26ce93e354120fd5aab59e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 18 Jun 2025 13:18:20 -0500 Subject: [PATCH 036/304] Bump PHP Extension Installer version to 2.8.0 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index 50529650e..1684f9897 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.7.31" +PHP_EXT_INSTALLER_VERSION="2.8.0" ############ # Main From 80c794fa2d99a8f5c423ebe47fbbf618ccf8d6fd Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 18 Jun 2025 13:20:55 -0500 Subject: [PATCH 037/304] Bump S6 version to 3.2.1.0 --- src/s6/usr/local/bin/docker-php-serversideup-s6-install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/s6/usr/local/bin/docker-php-serversideup-s6-install b/src/s6/usr/local/bin/docker-php-serversideup-s6-install index 0458d3a8f..8af2faa87 100644 --- a/src/s6/usr/local/bin/docker-php-serversideup-s6-install +++ b/src/s6/usr/local/bin/docker-php-serversideup-s6-install @@ -9,7 +9,7 @@ set -oue # Be sure to set the S6_SRC_URL, S6_SRC_DEP, and S6_DIR # environment variables before running this script. -S6_VERSION=v3.2.0.2 +S6_VERSION=v3.2.1.0 mkdir -p $S6_DIR export SYS_ARCH=$(uname -m) case "$SYS_ARCH" in From 3a03547df94490b5fc956be5d8ee8cf81657a3b2 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 20 Jun 2025 15:04:16 -0500 Subject: [PATCH 038/304] Add support for skipping database connection checks during Laravel migrations - Introduced the `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK` environment variable to allow users to bypass database connection checks before running migrations. - Updated the documentation to reflect this new environment variable and its default behavior. --- .../docs/7.reference/1.environment-variable-specification.md | 1 + src/common/etc/entrypoint.d/50-laravel-automations.sh | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 0e591a2bd..a2b046f34 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -27,6 +27,7 @@ We like to customize our images on a per app basis using environment variables. `AUTORUN_LARAVEL_EVENT_CACHE`
*Default: "true"*|Automatically run "php artisan event:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION`
*Default: "true"*|Automatically run `php artisan migrate --force` on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION_ISOLATION`
*Default: "false"*|Requires Laravel v9.38.0 or higher and a database that supports table locks. Automatically run `php artisan migrate --force --isolated` on container start.

ℹ️ Requires `AUTORUN_ENABLED = true` to run.
ℹ️ Does not work with SQLite.| all +`AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK`
*Default: "false"*|Skip the database connection check before running migrations.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION_TIMEOUT`
*Default: "30"*|The number of seconds to wait for the database to come online before attempting `php artisan migrate`..
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_ROUTE_CACHE`
*Default: "true"*|Automatically run "php artisan route:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_STORAGE_LINK`
*Default: "true"*|Automatically run "php artisan storage:link" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index f60af69d1..5ec637c2f 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -23,6 +23,7 @@ script_name="laravel-automations" : "${AUTORUN_LARAVEL_MIGRATION:=true}" : "${AUTORUN_LARAVEL_MIGRATION_ISOLATION:=false}" : "${AUTORUN_LARAVEL_MIGRATION_TIMEOUT:=30}" +: "${AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK:=false}" # Set default values for seeders : "${AUTORUN_LARAVEL_SEED:=false}" @@ -274,6 +275,10 @@ laravel_version_is_at_least() { } test_db_connection() { + if [ "$AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK" = "true" ]; then + return 0 + fi + php -r " require '$APP_BASE_DIR/vendor/autoload.php'; use Illuminate\Support\Facades\DB; From 2029bfafcb0711f81a9b0c287b67032658a46570 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 4 Aug 2025 10:44:51 -0500 Subject: [PATCH 039/304] Update php-fpm-healthcheck version to 0.6.0 in installation script --- src/s6/usr/local/bin/docker-php-serversideup-s6-install | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/s6/usr/local/bin/docker-php-serversideup-s6-install b/src/s6/usr/local/bin/docker-php-serversideup-s6-install index 8af2faa87..5dd2ea387 100644 --- a/src/s6/usr/local/bin/docker-php-serversideup-s6-install +++ b/src/s6/usr/local/bin/docker-php-serversideup-s6-install @@ -10,6 +10,7 @@ set -oue # environment variables before running this script. S6_VERSION=v3.2.1.0 +PHP_FPM_HEALTHCHECK_VERSION=v0.6.0 mkdir -p $S6_DIR export SYS_ARCH=$(uname -m) case "$SYS_ARCH" in @@ -34,5 +35,5 @@ untar ${S6_SRC_URL}/${S6_VERSION}/s6-overlay-${S6_ARCH}.tar.xz # Ensure "php-fpm-healthcheck" is installed echo "⬇️ Downloading php-fpm-healthcheck..." -curl -o /usr/local/bin/php-fpm-healthcheck https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/v0.5.0/php-fpm-healthcheck +curl -o /usr/local/bin/php-fpm-healthcheck https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/${PHP_FPM_HEALTHCHECK_VERSION}/php-fpm-healthcheck chmod +x /usr/local/bin/php-fpm-healthcheck \ No newline at end of file From 6eca49853f11f06562674bee1052e9e2b8da62f0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 4 Aug 2025 11:32:49 -0500 Subject: [PATCH 040/304] Enhance get-php-versions.sh with DockerHub validation and fallback mechanism - Added functionality to validate PHP versions against DockerHub. - Implemented a fallback to the previous patch version if the current version is unavailable. - Introduced a new option `--skip-dockerhub-validation` to bypass validation for testing. - Updated usage documentation to reflect new features and options. --- scripts/get-php-versions.sh | 193 +++++++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 2 deletions(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 9638e18f8..ac89505ce 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -1,14 +1,27 @@ #!/bin/bash ################################################### -# Usage: get-php-versions.sh [--skip-download] +# Usage: get-php-versions.sh [--skip-download] [--skip-dockerhub-validation] ################################################### # This file takes the official latest PHP releases from php.net merges them with our # "base php configuration". These files get merged into a final file called "php-versions.yml" # which is used to build our GitHub Actions jobs. # +# 🔍 DOCKERHUB VALIDATION & FALLBACK +# By default, this script validates that each PHP version from php.net is actually available +# on DockerHub before including it in the final configuration. If a version is not available: +# 1. The script attempts to fall back to the previous patch version (e.g., 8.3.24 -> 8.3.23) +# 2. A GitHub Actions warning is displayed explaining the fallback +# 3. If the fallback version is also unavailable, the script exits with an error +# +# This ensures that Docker builds won't fail due to non-existent base images. +# # 👉 REQUIRED FILES # - BASE_PHP_VERSIONS_CONFIG_FILE must be valid and set to a valid file path # (defaults to scripts/conf/php-versions-base-config.yml) +# +# 👉 OPTIONS +# --skip-download: Skip downloading from php.net and use existing base config +# --skip-dockerhub-validation: Skip DockerHub validation (useful for testing/development) set -oue pipefail @@ -16,13 +29,123 @@ set -oue pipefail # set -x # trap read DEBUG +########################## +# DockerHub API Functions + +# Check if a PHP version exists on DockerHub +check_dockerhub_php_version() { + local version="$1" + local variant="${2:-cli}" + local os="${3:-}" + + local image_tag + if [ -n "$os" ] && [ "$os" != "bullseye" ] && [ "$os" != "bookworm" ]; then + image_tag="${version}-${variant}-${os}" + else + image_tag="${version}-${variant}" + fi + + # Use Docker Hub API v2 to check if the tag exists with timeout and retry + local response + local max_retries=3 + local retry_count=0 + + while [ $retry_count -lt $max_retries ]; do + response=$(curl -s --max-time 10 --connect-timeout 5 \ + -o /dev/null -w "%{http_code}" \ + "https://registry.hub.docker.com/v2/repositories/library/php/tags/${image_tag}/") + + # Check if we got a valid HTTP response + if [ "$response" = "200" ]; then + return 0 # Version exists + elif [ "$response" = "404" ]; then + return 1 # Version definitely does not exist + else + # Network error or other issue, retry + retry_count=$((retry_count + 1)) + if [ $retry_count -lt $max_retries ]; then + echo_color_message yellow "⚠️ DockerHub API request failed (HTTP $response), retrying in 2 seconds..." + sleep 2 + fi + fi + done + + # If we get here, all retries failed + echo_color_message red "❌ Failed to check DockerHub after $max_retries attempts for $image_tag" + return 1 +} + +# Get previous patch version (e.g., 8.3.24 -> 8.3.23) +get_previous_patch_version() { + local version="$1" + local major_minor patch + + # Split version into major.minor and patch + major_minor=$(echo "$version" | cut -d'.' -f1-2) + patch=$(echo "$version" | cut -d'.' -f3) + + # Decrement patch version + if [ "$patch" -gt 0 ]; then + patch=$((patch - 1)) + echo "${major_minor}.${patch}" + else + # If patch is 0, we can't go lower + return 1 + fi +} + +# Validate and potentially fallback a PHP version +validate_php_version_with_fallback() { + local version="$1" + local original_version="$version" + local fallback_attempted=false + + echo_color_message yellow "🔍 Checking PHP version $version on DockerHub..." >&2 + + # Check if the version exists on DockerHub (using cli variant as reference) + if check_dockerhub_php_version "$version" "cli"; then + echo_color_message green "✅ PHP $version is available on DockerHub" >&2 + echo "$version" # Output to stdout for capture + return 0 + else + echo_color_message red "❌ PHP $version is not available on DockerHub" >&2 + + # Try to get previous patch version + local fallback_version + if fallback_version=$(get_previous_patch_version "$version"); then + fallback_attempted=true + echo_color_message yellow "⚠️ Attempting fallback to PHP $fallback_version..." >&2 + + if check_dockerhub_php_version "$fallback_version" "cli"; then + echo_color_message yellow "::warning title=PHP Version Fallback::PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." >&2 + echo_color_message green "✅ Fallback successful: Using PHP $fallback_version" >&2 + echo "$fallback_version" # Output to stdout for capture + return 0 + else + echo_color_message red "❌ Fallback version PHP $fallback_version is also not available on DockerHub" >&2 + fi + fi + + # If we get here, both original and fallback failed + if [ "$fallback_attempted" = true ]; then + echo_color_message red "::error title=PHP Version Unavailable::Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." >&2 + else + echo_color_message red "::error title=PHP Version Unavailable::PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." >&2 + fi + + return 1 + fi +} + ########################## # Argument Parsing SKIP_DOWNLOAD="${SKIP_DOWNLOAD:-false}" +SKIP_DOCKERHUB_VALIDATION="${SKIP_DOCKERHUB_VALIDATION:-false}" while [[ "$#" -gt 0 ]]; do case $1 in --skip-download) SKIP_DOWNLOAD=true ;; + --skip-dockerhub-validation) SKIP_DOCKERHUB_VALIDATION=true ;; *) echo "Unknown parameter passed: $1"; exit 1 ;; esac shift @@ -76,8 +199,74 @@ if [ "$SKIP_DOWNLOAD" = false ]; then # Fetch the JSON from the PHP website php_net_version_json=$(curl -s $PHP_VERSIONS_ACTIVE_JSON_FEED) + # Parse the fetched JSON data and optionally validate PHP versions on DockerHub + if [ "$SKIP_DOCKERHUB_VALIDATION" = true ]; then + echo_color_message yellow "⚠️ Skipping DockerHub validation as requested..." + processed_json="$php_net_version_json" + else + echo_color_message yellow "🔍 Parsing and validating PHP versions from php.net..." + + # First, extract versions from the JSON + php_versions_raw=$(echo "$php_net_version_json" | jq -r " + . as \$major | + to_entries[] | + .value | + to_entries[] | + .value.version" | grep -v "null" | sort -u) + + # Create temporary files to store validation results + validated_versions_file=$(mktemp) + version_map_file=$(mktemp) + + # Validate each version + validation_failed=false + while IFS= read -r version; do + if [ -n "$version" ]; then + echo_color_message yellow "🔍 Validating PHP $version..." + # Capture validation result without color codes + if validated_version=$(validate_php_version_with_fallback "$version" 2>/dev/null | tail -n1); then + # Double check that we got a valid version back + if [ -n "$validated_version" ] && [ "$validated_version" != "VALIDATION_FAILED" ]; then + echo "$version:$validated_version" >> "$validated_versions_file" + if [ "$version" != "$validated_version" ]; then + # Escape special characters for sed + escaped_original=$(echo "$version" | sed 's/[[\.*^$(){}?+|/]/\\&/g') + escaped_validated=$(echo "$validated_version" | sed 's/[[\.*^$(){}?+|/]/\\&/g') + echo "s/${escaped_original}/${escaped_validated}/g" >> "$version_map_file" + fi + else + echo_color_message red "❌ Validation failed for PHP $version" + validation_failed=true + fi + else + echo_color_message red "❌ Validation failed for PHP $version" + validation_failed=true + fi + fi + done <<< "$php_versions_raw" + + # Exit if any validation failed + if [ "$validation_failed" = true ]; then + echo_color_message red "❌ One or more PHP versions failed validation. Stopping build." + rm -f "$validated_versions_file" "$version_map_file" + exit 1 + fi + + # Apply version substitutions if any fallbacks were used + processed_json="$php_net_version_json" + if [ -s "$version_map_file" ]; then + echo_color_message yellow "📝 Applying version fallbacks..." + while IFS= read -r substitution; do + processed_json=$(echo "$processed_json" | sed "$substitution") + done < "$version_map_file" + fi + + # Clean up temporary files + rm -f "$validated_versions_file" "$version_map_file" + fi + # Parse the fetched JSON data and transform it to a specific YAML structure using jq and yq. - php_net_yaml_data=$(echo "$php_net_version_json" | jq -r " + php_net_yaml_data=$(echo "$processed_json" | jq -r " { \"php_versions\": [ . as \$major | From 08bb8106b1e743fad40410d444f7ee4c222f7efd Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 4 Aug 2025 11:37:15 -0500 Subject: [PATCH 041/304] Refine PHP version validation in get-php-versions.sh to preserve warnings - Updated the validation process to capture warnings during PHP version validation. - Ensured that the last line of the validation output is retained for further checks. --- scripts/get-php-versions.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index ac89505ce..7606694bc 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -223,8 +223,8 @@ if [ "$SKIP_DOWNLOAD" = false ]; then while IFS= read -r version; do if [ -n "$version" ]; then echo_color_message yellow "🔍 Validating PHP $version..." - # Capture validation result without color codes - if validated_version=$(validate_php_version_with_fallback "$version" 2>/dev/null | tail -n1); then + # Capture validation result while preserving warnings + if validated_version=$(validate_php_version_with_fallback "$version" | tail -n1); then # Double check that we got a valid version back if [ -n "$validated_version" ] && [ "$validated_version" != "VALIDATION_FAILED" ]; then echo "$version:$validated_version" >> "$validated_versions_file" From 205b069dacafbab5cd23f90f6a47f9543a8a9d3c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 4 Aug 2025 11:43:46 -0500 Subject: [PATCH 042/304] Refactor PHP version fallback warnings in get-php-versions.sh - Modified the output of PHP version fallback warnings to be sent directly to GitHub Actions. - Ensured error messages for unavailable PHP versions are also formatted for immediate output. - Updated comments to clarify the behavior of validation result capturing. --- scripts/get-php-versions.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 7606694bc..74e69c8cc 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -117,7 +117,8 @@ validate_php_version_with_fallback() { echo_color_message yellow "⚠️ Attempting fallback to PHP $fallback_version..." >&2 if check_dockerhub_php_version "$fallback_version" "cli"; then - echo_color_message yellow "::warning title=PHP Version Fallback::PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." >&2 + # Output GitHub Actions warning immediately + echo "::warning title=PHP Version Fallback::PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." echo_color_message green "✅ Fallback successful: Using PHP $fallback_version" >&2 echo "$fallback_version" # Output to stdout for capture return 0 @@ -128,9 +129,9 @@ validate_php_version_with_fallback() { # If we get here, both original and fallback failed if [ "$fallback_attempted" = true ]; then - echo_color_message red "::error title=PHP Version Unavailable::Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." >&2 + echo "::error title=PHP Version Unavailable::Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." else - echo_color_message red "::error title=PHP Version Unavailable::PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." >&2 + echo "::error title=PHP Version Unavailable::PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." fi return 1 @@ -223,7 +224,7 @@ if [ "$SKIP_DOWNLOAD" = false ]; then while IFS= read -r version; do if [ -n "$version" ]; then echo_color_message yellow "🔍 Validating PHP $version..." - # Capture validation result while preserving warnings + # Capture validation result (warnings output immediately) if validated_version=$(validate_php_version_with_fallback "$version" | tail -n1); then # Double check that we got a valid version back if [ -n "$validated_version" ] && [ "$validated_version" != "VALIDATION_FAILED" ]; then From b46699c24474fd0eee2635deec42cce27d314ac1 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 4 Aug 2025 11:50:40 -0500 Subject: [PATCH 043/304] Revert change to get-php-versions.sh --- scripts/get-php-versions.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 74e69c8cc..ac89505ce 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -117,8 +117,7 @@ validate_php_version_with_fallback() { echo_color_message yellow "⚠️ Attempting fallback to PHP $fallback_version..." >&2 if check_dockerhub_php_version "$fallback_version" "cli"; then - # Output GitHub Actions warning immediately - echo "::warning title=PHP Version Fallback::PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." + echo_color_message yellow "::warning title=PHP Version Fallback::PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." >&2 echo_color_message green "✅ Fallback successful: Using PHP $fallback_version" >&2 echo "$fallback_version" # Output to stdout for capture return 0 @@ -129,9 +128,9 @@ validate_php_version_with_fallback() { # If we get here, both original and fallback failed if [ "$fallback_attempted" = true ]; then - echo "::error title=PHP Version Unavailable::Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." + echo_color_message red "::error title=PHP Version Unavailable::Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." >&2 else - echo "::error title=PHP Version Unavailable::PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." + echo_color_message red "::error title=PHP Version Unavailable::PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." >&2 fi return 1 @@ -224,8 +223,8 @@ if [ "$SKIP_DOWNLOAD" = false ]; then while IFS= read -r version; do if [ -n "$version" ]; then echo_color_message yellow "🔍 Validating PHP $version..." - # Capture validation result (warnings output immediately) - if validated_version=$(validate_php_version_with_fallback "$version" | tail -n1); then + # Capture validation result without color codes + if validated_version=$(validate_php_version_with_fallback "$version" 2>/dev/null | tail -n1); then # Double check that we got a valid version back if [ -n "$validated_version" ] && [ "$validated_version" != "VALIDATION_FAILED" ]; then echo "$version:$validated_version" >> "$validated_versions_file" From 7e8beaa7503b768c9b7196dba3576a94d24a908f Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 4 Aug 2025 11:59:07 -0500 Subject: [PATCH 044/304] Add GitHub Actions annotation function to get-php-versions.sh - Introduced a new function to output GitHub Actions workflow commands without color formatting. - Updated PHP version fallback warnings and errors to utilize the new annotation function for direct output. - Refined validation result capturing to ensure proper handling of version checks. --- scripts/get-php-versions.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index ac89505ce..1e71bc697 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -94,6 +94,12 @@ get_previous_patch_version() { fi } +# Add a new function for GitHub Actions annotations (around line 193) +function github_actions_annotation() { + # Output GitHub Actions workflow commands directly without color formatting + echo "$1" >&2 +} + # Validate and potentially fallback a PHP version validate_php_version_with_fallback() { local version="$1" @@ -117,7 +123,8 @@ validate_php_version_with_fallback() { echo_color_message yellow "⚠️ Attempting fallback to PHP $fallback_version..." >&2 if check_dockerhub_php_version "$fallback_version" "cli"; then - echo_color_message yellow "::warning title=PHP Version Fallback::PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." >&2 + # Output GitHub Actions annotation without color formatting + github_actions_annotation "::warning title=PHP Version Fallback::PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." echo_color_message green "✅ Fallback successful: Using PHP $fallback_version" >&2 echo "$fallback_version" # Output to stdout for capture return 0 @@ -128,9 +135,9 @@ validate_php_version_with_fallback() { # If we get here, both original and fallback failed if [ "$fallback_attempted" = true ]; then - echo_color_message red "::error title=PHP Version Unavailable::Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." >&2 + github_actions_annotation "::error title=PHP Version Unavailable::Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." else - echo_color_message red "::error title=PHP Version Unavailable::PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." >&2 + github_actions_annotation "::error title=PHP Version Unavailable::PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." fi return 1 @@ -224,7 +231,7 @@ if [ "$SKIP_DOWNLOAD" = false ]; then if [ -n "$version" ]; then echo_color_message yellow "🔍 Validating PHP $version..." # Capture validation result without color codes - if validated_version=$(validate_php_version_with_fallback "$version" 2>/dev/null | tail -n1); then + if validated_version=$(validate_php_version_with_fallback "$version" | tail -n1); then # Double check that we got a valid version back if [ -n "$validated_version" ] && [ "$validated_version" != "VALIDATION_FAILED" ]; then echo "$version:$validated_version" >> "$validated_versions_file" From 4cbf04c8477e41d3f588f51377ae2cfb5845a4e9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 4 Aug 2025 12:17:01 -0500 Subject: [PATCH 045/304] Fix output of GitHub Actions annotation function in get-php-versions.sh - Adjusted the echo command in the github_actions_annotation function to remove redirection to stderr, ensuring proper output formatting for GitHub Actions. --- scripts/get-php-versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 1e71bc697..06b0b5dfa 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -97,7 +97,7 @@ get_previous_patch_version() { # Add a new function for GitHub Actions annotations (around line 193) function github_actions_annotation() { # Output GitHub Actions workflow commands directly without color formatting - echo "$1" >&2 + echo "$1" } # Validate and potentially fallback a PHP version From 8a0b41c481f6484f9e2f9e656f546a5299726092 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 4 Aug 2025 12:25:32 -0500 Subject: [PATCH 046/304] Cleaner method for GitHub annotation warnings --- scripts/get-php-versions.sh | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 06b0b5dfa..2cdf2b5b3 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -96,8 +96,22 @@ get_previous_patch_version() { # Add a new function for GitHub Actions annotations (around line 193) function github_actions_annotation() { - # Output GitHub Actions workflow commands directly without color formatting - echo "$1" + local type="$1" # warning, error, notice, debug + local title="$2" # The title + local message="$3" # The message + + # Output the official workflow command format + echo "::${type} title=${title}::${message}" + + # Add to step summary for better visibility + case "$type" in + warning) + echo "⚠️ **Warning: ${title}** - ${message}" >> $GITHUB_STEP_SUMMARY + ;; + error) + echo "❌ **Error: ${title}** - ${message}" >> $GITHUB_STEP_SUMMARY + ;; + esac } # Validate and potentially fallback a PHP version @@ -124,7 +138,7 @@ validate_php_version_with_fallback() { if check_dockerhub_php_version "$fallback_version" "cli"; then # Output GitHub Actions annotation without color formatting - github_actions_annotation "::warning title=PHP Version Fallback::PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." + github_actions_annotation "warning" "PHP Version Fallback" "PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." echo_color_message green "✅ Fallback successful: Using PHP $fallback_version" >&2 echo "$fallback_version" # Output to stdout for capture return 0 @@ -135,9 +149,9 @@ validate_php_version_with_fallback() { # If we get here, both original and fallback failed if [ "$fallback_attempted" = true ]; then - github_actions_annotation "::error title=PHP Version Unavailable::Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." + github_actions_annotation "error" "PHP Version Unavailable" "Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." else - github_actions_annotation "::error title=PHP Version Unavailable::PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." + github_actions_annotation "error" "PHP Version Unavailable" "PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." fi return 1 From 55c03c5d481148cd91006407ee091f66d225eb9b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 19 Aug 2025 14:40:33 -0500 Subject: [PATCH 047/304] Disable warning about secrets because they don't have any secrets to be concerned about --- src/variations/fpm-apache/Dockerfile | 1 + src/variations/fpm-nginx/Dockerfile | 1 + src/variations/unit/Dockerfile | 1 + 3 files changed, 3 insertions(+) diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index e6c4cdcf3..7d39b20ac 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -1,3 +1,4 @@ +# check=skip=SecretsUsedInArgOrEnv ARG BASE_OS_VERSION='bookworm' ARG PHP_VERSION='8.4' ARG PHP_VARIATION='fpm-apache' diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index a8197b6df..e13625c74 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -1,3 +1,4 @@ +# check=skip=SecretsUsedInArgOrEnv ARG BASE_OS_VERSION='bookworm' ARG PHP_VERSION='8.4' ARG BASE_IMAGE="php:${PHP_VERSION}-fpm-${BASE_OS_VERSION}" diff --git a/src/variations/unit/Dockerfile b/src/variations/unit/Dockerfile index 88aa98f08..41cc5fb99 100644 --- a/src/variations/unit/Dockerfile +++ b/src/variations/unit/Dockerfile @@ -1,3 +1,4 @@ +# check=skip=SecretsUsedInArgOrEnv ARG BASE_OS_VERSION='bookworm' ARG PHP_VERSION='8.4' ARG PHP_VARIATION='unit' From d344d53c4de3cbebd205e94cf8e6281e7040c5b7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 20 Aug 2025 18:39:41 -0500 Subject: [PATCH 048/304] Add "Professionally Supported" section to README with service options for Docker and PHP integration --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 899dd531c..76dfcecad 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,13 @@ Experience the ***true difference*** of using these images vs the other options +## Professionally Supported +Are you looking for help on integreating Docker with your PHP application? We have multiple options to help your team out: + +- [Get Managed Hosting](https://serversideup.net/hire-us/): CI/CD design and engineering, managed hosting, guaranteed uptime, any host, any server. +- [Get Professional Help](https://schedule.serversideup.net/team/serversideup/quick-chat-with-jay): Get video + screen-sharing help directly from the core contributors. +- [Get a Full-Stack Development Team](https://serversideup.net/hire-us/): We can build your app from the ground up, or help you with your existing codebase. + ## Usage This repository creates a number of Docker image variations, allowing you to choose exactly what you need. From e4ff084d929b5d299992602bb088b28eb33bc098 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 20 Aug 2025 20:03:43 -0500 Subject: [PATCH 049/304] Add script to fetch latest NGINX versions for different operating systems and update configuration file with new OS versions --- scripts/conf/php-versions-base-config.yml | 36 ++++ scripts/view-nginx-versions.sh | 207 ++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100755 scripts/view-nginx-versions.sh diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index e2ea3e695..5e379a872 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -58,3 +58,39 @@ php_variations: supported_os: # Alpine with Unit is not supported yet. Submit a PR if you can help (https://github.com/serversideup/docker-php/issues/233) - bullseye - bookworm + +operating_systems: + - family: alpine + versions: + - name: "Alpine 3.16" + version: alpine3.16 + nginx_version: 1.26.1-r2 + - name: "Alpine 3.17" + version: alpine3.17 + nginx_version: 1.26.1-r2 + - name: "Alpine 3.18" + version: alpine3.18 + nginx_version: 1.28.0-r1 + - name: "Alpine 3.19" + version: alpine3.19 + nginx_version: 1.28.0-r1 + - name: "Alpine 3.20" + version: alpine3.20 + nginx_version: 1.28.0-r1 + - name: "Alpine 3.21" + version: alpine3.21 + nginx_version: 1.28.0-r1 + - name: "Alpine 3.22" + version: alpine3.22 + nginx_version: 1.28.0-r1 + - family: debian + versions: + - name: "Debian Bullseye" + version: bullseye + nginx_version: 1.28.0-1~bullseye + - name: "Debian Bookworm" + version: bookworm + nginx_version: 1.28.0-1~bookworm + - name: "Debian Trixie" + version: trixie + nginx_version: 1.28.0-1~trixie \ No newline at end of file diff --git a/scripts/view-nginx-versions.sh b/scripts/view-nginx-versions.sh new file mode 100755 index 000000000..0f9abf2a3 --- /dev/null +++ b/scripts/view-nginx-versions.sh @@ -0,0 +1,207 @@ +#!/bin/bash +################################################### +# Usage: view-nginx-versions.sh [--os ] +################################################### +# This script fetches the latest NGINX versions available for different +# operating systems from the official NGINX repositories. By default, it +# shows all operating systems, but you can filter to a specific OS. + +set -oe pipefail + +########################## +# Configuration +os_config() { + # Resolve config path relative to this script so --help works before SCRIPT_DIR is set + local this_script_dir + this_script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + local config_file="$this_script_dir/conf/php-versions-base-config.yml" + + if ! command -v yq >/dev/null 2>&1; then + echo "yq is required but not found. Install 'yq' (https://github.com/mikefarah/yq) to continue." 1>&2 + return 1 + fi + if ! command -v jq >/dev/null 2>&1; then + echo "jq is required but not found. Install 'jq' to continue." 1>&2 + return 1 + fi + + yq -r '.operating_systems[] | .family as $f | .versions[] | "\(.version)|\($f)|\(.name)"' "$config_file" \ + | while IFS='|' read -r version family name; do + if [[ "$family" == "alpine" ]]; then + # version comes as alpineX.Y (e.g., alpine3.20) + key="$version" + alpine_num_version="${version#alpine}" + url="http://nginx.org/packages/alpine/v${alpine_num_version}/main/x86_64/" + pattern='nginx-[0-9][^"\n]*\.apk' + else + key="$version" + url="http://nginx.org/packages/debian/dists/${version}/nginx/binary-amd64/Packages" + pattern='^Package: nginx$' + fi + printf '%s|%s|%s|%s\n' "$key" "$name" "$url" "$pattern" + done +} + +########################## +# Functions + +help_menu() { + echo "Usage: $0 [--os ]" + echo + echo "This script fetches the latest NGINX versions available for different" + echo "operating systems from the official NGINX repositories." + echo + echo "Options:" + echo " --os Filter to a specific operating system" + echo " --help, -h Show this help message" + echo + echo "Available Operating Systems:" + os_config | awk -F'|' '{printf " %-12s %s\n", $1, $2}' + echo + echo "Examples:" + echo " $0 # Show all operating systems" + echo " $0 --os alpine3.20 # Show only Alpine 3.20" + echo " $0 --os bookworm # Show only Debian Bookworm" +} + +########################## +# Argument Parsing + +FILTER_OS="" +while [[ "$#" -gt 0 ]]; do + case $1 in + --os) + FILTER_OS="$2" + shift 2 + ;; + --help|-h) + help_menu + exit 0 + ;; + *) + echo "Unknown parameter passed: $1" + help_menu + exit 1 + ;; + esac +done + +########################## +# Environment Settings + +# Script Configurations +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +# UI Colors +function ui_set_yellow { + printf $'\033[0;33m' +} + +function ui_set_green { + printf $'\033[0;32m' +} + +function ui_set_red { + printf $'\033[0;31m' +} + +function ui_set_blue { + printf $'\033[0;34m' +} + +function ui_set_bold { + printf $'\033[1m' +} + +function ui_reset_colors { + printf "\e[0m" +} + +function echo_color_message (){ + color=$1 + message=$2 + + ui_set_$color + echo "$message" + ui_reset_colors +} + +########################## +# Fetch helpers + +get_alpine_version() { + local url="$1" + local pattern="$2" + + local version=$(curl -s "$url" | grep -o "$pattern" | sort -V | tail -1) + if [[ -n "$version" ]]; then + # Extract version number from package name (e.g., nginx-1.24.0-r7.apk -> 1.24.0-r7) + echo "$version" | sed 's/nginx-\(.*\)\.apk/\1/' + else + echo "Unable to fetch" + fi +} + +get_debian_version() { + local url="$1" + + local version=$(curl -s "$url" \ + | awk 'BEGIN{RS=""; FS="\n"} { pkg=0; ver=""; for (i=1;i<=NF;i++){ if ($i ~ /^Package: nginx$/) pkg=1; if ($i ~ /^Version:/){ split($i,a,": *"); ver=a[2]; } } if (pkg && ver!="") print ver; }' \ + | sort -V | tail -1) + if [[ -n "$version" ]]; then + echo "$version" + else + echo "Unable to fetch" + fi +} + +fetch_nginx_version() { + local os_key="$1" + local os_name="$2" + local url="$3" + local pattern="$4" + + echo_color_message blue "🔍 Fetching NGINX version for $os_name from $url..." + + local version="" + if [[ "$url" == *"alpine"* ]]; then + version=$(get_alpine_version "$url" "$pattern") + else + version=$(get_debian_version "$url") + fi + + ui_set_bold + ui_set_green + printf "%-20s" "$os_name:" + ui_reset_colors + echo " $version" +} + +########################## +# Main script starts here + +echo_color_message yellow "🌐 NGINX Version Checker" +echo + +# If a specific OS is requested, validate it exists +if [[ -n "$FILTER_OS" ]]; then + if ! grep -q "^$FILTER_OS|" < <(os_config); then + echo_color_message red "❌ Unknown operating system: $FILTER_OS" + echo + help_menu + exit 1 + fi +fi + +# Process operating systems +os_config | while IFS='|' read -r os_key os_name url pattern; do + # Skip if filtering and this isn't the requested OS + if [[ -n "$FILTER_OS" && "$os_key" != "$FILTER_OS" ]]; then + continue + fi + + fetch_nginx_version "$os_key" "$os_name" "$url" "$pattern" +done + +echo +echo_color_message green "✅ NGINX version check complete!" \ No newline at end of file From a20fc49808462a76358e914c342f03f107ff5a5c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 20 Aug 2025 20:07:32 -0500 Subject: [PATCH 050/304] Update view-nginx-versions.sh script comments for clarity on OS filtering options --- scripts/view-nginx-versions.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/view-nginx-versions.sh b/scripts/view-nginx-versions.sh index 0f9abf2a3..30a9bb207 100755 --- a/scripts/view-nginx-versions.sh +++ b/scripts/view-nginx-versions.sh @@ -4,7 +4,8 @@ ################################################### # This script fetches the latest NGINX versions available for different # operating systems from the official NGINX repositories. By default, it -# shows all operating systems, but you can filter to a specific OS. +# shows all operating systems from the base config file, but you can filter +# to a specific OS if needed. set -oe pipefail From 831e272cfac0fa2888faab9611cdfe7cc553e1f2 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 20 Aug 2025 20:07:59 -0500 Subject: [PATCH 051/304] Update view-nginx-versions.sh help menu to clarify source of NGINX version information --- scripts/view-nginx-versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/view-nginx-versions.sh b/scripts/view-nginx-versions.sh index 30a9bb207..4ca74640b 100755 --- a/scripts/view-nginx-versions.sh +++ b/scripts/view-nginx-versions.sh @@ -50,7 +50,7 @@ help_menu() { echo "Usage: $0 [--os ]" echo echo "This script fetches the latest NGINX versions available for different" - echo "operating systems from the official NGINX repositories." + echo "operating systems from the PHP base config file." echo echo "Options:" echo " --os Filter to a specific operating system" From 178174382006e9049fd5c2d0a5c2ae91ff248be9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 20 Aug 2025 20:54:24 -0500 Subject: [PATCH 052/304] Add functionality to determine and add family alias tags for Docker images based on the latest OS within the build minor. Update PHP versions configuration to include new base OS versions and variations. --- scripts/assemble-docker-tags.sh | 65 ++++++++++++++++++++- scripts/conf/php-versions-base-config.yml | 70 +++++++++++++++-------- scripts/get-php-versions.sh | 4 +- 3 files changed, 110 insertions(+), 29 deletions(-) diff --git a/scripts/assemble-docker-tags.sh b/scripts/assemble-docker-tags.sh index 60d102eff..675335f08 100755 --- a/scripts/assemble-docker-tags.sh +++ b/scripts/assemble-docker-tags.sh @@ -132,6 +132,23 @@ function is_default_base_os() { [[ "$build_base_os" == "$default_base_os_within_build_minor" ]] } +function is_latest_family_os_for_build_minor() { + [[ "$build_base_os" == "$latest_family_os_within_build_minor" ]] +} + +add_family_alias_if_latest() { + # Emits a family alias tag if the current build base OS is the latest within its family for this minor + local docker_tag_suffix=$1 + if is_latest_family_os_for_build_minor; then + if [[ "$docker_tag_suffix" == *"-$build_base_os" ]]; then + local family_tag_suffix="${docker_tag_suffix%$build_base_os}$build_family" + add_docker_tag "$family_tag_suffix" + elif [[ "$docker_tag_suffix" == "$build_base_os" ]]; then + add_docker_tag "$build_family" + fi + fi +} + function ci_release_is_production_launch() { [[ -z "$DOCKER_TAG_PREFIX" && "$RELEASE_TYPE" == "latest" ]] } @@ -226,9 +243,42 @@ build_minor_version="${build_patch_version%.*}" latest_global_stable_major=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r '[.php_versions[] | select(.major | test("-rc") | not) | .major | tonumber] | max | tostring') latest_global_stable_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg latest_global_stable_major "$latest_global_stable_major" '.php_versions[] | select(.major == $latest_global_stable_major) | .minor_versions | map(select(.minor | test("-rc") | not) | .minor | split(".") | .[1] | tonumber) | max | $latest_global_stable_major + "." + tostring') latest_minor_within_build_major=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_major "$build_major_version" '.php_versions[] | select(.major == $build_major) | .minor_versions | map(select(.minor | test("-rc") | not) | .minor | split(".") | .[1] | tonumber) | max | $build_major + "." + tostring') -latest_patch_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" '.php_versions[] | .minor_versions[] | select(.minor == $build_minor) | .patch_versions | map( split(".") | map(tonumber) ) | max | join(".")') -latest_patch_global=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r '[.php_versions[] | .minor_versions[] | select(.minor | test("-rc") | not) | .patch_versions[] | select(test("-rc") | not) | split(".") | map(tonumber) ] | max | join(".")') -default_base_os_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" '.php_versions[] | .minor_versions[] | select(.minor == $build_minor) | .base_os[] | select(.default == true) | .name') +latest_patch_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" '.php_versions[] | .minor_versions[] | select(.minor == $build_minor) | (.patch_versions // []) | map( split(".") | map(tonumber) ) | max | join(".")') +latest_patch_global=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r '[.php_versions[] | .minor_versions[] | select(.minor | test("-rc") | not) | ((.patch_versions // [])[]) | select(test("-rc") | not) | split(".") | map(tonumber) ] | max | join(".")') +# Determine default base OS within the build minor using operating_systems default family and highest available version +default_base_os_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" ' + . as $root + | ($root.operating_systems[] | select(.default == true) | .family) as $defaultFamily + | ($root.operating_systems[] | select(.family == $defaultFamily) | .versions) as $familyVersions + | ($root.php_versions[] + | .minor_versions[] + | select(.minor == $build_minor) + | .base_os + | map(.name)) as $minorBaseOs + | $familyVersions + | map(select(.version as $v | $minorBaseOs | index($v))) + | max_by(.number) + | .version') + +# Determine the build family (alpine/debian) for the selected base OS +build_family=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg base_os "$build_base_os" ' + .operating_systems[] + | select(([.versions[] | .version] | index($base_os)) != null) + | .family' | head -n1) + +# Determine the latest OS within this family for the current minor +latest_family_os_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" --arg family "$build_family" ' + . as $root + | ($root.operating_systems[] | select(.family == $family) | .versions) as $familyVersions + | ($root.php_versions[] + | .minor_versions[] + | select(.minor == $build_minor) + | .base_os + | map(.name)) as $minorBaseOs + | $familyVersions + | map(select(.version as $v | $minorBaseOs | index($v))) + | max_by(.number) + | .version') check_vars \ "🚨 Missing critical build variable. Check the script logic and logs" \ @@ -251,11 +301,14 @@ echo "Latest Global Minor Version: $latest_global_stable_minor" echo "Latest Minor Version within Build Major: $latest_minor_within_build_major" echo "Latest Patch Version within Build Minor: $latest_patch_within_build_minor" echo "Default Base OS within Build Minor: $default_base_os_within_build_minor" +echo "Build Family: $build_family" +echo "Latest Family OS within Build Minor: $latest_family_os_within_build_minor" echo "Latest Global Patch Version: $latest_patch_global" # Set default tag DOCKER_TAGS="" add_docker_tag "$build_patch_version-$build_variation-$build_base_os" +add_family_alias_if_latest "$build_patch_version-$build_variation-$build_base_os" if is_default_base_os; then add_docker_tag "$build_patch_version-$build_variation" @@ -263,6 +316,7 @@ fi if is_latest_stable_patch_within_build_minor; then add_docker_tag "$build_minor_version-$build_variation-$build_base_os" + add_family_alias_if_latest "$build_minor_version-$build_variation-$build_base_os" if is_default_base_os; then add_docker_tag "$build_minor_version-$build_variation" @@ -270,6 +324,7 @@ if is_latest_stable_patch_within_build_minor; then if is_default_variation; then add_docker_tag "$build_minor_version-$build_base_os" + add_family_alias_if_latest "$build_minor_version-$build_base_os" fi if is_default_base_os && is_default_variation; then @@ -278,6 +333,7 @@ if is_latest_stable_patch_within_build_minor; then if is_latest_minor_within_build_major; then add_docker_tag "$build_major_version-$build_variation-$build_base_os" + add_family_alias_if_latest "$build_major_version-$build_variation-$build_base_os" if is_default_base_os; then add_docker_tag "$build_major_version-$build_variation" @@ -285,6 +341,7 @@ if is_latest_stable_patch_within_build_minor; then if is_default_variation; then add_docker_tag "$build_major_version-$build_base_os" + add_family_alias_if_latest "$build_major_version-$build_base_os" fi if is_default_base_os && is_default_variation; then @@ -294,9 +351,11 @@ if is_latest_stable_patch_within_build_minor; then if is_latest_global_patch; then add_docker_tag "$build_variation-$build_base_os" + add_family_alias_if_latest "$build_variation-$build_base_os" if is_default_variation; then add_docker_tag "$build_base_os" + add_family_alias_if_latest "$build_base_os" fi if is_default_base_os; then diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 5e379a872..6a249370a 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -1,96 +1,116 @@ +php_variations: + - name: cli + default: true + - name: fpm + - name: fpm-apache + supported_os: # Open a discussion on serversideup/php if you want to see Alpine support for fpm-apache (https://github.com/serversideup/docker-php/discussions/66) + - bullseye + - bookworm + - trixie + - name: fpm-nginx + - name: unit + supported_os: # Alpine with Unit is not supported yet. Submit a PR if you can help (https://github.com/serversideup/docker-php/issues/233) + - bullseye + - bookworm + - trixie + php_versions: - major: "7" minor_versions: - minor: "7.4" base_os: - - name: alpine + - name: alpine3.16 - name: bullseye - default: true patch_versions: - 7.4.33 - major: "8" minor_versions: - minor: "8.0" base_os: - - name: alpine + - name: alpine3.16 - name: bullseye - default: true patch_versions: - 8.0.30 - minor: "8.1" base_os: - - name: alpine + - name: alpine3.20 + - name: alpine3.21 + - name: alpine3.22 - name: bookworm - default: true + - name: trixie patch_versions: # - 8.1.28 # Pull latest from Official PHP source - minor: "8.2" base_os: - - name: alpine + - name: alpine3.20 + - name: alpine3.21 + - name: alpine3.22 - name: bookworm - default: true + - name: trixie patch_versions: # - 8.2.18 # Pull latest from Official PHP source - minor: "8.3" base_os: - - name: alpine + - name: alpine3.20 + - name: alpine3.21 + - name: alpine3.22 - name: bookworm - default: true + - name: trixie patch_versions: # - 8.3.6 # Pull latest from Official PHP source - minor: "8.4" base_os: - - name: alpine + - name: alpine3.20 + - name: alpine3.21 + - name: alpine3.22 - name: bookworm - default: true + - name: trixie patch_versions: # - 8.4.1 # Pull latest from Official PHP source -php_variations: - - name: cli - default: true - - name: fpm - - name: fpm-apache - supported_os: # Open a discussion on serversideup/php if you want to see Alpine support for fpm-apache (https://github.com/serversideup/docker-php/discussions/66) - - bullseye - - bookworm - - name: fpm-nginx - - name: unit - supported_os: # Alpine with Unit is not supported yet. Submit a PR if you can help (https://github.com/serversideup/docker-php/issues/233) - - bullseye - - bookworm operating_systems: - family: alpine versions: - name: "Alpine 3.16" version: alpine3.16 + number: 3.16 nginx_version: 1.26.1-r2 - name: "Alpine 3.17" version: alpine3.17 + number: 3.17 nginx_version: 1.26.1-r2 - name: "Alpine 3.18" version: alpine3.18 + number: 3.18 nginx_version: 1.28.0-r1 - name: "Alpine 3.19" version: alpine3.19 + number: 3.19 nginx_version: 1.28.0-r1 - name: "Alpine 3.20" version: alpine3.20 + number: 3.20 nginx_version: 1.28.0-r1 - name: "Alpine 3.21" version: alpine3.21 + number: 3.21 nginx_version: 1.28.0-r1 - name: "Alpine 3.22" version: alpine3.22 + number: 3.22 nginx_version: 1.28.0-r1 - family: debian + default: true versions: - name: "Debian Bullseye" version: bullseye + number: 11 nginx_version: 1.28.0-1~bullseye - name: "Debian Bookworm" version: bookworm + number: 12 nginx_version: 1.28.0-1~bookworm - name: "Debian Trixie" version: trixie + number: 13 nginx_version: 1.28.0-1~trixie \ No newline at end of file diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 9638e18f8..802a69fab 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -108,6 +108,7 @@ if [ "$SKIP_DOWNLOAD" = false ]; then # Use 'echo' to pass the JSON data to 'jq' merged_json=$(jq -s ' { + php_variations: (.[1].php_variations // []), php_versions: ( .[0].php_versions + .[1].php_versions | group_by(.major) @@ -124,7 +125,8 @@ if [ "$SKIP_DOWNLOAD" = false ]; then ) }) ), - php_variations: (. | map(.php_variations // []) | add) + operating_systems: (.[1].operating_systems // []) + } ' <(echo "$downloaded_and_normalized_json_data") <(echo "$base_json_data")) From 7a7fc6b427e387325413595b3142125932a6a4ed Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 20 Aug 2025 21:35:25 -0500 Subject: [PATCH 053/304] Add auto-resolution for NGINX version in dev.sh for fpm-nginx builds. Ensure yq is installed and validate NGINX version against the PHP versions configuration file. --- scripts/dev.sh | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/scripts/dev.sh b/scripts/dev.sh index d93de3f5e..860a7b402 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -17,6 +17,7 @@ set -oe pipefail # Script Configurations SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" PROJECT_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +BASE_PHP_VERSIONS_CONFIG_FILE="${BASE_PHP_VERSIONS_CONFIG_FILE:-"$SCRIPT_DIR/conf/php-versions-base-config.yml"}" PHP_BUILD_VERSION="" @@ -94,12 +95,20 @@ build_docker_image() { PLATFORM=$(detect_platform) fi + # Assemble build arguments + local build_args=() + build_args+=(--build-arg PHP_VARIATION="$PHP_BUILD_VARIATION") + build_args+=(--build-arg PHP_VERSION="$PHP_BUILD_VERSION") + build_args+=(--build-arg BASE_OS_VERSION="$PHP_BUILD_BASE_OS") + + if [ -n "$NGINX_VERSION" ] && [ "$PHP_BUILD_VARIATION" = "fpm-nginx" ]; then + build_args+=(--build-arg "NGINX_VERSION=$NGINX_VERSION") + fi + docker buildx build \ "${DOCKER_ADDITIONAL_BUILD_ARGS[@]}" \ --platform "$PLATFORM" \ - --build-arg PHP_VARIATION="$PHP_BUILD_VARIATION" \ - --build-arg PHP_VERSION="$PHP_BUILD_VERSION" \ - --build-arg BASE_OS_VERSION="$PHP_BUILD_BASE_OS" \ + "${build_args[@]}" \ --tag "$build_tag" \ --file "$PROJECT_ROOT_DIR/src/variations/$PHP_BUILD_VARIATION/Dockerfile" \ "$PROJECT_ROOT_DIR" @@ -191,4 +200,23 @@ check_vars \ PHP_BUILD_VERSION \ PHP_BUILD_BASE_OS +# Auto-resolve NGINX version for fpm-nginx if not provided +if [ -z "$NGINX_VERSION" ] && [ "$PHP_BUILD_VARIATION" = "fpm-nginx" ]; then + if ! command -v yq >/dev/null 2>&1; then + echo_color_message red "yq is required but not found. Install 'yq' (https://github.com/mikefarah/yq) to continue." + exit 1 + fi + + NGINX_VERSION=$(BASE_OS="$PHP_BUILD_BASE_OS" yq -r '.operating_systems[].versions[] | select(.version == env(BASE_OS)) | .nginx_version' "$BASE_PHP_VERSIONS_CONFIG_FILE") + + if [ -z "$NGINX_VERSION" ] || [ "$NGINX_VERSION" = "null" ]; then + echo_color_message red "❌ Unable to determine NGINX version for OS '$PHP_BUILD_BASE_OS' from $BASE_PHP_VERSIONS_CONFIG_FILE" + echo + echo "Ensure an entry exists under 'operating_systems' with version: $PHP_BUILD_BASE_OS and a valid 'nginx_version' key." + exit 1 + fi + + echo_color_message green "✅ Using NGINX version '$NGINX_VERSION' for OS '$PHP_BUILD_BASE_OS'" +fi + build_docker_image \ No newline at end of file From 99d7bd4fd3298de9679ad7058a7b8cc4139451fd Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 08:16:13 -0500 Subject: [PATCH 054/304] Update NGINX version for Alpine 3.17 to 1.26.2-r1 and ensure proper formatting for Debian Trixie in php-versions-base-config.yml --- scripts/conf/php-versions-base-config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 6a249370a..95c8f1f55 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -78,7 +78,7 @@ operating_systems: - name: "Alpine 3.17" version: alpine3.17 number: 3.17 - nginx_version: 1.26.1-r2 + nginx_version: 1.26.2-r1 - name: "Alpine 3.18" version: alpine3.18 number: 3.18 @@ -113,4 +113,4 @@ operating_systems: - name: "Debian Trixie" version: trixie number: 13 - nginx_version: 1.28.0-1~trixie \ No newline at end of file + nginx_version: 1.28.0-1~trixie From e9d3bb8fdff0d5691d639f669a7edb94e8ea0aa6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 08:16:37 -0500 Subject: [PATCH 055/304] Renamed NGINX script and added --write mode --- ...ginx-versions.sh => get-nginx-versions.sh} | 102 ++++++++++++++---- 1 file changed, 80 insertions(+), 22 deletions(-) rename scripts/{view-nginx-versions.sh => get-nginx-versions.sh} (66%) diff --git a/scripts/view-nginx-versions.sh b/scripts/get-nginx-versions.sh similarity index 66% rename from scripts/view-nginx-versions.sh rename to scripts/get-nginx-versions.sh index 4ca74640b..a57202526 100755 --- a/scripts/view-nginx-versions.sh +++ b/scripts/get-nginx-versions.sh @@ -1,6 +1,6 @@ #!/bin/bash ################################################### -# Usage: view-nginx-versions.sh [--os ] +# Usage: get-nginx-versions.sh [--os ] [--write] ################################################### # This script fetches the latest NGINX versions available for different # operating systems from the official NGINX repositories. By default, it @@ -21,10 +21,7 @@ os_config() { echo "yq is required but not found. Install 'yq' (https://github.com/mikefarah/yq) to continue." 1>&2 return 1 fi - if ! command -v jq >/dev/null 2>&1; then - echo "jq is required but not found. Install 'jq' to continue." 1>&2 - return 1 - fi + # jq is not required; all updates are handled via yq yq -r '.operating_systems[] | .family as $f | .versions[] | "\(.version)|\($f)|\(.name)"' "$config_file" \ | while IFS='|' read -r version family name; do @@ -47,13 +44,14 @@ os_config() { # Functions help_menu() { - echo "Usage: $0 [--os ]" + echo "Usage: $0 [--os ] [--write]" echo echo "This script fetches the latest NGINX versions available for different" echo "operating systems from the PHP base config file." echo echo "Options:" echo " --os Filter to a specific operating system" + echo " --write Write discovered versions back to the base config (yq)" echo " --help, -h Show this help message" echo echo "Available Operating Systems:" @@ -69,12 +67,17 @@ help_menu() { # Argument Parsing FILTER_OS="" +WRITE_MODE=false while [[ "$#" -gt 0 ]]; do case $1 in --os) FILTER_OS="$2" shift 2 ;; + --write) + WRITE_MODE=true + shift 1 + ;; --help|-h) help_menu exit 0 @@ -92,6 +95,7 @@ done # Script Configurations SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +CONFIG_FILE="$SCRIPT_DIR/conf/php-versions-base-config.yml" # UI Colors function ui_set_yellow { @@ -156,26 +160,65 @@ get_debian_version() { fi } -fetch_nginx_version() { - local os_key="$1" - local os_name="$2" - local url="$3" - local pattern="$4" - - echo_color_message blue "🔍 Fetching NGINX version for $os_name from $url..." - +compute_nginx_version() { + local url="$1" + local pattern="$2" + local version="" if [[ "$url" == *"alpine"* ]]; then version=$(get_alpine_version "$url" "$pattern") else version=$(get_debian_version "$url") fi - - ui_set_bold - ui_set_green - printf "%-20s" "$os_name:" - ui_reset_colors - echo " $version" + + echo "$version" +} + +update_config_nginx_version() { + local version_key="$1" # e.g., alpine3.20 or bookworm + local new_nginx_version="$2" + + if [[ -z "$new_nginx_version" || "$new_nginx_version" == "Unable to fetch" ]]; then + echo_color_message red "⚠️ Skipping update for $version_key (no version found)" + return 0 + fi + + # Preserve comments/spacing: perform a targeted line replacement using awk + # Strategy: when we encounter the specific 'version: ' line, the next + # 'nginx_version:' line in that list item will be replaced with the new value, + # while keeping indentation and any spacing after the colon intact. + local tmp_file + tmp_file="$(mktemp)" + + VERSION_KEY="$version_key" NEW_VER="$new_nginx_version" awk ' + BEGIN { target = ENVIRON["VERSION_KEY"]; replacement = ENVIRON["NEW_VER"]; in_block = 0 } + { + # Detect a version line and check if it matches our target value + if ($0 ~ /^[ \t]*version:[ \t]*/) { + line = $0 + gsub(/^[ \t]*version:[ \t]*/, "", line) + # Extract the first token before a space or comment + split(line, parts, /[ \t#]/) + val = parts[1] + in_block = (val == target) ? 1 : 0 + } + + if (in_block && $0 ~ /^[ \t]*nginx_version:[ \t]*/) { + # Capture exact prefix including key and any spaces after the colon + mpos = match($0, /^[ \t]*nginx_version:[ \t]*/) + if (mpos > 0) { + prefix = substr($0, RSTART, RLENGTH) + print prefix replacement + in_block = 0 + next + } + } + + print $0 + } + ' "$CONFIG_FILE" > "$tmp_file" && mv "$tmp_file" "$CONFIG_FILE" + + echo_color_message green "✍️ Updated nginx_version for $version_key -> $new_nginx_version" } ########################## @@ -201,8 +244,23 @@ os_config | while IFS='|' read -r os_key os_name url pattern; do continue fi - fetch_nginx_version "$os_key" "$os_name" "$url" "$pattern" + echo_color_message blue "🔍 Fetching NGINX version for $os_name from $url..." + version=$(compute_nginx_version "$url" "$pattern") + + ui_set_bold + ui_set_green + printf "%-20s" "$os_name:" + ui_reset_colors + echo " $version" + + if [[ "$WRITE_MODE" == true ]]; then + update_config_nginx_version "$os_key" "$version" + fi done echo -echo_color_message green "✅ NGINX version check complete!" \ No newline at end of file +if [[ "$WRITE_MODE" == true ]]; then + echo_color_message green "✅ NGINX version check and write complete!" +else + echo_color_message green "✅ NGINX version check complete!" +fi \ No newline at end of file From a39020658245174f96521658326ed3d92c2b623e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 08:19:40 -0500 Subject: [PATCH 056/304] Add documentation for NGINX version management, including commands to view and update versions using the get-nginx-versions.sh script. --- .../docs/2.getting-started/7.contributing.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/content/docs/2.getting-started/7.contributing.md b/docs/content/docs/2.getting-started/7.contributing.md index 30ddda402..bd8532216 100644 --- a/docs/content/docs/2.getting-started/7.contributing.md +++ b/docs/content/docs/2.getting-started/7.contributing.md @@ -85,6 +85,31 @@ We use GitHub Actions exclusively to publish all of our releases. If the image e See `.github/workflows/action_publish-beta-images.yml` for an example of how we publish our beta images. +## NGINX Versions +We use the official NGINX repos to install the latest version of NGINX for each OS. The version to install is set by a build argument, which is loaded from the `scripts/conf/php-versions-base-config.yml` file. + +To view the current NGINX versions, run the following command: + +::code-panel +--- +label: "View NGINX versions" +--- +```bash +./scripts/get-nginx-versions.sh +``` +:: + +This script will look at the official NGINX repos to find the latest version of NGINX for each OS. If you want to update the version, you can run the script with the `--write` flag. + +::code-panel +--- +label: "Update NGINX versions" +--- +```bash +./scripts/get-nginx-versions.sh --write +``` +:: + ## Helping out If you're really eager to help out, here are a few places to get started: - Help answer questions on [our GitHub Discussions](https://github.com/serversideup/docker-php/discussions) and [our Discord](https://serversideup.net/discord) From 56668934c5789ad14aeeea12fa2e4e70e427a97f Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 08:45:36 -0500 Subject: [PATCH 057/304] Refactor to support installing NGINX from a specific version from the official NGINX repos --- src/variations/fpm-nginx/Dockerfile | 98 ++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index e13625c74..9a5210ecf 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -20,42 +20,88 @@ RUN docker-php-serversideup-s6-install #################### # NGINX Signing Key #################### -FROM php:${PHP_VERSION}-fpm AS nginx-repo-config +FROM php:${PHP_VERSION}-fpm-${BASE_OS_VERSION} AS nginx-repo-config ARG SIGNING_KEY_URL="https://nginx.org/keys/nginx_signing.key" ARG SIGNING_FINGERPRINT="573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62" ARG SIGNING_KEY_OUTPUT_FILE="/usr/share/keyrings/nginx-archive-keyring.gpg" +ARG SIGNING_ALPINE_RSA_PUB_SHA256="03833138bf6288dcb545f7a154af24f5c63b94931fef6b078cd8061204a44327" # copy our scripts COPY --chmod=755 src/common/ / -RUN docker-php-serversideup-dep-install-debian "curl gnupg2 ca-certificates lsb-release debian-archive-keyring" && \ - \ - # Import signing key - curl "$SIGNING_KEY_URL" | gpg --dearmor | tee "$SIGNING_KEY_OUTPUT_FILE" && \ - \ - # Verify signing key - VALID_KEY=$(gpg --dry-run --quiet --no-keyring --import --import-options import-show "$SIGNING_KEY_OUTPUT_FILE" | grep "$SIGNING_FINGERPRINT") && \ - \ - if [ -z "$VALID_KEY" ]; then \ - echo "ERROR: Key did not match signing signature!" && \ +# Install NGINX signing key +RUN \ + if [ -f /etc/debian_version ]; then \ + # Install dependencies + apt-get update && \ + apt-get install -y curl gnupg2 ca-certificates lsb-release debian-archive-keyring && \ + \ + # Create GPG home directory + mkdir -p /root/.gnupg && \ + \ + # Import signing key + curl "$SIGNING_KEY_URL" | gpg --dearmor | tee "$SIGNING_KEY_OUTPUT_FILE" && \ + \ + # Verify signing key + VALID_KEY=$(gpg --dry-run --quiet --no-keyring --import --import-options import-show "$SIGNING_KEY_OUTPUT_FILE" | grep "$SIGNING_FINGERPRINT") && \ + if [ -z "$VALID_KEY" ]; then \ + echo "ERROR: Key did not match signing signature!" && \ + exit 1; \ + fi && \ + \ + # Setup apt repository for stable nginx packages + echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian $(lsb_release -cs) nginx" | tee /etc/apt/sources.list.d/nginx.list && \ + \ + # Setup repository pinning to prefer nginx packages over debian packages + printf "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" > /etc/apt/preferences.d/99nginx && \ + \ + # Create dummy APK files so subsequent unconditional COPY does not fail on Debian + mkdir -p /etc/apk/keys && \ + touch /etc/apk/repositories /etc/apk/keys/nginx_signing.rsa.pub; \ + elif [ -f /etc/alpine-release ]; then \ + # Install prerequisites for Alpine + apk add --no-cache openssl curl ca-certificates && \ + \ + # Set up the APK repository for stable NGINX packages + printf "%s%s%s%s%s\n" \ + "@nginx " \ + "http://nginx.org/packages/alpine/v" \ + "$(egrep -o '^[0-9]+\.[0-9]+' /etc/alpine-release)" \ + "/main" \ + | tee -a /etc/apk/repositories && \ + \ + # Download the NGINX APK RSA repository key + curl -o /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub && \ + \ + # Verify the key by pinning the SHA-256 of the DER-encoded public key. + # Allow multiple hashes (comma-separated) for rotation via build args. + CALC_SHA256=$(openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | sha256sum | awk '{print $1}') && \ + printf "%s" "$SIGNING_ALPINE_RSA_PUB_SHA256" | tr ',' '\n' | grep -qx "$CALC_SHA256" || { \ + echo "ERROR: Alpine NGINX RSA key SHA-256 mismatch. Expected one of [$SIGNING_ALPINE_RSA_PUB_SHA256], got: $CALC_SHA256"; \ + exit 1; \ + } && \ + \ + # Move the key to APK's trusted keys directory + mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/ && \ + \ + # Create dummy Debian files so subsequent unconditional COPY does not fail on Alpine + mkdir -p /usr/share/keyrings /etc/apt/sources.list.d /etc/apt/preferences.d && \ + touch /usr/share/keyrings/nginx-archive-keyring.gpg /etc/apt/sources.list.d/nginx.list /etc/apt/preferences.d/99nginx; \ + else \ + echo "ERROR: Unsupported or undetected OS. Only Debian-based and Alpine-based systems are handled." && \ exit 1; \ - fi && \ - \ - # setup apt repository for stable nginx packages - echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list && \ - \ - # setup repository pinning to prefer nginx packages over debian packages - printf "Package: *\nPin: origin nginx.org\nPin-Priority: 900\n" > /etc/apt/preferences.d/99nginx + fi ########## # FPM-NGINX: Main Image ########## FROM ${BASE_IMAGE} -ARG DEPENDENCY_PACKAGES_ALPINE='fcgi nginx gettext shadow' -ARG DEPENDENCY_PACKAGES_DEBIAN='libfcgi-bin nginx gettext-base procps zip' +ARG DEPENDENCY_PACKAGES_ALPINE='fcgi gettext shadow' +ARG DEPENDENCY_PACKAGES_DEBIAN='libfcgi-bin gettext-base procps zip' ARG DEPENDENCY_PHP_EXTENSIONS='opcache pcntl pdo_mysql pdo_pgsql redis zip' ARG REPOSITORY_BUILD_VERSION='dev' +ARG NGINX_VERSION='1.28.0-1' LABEL org.opencontainers.image.title="serversideup/php (fpm-nginx)" \ org.opencontainers.image.description="Supercharge your PHP experience. Based off the official PHP images, serversideup/php includes pre-configured PHP extensions and settings for enhanced performance and security. Optimized for Laravel and WordPress." \ @@ -125,10 +171,12 @@ COPY --from=s6-build /usr/local/bin/php-fpm-healthcheck /usr/local/bin/php-fpm-h COPY --from=nginx-repo-config /usr/share/keyrings/nginx-archive-keyring.gpg /usr/share/keyrings/ COPY --from=nginx-repo-config /etc/apt/sources.list.d/nginx.list /etc/apt/sources.list.d/ COPY --from=nginx-repo-config /etc/apt/preferences.d/99nginx /etc/apt/preferences.d/ +COPY --from=nginx-repo-config /etc/apk/repositories /etc/apk/repositories +COPY --from=nginx-repo-config /etc/apk/keys/nginx_signing.rsa.pub /etc/apk/keys/ # install pecl extensions, dependencies, and clean up -RUN docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE}" && \ - docker-php-serversideup-dep-install-debian "${DEPENDENCY_PACKAGES_DEBIAN}" && \ +RUN docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE} nginx@nginx=${NGINX_VERSION}" && \ + docker-php-serversideup-dep-install-debian "${DEPENDENCY_PACKAGES_DEBIAN} nginx=${NGINX_VERSION}" && \ docker-php-serversideup-install-php-ext-installer && \ \ # Ensure /var/www/ has the correct permissions @@ -157,10 +205,14 @@ RUN docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE}" & rm -rf /etc/nginx/http.d/ && \ rm /etc/nginx/nginx.conf && \ \ - # Docker doesn't support conditional COPY, so we have to remove the apt directory if we're on Alpine + # Docker doesn't support conditional COPY, so remove OS-specific repo/key artifacts that were copied unconditionally if cat /etc/os-release | grep -qi 'Alpine'; then \ + # On Alpine, remove Debian APT artifacts rm -rf /usr/share/keyrings/ && \ rm -rf /etc/apt/; \ + else \ + # On Debian, remove Alpine APK artifacts + rm -rf /etc/apk/; \ fi # Copy our nginx configurations From 83761e225d8aa9c5bac4841dafc1f0abc410d457 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 08:45:44 -0500 Subject: [PATCH 058/304] Add NGINX repository key verification details for Debian and Alpine, including instructions for hash computation and build arguments for key rotation. --- .../docs/2.getting-started/7.contributing.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/content/docs/2.getting-started/7.contributing.md b/docs/content/docs/2.getting-started/7.contributing.md index bd8532216..54547ea6f 100644 --- a/docs/content/docs/2.getting-started/7.contributing.md +++ b/docs/content/docs/2.getting-started/7.contributing.md @@ -110,6 +110,39 @@ label: "Update NGINX versions" ``` :: +### NGINX repository key verification + +- **Debian (APT)**: We import the official NGINX GPG key from `https://nginx.org/keys/nginx_signing.key` and verify it against a pinned fingerprint via the `SIGNING_FINGERPRINT` build arg. +- **Alpine (APK)**: APK uses a raw RSA public key (`nginx_signing.rsa.pub`). We verify this key by pinning the SHA‑256 of the DER‑encoded public key via the `SIGNING_ALPINE_RSA_PUB_SHA256` build arg. You can provide multiple comma‑separated hashes to support key rotation. + +Compute the Alpine key hash when updating: + +```bash +curl -sS https://nginx.org/keys/nginx_signing.rsa.pub -o /tmp/nginx_signing.rsa.pub +# macOS +openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | shasum -a 256 | awk '{print $1}' +# Linux +openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | sha256sum | awk '{print $1}' +``` + +Then build with the new hash (optionally include the old hash during rotation): + +```bash +docker build \ + --build-arg SIGNING_ALPINE_RSA_PUB_SHA256="," \ + -f src/variations/fpm-nginx/Dockerfile . +``` + +Reference: [Installing NGINX Open Source → Alpine packages](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#prebuilt_alpine). + +Why allow multiple hashes? This is optional, but useful during a short rotation window: + +- Ensure CI builds across branches/runners succeed while the upstream key change propagates. +- Avoid flakes from CDN/caching delays where some environments still see the old key. +- Let you pre-stage the new value before the official switch, then remove the old afterwards. + +If you control all builds centrally and can update quickly, pass a single hash. + ## Helping out If you're really eager to help out, here are a few places to get started: - Help answer questions on [our GitHub Discussions](https://github.com/serversideup/docker-php/discussions) and [our Discord](https://serversideup.net/discord) From 85b4543e4743cfab1d42b58660dfeb48b3acf7c0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 08:45:52 -0500 Subject: [PATCH 059/304] Update NGINX directory paths in docker-php-serversideup-set-file-permissions script to include /var/cache/nginx for improved file permission management. --- .../usr/local/bin/docker-php-serversideup-set-file-permissions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 4139a9f9e..d31c1950a 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -102,7 +102,7 @@ case "$OS" in DIRS="/etc/apache2 /etc/ssl/private /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; nginx) - DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/lib/nginx /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) echo "$script_name: Unsupported SERVICE: $SERVICE" From 238e208a358a0206a7da3477e1cd109c47aa0df5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 08:55:45 -0500 Subject: [PATCH 060/304] Add dockerhub validation --- scripts/get-php-versions.sh | 212 +++++++++++++++++++++++++++++++++++- 1 file changed, 211 insertions(+), 1 deletion(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 802a69fab..bda9d8b03 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -1,14 +1,27 @@ #!/bin/bash ################################################### -# Usage: get-php-versions.sh [--skip-download] +# Usage: get-php-versions.sh [--skip-download] [--skip-dockerhub-validation] ################################################### # This file takes the official latest PHP releases from php.net merges them with our # "base php configuration". These files get merged into a final file called "php-versions.yml" # which is used to build our GitHub Actions jobs. # +# 🔍 DOCKERHUB VALIDATION & FALLBACK +# By default, this script validates that each PHP version from php.net is actually available +# on DockerHub before including it in the final configuration. If a version is not available: +# 1. The script attempts to fall back to the previous patch version (e.g., 8.3.24 -> 8.3.23) +# 2. A GitHub Actions warning is displayed explaining the fallback +# 3. If the fallback version is also unavailable, the script exits with an error +# +# This ensures that Docker builds won't fail due to non-existent base images. +# # 👉 REQUIRED FILES # - BASE_PHP_VERSIONS_CONFIG_FILE must be valid and set to a valid file path # (defaults to scripts/conf/php-versions-base-config.yml) +# +# 👉 OPTIONS +# --skip-download: Skip downloading from php.net and use existing base config +# --skip-dockerhub-validation: Skip DockerHub validation (useful for testing/development) set -oue pipefail @@ -16,13 +29,144 @@ set -oue pipefail # set -x # trap read DEBUG +########################## +# DockerHub API Functions + +# Check if a PHP version exists on DockerHub +check_dockerhub_php_version() { + local version="$1" + local variant="${2:-cli}" + local os="${3:-}" + + local image_tag + if [ -n "$os" ] && [ "$os" != "bullseye" ] && [ "$os" != "bookworm" ]; then + image_tag="${version}-${variant}-${os}" + else + image_tag="${version}-${variant}" + fi + + # Use Docker Hub API v2 to check if the tag exists with timeout and retry + local response + local max_retries=3 + local retry_count=0 + + while [ $retry_count -lt $max_retries ]; do + response=$(curl -s --max-time 10 --connect-timeout 5 \ + -o /dev/null -w "%{http_code}" \ + "https://registry.hub.docker.com/v2/repositories/library/php/tags/${image_tag}/") + + # Check if we got a valid HTTP response + if [ "$response" = "200" ]; then + return 0 # Version exists + elif [ "$response" = "404" ]; then + return 1 # Version definitely does not exist + else + # Network error or other issue, retry + retry_count=$((retry_count + 1)) + if [ $retry_count -lt $max_retries ]; then + echo_color_message yellow "⚠️ DockerHub API request failed (HTTP $response), retrying in 2 seconds..." + sleep 2 + fi + fi + done + + # If we get here, all retries failed + echo_color_message red "❌ Failed to check DockerHub after $max_retries attempts for $image_tag" + return 1 +} + +# Get previous patch version (e.g., 8.3.24 -> 8.3.23) +get_previous_patch_version() { + local version="$1" + local major_minor patch + + # Split version into major.minor and patch + major_minor=$(echo "$version" | cut -d'.' -f1-2) + patch=$(echo "$version" | cut -d'.' -f3) + + # Decrement patch version + if [ "$patch" -gt 0 ]; then + patch=$((patch - 1)) + echo "${major_minor}.${patch}" + else + # If patch is 0, we can't go lower + return 1 + fi +} + +# Add a new function for GitHub Actions annotations (around line 193) +function github_actions_annotation() { + local type="$1" # warning, error, notice, debug + local title="$2" # The title + local message="$3" # The message + + # Output the official workflow command format + echo "::${type} title=${title}::${message}" + + # Add to step summary for better visibility + case "$type" in + warning) + echo "⚠️ **Warning: ${title}** - ${message}" >> $GITHUB_STEP_SUMMARY + ;; + error) + echo "❌ **Error: ${title}** - ${message}" >> $GITHUB_STEP_SUMMARY + ;; + esac +} + +# Validate and potentially fallback a PHP version +validate_php_version_with_fallback() { + local version="$1" + local original_version="$version" + local fallback_attempted=false + + echo_color_message yellow "🔍 Checking PHP version $version on DockerHub..." >&2 + + # Check if the version exists on DockerHub (using cli variant as reference) + if check_dockerhub_php_version "$version" "cli"; then + echo_color_message green "✅ PHP $version is available on DockerHub" >&2 + echo "$version" # Output to stdout for capture + return 0 + else + echo_color_message red "❌ PHP $version is not available on DockerHub" >&2 + + # Try to get previous patch version + local fallback_version + if fallback_version=$(get_previous_patch_version "$version"); then + fallback_attempted=true + echo_color_message yellow "⚠️ Attempting fallback to PHP $fallback_version..." >&2 + + if check_dockerhub_php_version "$fallback_version" "cli"; then + # Output GitHub Actions annotation without color formatting + github_actions_annotation "warning" "PHP Version Fallback" "PHP $original_version is not available on DockerHub. Falling back to PHP $fallback_version. This may indicate that DockerHub has not yet published the latest PHP release. Consider checking DockerHub availability before updating to newer versions." + echo_color_message green "✅ Fallback successful: Using PHP $fallback_version" >&2 + echo "$fallback_version" # Output to stdout for capture + return 0 + else + echo_color_message red "❌ Fallback version PHP $fallback_version is also not available on DockerHub" >&2 + fi + fi + + # If we get here, both original and fallback failed + if [ "$fallback_attempted" = true ]; then + github_actions_annotation "error" "PHP Version Unavailable" "Neither PHP $original_version nor fallback version $fallback_version are available on DockerHub. This suggests a significant lag in DockerHub publishing or a configuration issue. Please check DockerHub manually and consider using a known working version." + else + github_actions_annotation "error" "PHP Version Unavailable" "PHP $original_version is not available on DockerHub and no fallback version could be determined (patch version is 0). Please check DockerHub manually and use a known working version." + fi + + return 1 + fi +} + ########################## # Argument Parsing SKIP_DOWNLOAD="${SKIP_DOWNLOAD:-false}" +SKIP_DOCKERHUB_VALIDATION="${SKIP_DOCKERHUB_VALIDATION:-false}" while [[ "$#" -gt 0 ]]; do case $1 in --skip-download) SKIP_DOWNLOAD=true ;; + --skip-dockerhub-validation) SKIP_DOCKERHUB_VALIDATION=true ;; *) echo "Unknown parameter passed: $1"; exit 1 ;; esac shift @@ -76,6 +220,72 @@ if [ "$SKIP_DOWNLOAD" = false ]; then # Fetch the JSON from the PHP website php_net_version_json=$(curl -s $PHP_VERSIONS_ACTIVE_JSON_FEED) + # Parse the fetched JSON data and optionally validate PHP versions on DockerHub + if [ "$SKIP_DOCKERHUB_VALIDATION" = true ]; then + echo_color_message yellow "⚠️ Skipping DockerHub validation as requested..." + processed_json="$php_net_version_json" + else + echo_color_message yellow "🔍 Parsing and validating PHP versions from php.net..." + + # First, extract versions from the JSON + php_versions_raw=$(echo "$php_net_version_json" | jq -r " + . as \$major | + to_entries[] | + .value | + to_entries[] | + .value.version" | grep -v "null" | sort -u) + + # Create temporary files to store validation results + validated_versions_file=$(mktemp) + version_map_file=$(mktemp) + + # Validate each version + validation_failed=false + while IFS= read -r version; do + if [ -n "$version" ]; then + echo_color_message yellow "🔍 Validating PHP $version..." + # Capture validation result without color codes + if validated_version=$(validate_php_version_with_fallback "$version" | tail -n1); then + # Double check that we got a valid version back + if [ -n "$validated_version" ] && [ "$validated_version" != "VALIDATION_FAILED" ]; then + echo "$version:$validated_version" >> "$validated_versions_file" + if [ "$version" != "$validated_version" ]; then + # Escape special characters for sed + escaped_original=$(echo "$version" | sed 's/[[\.*^$(){}?+|/]/\\&/g') + escaped_validated=$(echo "$validated_version" | sed 's/[[\.*^$(){}?+|/]/\\&/g') + echo "s/${escaped_original}/${escaped_validated}/g" >> "$version_map_file" + fi + else + echo_color_message red "❌ Validation failed for PHP $version" + validation_failed=true + fi + else + echo_color_message red "❌ Validation failed for PHP $version" + validation_failed=true + fi + fi + done <<< "$php_versions_raw" + + # Exit if any validation failed + if [ "$validation_failed" = true ]; then + echo_color_message red "❌ One or more PHP versions failed validation. Stopping build." + rm -f "$validated_versions_file" "$version_map_file" + exit 1 + fi + + # Apply version substitutions if any fallbacks were used + processed_json="$php_net_version_json" + if [ -s "$version_map_file" ]; then + echo_color_message yellow "📝 Applying version fallbacks..." + while IFS= read -r substitution; do + processed_json=$(echo "$processed_json" | sed "$substitution") + done < "$version_map_file" + fi + + # Clean up temporary files + rm -f "$validated_versions_file" "$version_map_file" + fi + # Parse the fetched JSON data and transform it to a specific YAML structure using jq and yq. php_net_yaml_data=$(echo "$php_net_version_json" | jq -r " { From 0f41ccafa8c686a08bda9cdd81422de4d6a7bfe3 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 09:06:27 -0500 Subject: [PATCH 061/304] Implement NGINX version computation for fpm-nginx builds in Docker workflow, enhancing version resolution using yq or awk. Remove unused AWS runner configuration and cache settings. --- .../service_docker-build-and-publish.yml | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 769f88cbe..e0350f269 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -79,11 +79,6 @@ jobs: docker-publish: needs: setup-matrix runs-on: ubuntu-24.04 - ## Use AWS runners - # runs-on: - # - runs-on - # - runner=4cpu-linux-x64 - # - run-id=${{ github.run_id }} strategy: matrix: ${{fromJson(needs.setup-matrix.outputs.php-version-map-json)}} @@ -161,20 +156,33 @@ jobs: echo "REPOSITORY_BUILD_VERSION=git-${SHORT_SHA}-${{ github.run_id }}" >> $GITHUB_ENV fi + - name: Compute NGINX build-arg (only for fpm-nginx) + id: compute_nginx + if: ${{ matrix.php_variation == 'fpm-nginx' }} + 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) + 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 }}") + fi + if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then + echo "Unable to determine NGINX version for OS ${{ matrix.base_os }}" 1>&2 + exit 1 + fi + echo "nginx_arg=NGINX_VERSION=$VERSION" >> $GITHUB_OUTPUT + - name: Build images uses: docker/build-push-action@v6 with: file: src/variations/${{ matrix.php_variation }}/Dockerfile cache-from: type=gha cache-to: type=gha - ## Run-on cache - # cache-from: type=s3,blobs_prefix=cache/${{ github.repository }}/,manifests_prefix=cache/${{ github.repository }}/,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }} - # cache-to: type=s3,blobs_prefix=cache/${{ github.repository }}/,manifests_prefix=cache/${{ github.repository }}/,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }},mode=max build-args: | BASE_OS_VERSION=${{ matrix.base_os }} PHP_VERSION=${{ matrix.patch_version }} PHP_VARIATION=${{ matrix.php_variation }} REPOSITORY_BUILD_VERSION=${{ env.REPOSITORY_BUILD_VERSION }} + ${{ steps.compute_nginx.outputs.nginx_arg }} platforms: | linux/amd64 linux/arm64/v8 From 6e1703a2e7a614c5fb60e7c3a1a7375cff9c874a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 09:10:39 -0500 Subject: [PATCH 062/304] Added Depot CI runners --- .github/workflows/service_docker-build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index e0350f269..04ca2e03f 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -78,7 +78,7 @@ jobs: docker-publish: needs: setup-matrix - runs-on: ubuntu-24.04 + runs-on: depot-ubuntu-24.04-4 strategy: matrix: ${{fromJson(needs.setup-matrix.outputs.php-version-map-json)}} From a711abb0dcae14bfcb8f48eddd19449fad7f660a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 10:14:41 -0500 Subject: [PATCH 063/304] Adjust filtering logic to supported "latest OS" within the "supported OS" filter --- scripts/assemble-docker-tags.sh | 53 ++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/scripts/assemble-docker-tags.sh b/scripts/assemble-docker-tags.sh index 675335f08..4ea8161da 100755 --- a/scripts/assemble-docker-tags.sh +++ b/scripts/assemble-docker-tags.sh @@ -117,7 +117,7 @@ function is_latest_stable_patch_within_build_minor() { } function is_latest_global_patch() { - [[ "$build_patch_version" == $latest_patch_global && "$build_patch_version" != *"rc"* ]] + [[ "$build_patch_version" == "$latest_patch_global" && "$build_patch_version" != *"rc"* ]] } function is_latest_minor_within_build_major() { @@ -129,11 +129,11 @@ function is_latest_major() { } function is_default_base_os() { - [[ "$build_base_os" == "$default_base_os_within_build_minor" ]] + [[ "$build_base_os" == "$default_supported_base_os_within_build_minor" ]] } function is_latest_family_os_for_build_minor() { - [[ "$build_base_os" == "$latest_family_os_within_build_minor" ]] + [[ "$build_base_os" == "$latest_family_supported_os_within_build_minor" ]] } add_family_alias_if_latest() { @@ -280,6 +280,49 @@ latest_family_os_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r -- | max_by(.number) | .version') +# Determine the default base OS within the build minor considering the variation's supported_os +default_supported_base_os_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" --arg variation "$build_variation" ' + . as $root + | ($root.php_variations[] | select(.name == $variation) | (.supported_os // [])) as $supported + | ($root.operating_systems[] | select(.default == true) | .family) as $defaultFamily + | ($root.operating_systems[] | select(.family == $defaultFamily) | .versions) as $familyVersions + | ($root.php_versions[] + | .minor_versions[] + | select(.minor == $build_minor) + | .base_os + | map(.name)) as $minorBaseOs + | $familyVersions + | map(select( + .version as $v + | ($minorBaseOs | index($v)) != null + and ( + ($supported | length) == 0 + or any($supported[]; . == $v or (. == "alpine" and ($v | startswith("alpine")))) + ) + )) + | if length > 0 then (max_by(.number) | .version) else empty end') + +# Determine the latest OS within this family for the current minor considering the variation's supported_os +latest_family_supported_os_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" --arg family "$build_family" --arg variation "$build_variation" ' + . as $root + | ($root.php_variations[] | select(.name == $variation) | (.supported_os // [])) as $supported + | ($root.operating_systems[] | select(.family == $family) | .versions) as $familyVersions + | ($root.php_versions[] + | .minor_versions[] + | select(.minor == $build_minor) + | .base_os + | map(.name)) as $minorBaseOs + | $familyVersions + | map(select( + .version as $v + | ($minorBaseOs | index($v)) != null + and ( + ($supported | length) == 0 + or any($supported[]; . == $v or (. == "alpine" and ($v | startswith("alpine")))) + ) + )) + | if length > 0 then (max_by(.number) | .version) else empty end') + check_vars \ "🚨 Missing critical build variable. Check the script logic and logs" \ build_patch_version \ @@ -303,6 +346,8 @@ echo "Latest Patch Version within Build Minor: $latest_patch_within_build_minor" echo "Default Base OS within Build Minor: $default_base_os_within_build_minor" echo "Build Family: $build_family" echo "Latest Family OS within Build Minor: $latest_family_os_within_build_minor" +echo "Default Supported Base OS within Build Minor: ${default_supported_base_os_within_build_minor:-}" +echo "Latest Supported Family OS within Build Minor: ${latest_family_supported_os_within_build_minor:-}" echo "Latest Global Patch Version: $latest_patch_global" # Set default tag @@ -377,5 +422,5 @@ echo_color_message green "🚀 Summary of Docker Tags Being Shipped: $DOCKER_TAG # Save to GitHub's environment if [[ $CI == "true" ]]; then echo "DOCKER_TAGS=${DOCKER_TAGS}" >> $GITHUB_ENV - echo_color_message green "✅ Saved Docker Tags to "GITHUB_ENV"" + echo_color_message green "✅ Saved Docker Tags to GITHUB_ENV" fi \ No newline at end of file From 304c52788b33ec60dafcaf7b50761b4ad0386425 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 10:15:05 -0500 Subject: [PATCH 064/304] Move matrix generation to a script --- .../service_docker-build-and-publish.yml | 2 +- scripts/generate-matrix.sh | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 scripts/generate-matrix.sh diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 04ca2e03f..478135569 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -66,7 +66,7 @@ jobs: - name: Assemble PHP versions into the matrix. 😎 id: get-php-versions run: | - MATRIX_JSON=$(yq -o=json scripts/conf/php-versions.yml | jq -c '{include: [(.php_variations[] | {name, supported_os: (.supported_os // ["alpine", "bullseye", "bookworm"])} ) as $variation | .php_versions[] | .minor_versions[] | .patch_versions[] as $patch | .base_os[] as $os | select($variation.supported_os | if length == 0 then . else . | index($os.name) end) | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name}]} | {include: (.include | sort_by(.patch_version | split(".") | map(tonumber) | . as $nums | ($nums[0]*10000 + $nums[1]*100 + $nums[2])) | reverse)}') + MATRIX_JSON=$(bash ./scripts/generate-matrix.sh '${{ inputs.php-versions-file }}') echo "php-version-map-json=${MATRIX_JSON}" >> $GITHUB_OUTPUT echo "${MATRIX_JSON}" | jq '.' diff --git a/scripts/generate-matrix.sh b/scripts/generate-matrix.sh new file mode 100644 index 000000000..765c6cf2a --- /dev/null +++ b/scripts/generate-matrix.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Usage: generate-matrix.sh [path/to/php-versions.yml] +# Reads the provided YAML (or $PHP_VERSIONS_FILE, or default scripts/conf/php-versions.yml) +# and prints a GitHub Actions matrix JSON of the form {"include": [...]} + +PHP_VERSIONS_FILE="${1:-${PHP_VERSIONS_FILE:-scripts/conf/php-versions.yml}}" + +if [ ! -f "$PHP_VERSIONS_FILE" ]; then + echo "YAML file not found: $PHP_VERSIONS_FILE" >&2 + exit 1 +fi + +# Convert YAML to JSON, then shape it with jq. +yq -o=json "$PHP_VERSIONS_FILE" | jq -c ' + + def version_weight: + # Convert "x.y.z" into a sortable integer weight + split(".") | map(tonumber) as $nums | ($nums[0]*10000 + $nums[1]*100 + $nums[2]); + + def os_family_match($os_name; $supported): + # Allow listing "alpine" to include any alpine3.xx base_os + # Exact matches like "bullseye", "bookworm", "trixie" must match exactly + ($supported == $os_name) or ($supported == "alpine" and ($os_name | startswith("alpine"))); + + def is_supported($variation; $os): + # If no supported_os specified, allow all; otherwise filter + (($variation.supported_os // []) | length) == 0 or + ((($variation.supported_os // []) | any(os_family_match($os.name; .)))); + + . as $root + | [ ($root.php_variations[] | {name, supported_os}) as $variation + | $root.php_versions[] + | .minor_versions[] + | .base_os[] as $os + | .patch_versions[] as $patch + | select(is_supported($variation; $os)) + | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name} + ] + | { include: ( . | sort_by(.patch_version | version_weight) | reverse ) } +' + + From b015a35bfdba30af38fb1860f1c8206a6d78b9c6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 10:15:16 -0500 Subject: [PATCH 065/304] Remove Trixie support for Unit --- scripts/conf/php-versions-base-config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 95c8f1f55..4ad994d96 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -12,7 +12,6 @@ php_variations: supported_os: # Alpine with Unit is not supported yet. Submit a PR if you can help (https://github.com/serversideup/docker-php/issues/233) - bullseye - bookworm - - trixie php_versions: - major: "7" From 4f7d9fab1f2a281dbb3222fb6e1cb81990a31fb5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 10:18:17 -0500 Subject: [PATCH 066/304] Remove support for Alpine 3.20 in PHP 8.4 --- scripts/conf/php-versions-base-config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 4ad994d96..72a40e0ac 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -59,7 +59,6 @@ php_versions: # - 8.3.6 # Pull latest from Official PHP source - minor: "8.4" base_os: - - name: alpine3.20 - name: alpine3.21 - name: alpine3.22 - name: bookworm From c0e0f82fd6317bca60b80a73d93dc14991730e9b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 10:24:18 -0500 Subject: [PATCH 067/304] Removed Alpine 3.20 --- scripts/conf/php-versions-base-config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 72a40e0ac..2cff3dd33 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -32,7 +32,6 @@ php_versions: - 8.0.30 - minor: "8.1" base_os: - - name: alpine3.20 - name: alpine3.21 - name: alpine3.22 - name: bookworm @@ -41,7 +40,6 @@ php_versions: # - 8.1.28 # Pull latest from Official PHP source - minor: "8.2" base_os: - - name: alpine3.20 - name: alpine3.21 - name: alpine3.22 - name: bookworm @@ -50,7 +48,6 @@ php_versions: # - 8.2.18 # Pull latest from Official PHP source - minor: "8.3" base_os: - - name: alpine3.20 - name: alpine3.21 - name: alpine3.22 - name: bookworm From 306bf0c1bc41a36891fbd559e75fb257c6dc2dc9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 11:20:58 -0500 Subject: [PATCH 068/304] Added trixie support to Unit --- scripts/conf/php-versions-base-config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 2cff3dd33..3c770475e 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -12,6 +12,7 @@ php_variations: supported_os: # Alpine with Unit is not supported yet. Submit a PR if you can help (https://github.com/serversideup/docker-php/issues/233) - bullseye - bookworm + - trixie php_versions: - major: "7" From 9fefa9b3397c339bf8ca6cba9d03c4028ebda53f Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 12:17:59 -0500 Subject: [PATCH 069/304] Add validation for OS and variation in Docker tag assembly script; include support for PHP 8.5-rc in configuration --- scripts/assemble-docker-tags.sh | 113 +++++++++++++++++++--- scripts/conf/php-versions-base-config.yml | 8 ++ 2 files changed, 108 insertions(+), 13 deletions(-) diff --git a/scripts/assemble-docker-tags.sh b/scripts/assemble-docker-tags.sh index 4ea8161da..aa248b296 100755 --- a/scripts/assemble-docker-tags.sh +++ b/scripts/assemble-docker-tags.sh @@ -157,6 +157,55 @@ function is_default_variation() { [[ "$PHP_BUILD_VARIATION" == "$DEFAULT_IMAGE_VARIATION" ]] } +function is_rc_build() { + [[ "$build_patch_version" == *"-rc"* ]] +} + +validate_os_and_variation() { + local os_to_check="$build_base_os" + local variation_to_check="$build_variation" + + # Validate OS exists in config + if ! yq -o=json "$PHP_VERSIONS_FILE" | jq -e --arg os "$os_to_check" 'any(.operating_systems[] | .versions[]; .version == $os)' > /dev/null; then + echo_color_message red "🛑 ERROR: Unknown --os '$os_to_check'" + echo "Valid options are:" + yq -o=json "$PHP_VERSIONS_FILE" | jq -r '.operating_systems[].versions[].version' | sed 's/^/- /' + echo + help_menu + exit 1 + fi + + # Validate variation exists + if ! yq -o=json "$PHP_VERSIONS_FILE" | jq -e --arg v "$variation_to_check" 'any(.php_variations[]; .name == $v)' > /dev/null; then + echo_color_message red "🛑 ERROR: Unknown --variation '$variation_to_check'" + echo "Valid options are:" + yq -o=json "$PHP_VERSIONS_FILE" | jq -r '.php_variations[].name' | sed 's/^/- /' + echo + help_menu + exit 1 + fi + + # Validate variation supports OS if a constraint is defined + local supports_json + supports_json=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg v "$variation_to_check" '[.php_variations[] | select(.name == $v) | (.supported_os // [])[]]') + local has_constraints + has_constraints=$(echo "$supports_json" | jq 'length > 0') + + if [[ "$has_constraints" == "true" ]]; then + local is_supported + is_supported=$(echo "$supports_json" | jq -e --arg os "$os_to_check" ' + any(.[]; . == $os or (. == "alpine" and ($os | startswith("alpine")))) + ' >/dev/null 2>&1; echo $?) + if [[ "$is_supported" != "0" ]]; then + echo_color_message red "🛑 ERROR: Variation '$variation_to_check' does not support OS '$os_to_check'" + echo "Supported values for '$variation_to_check' are:" + echo "$supports_json" | jq -r '.[]' | sed 's/^/- /' + echo + exit 1 + fi + fi +} + help_menu() { echo "Usage: $0 --variation --os --patch-version [--stable-release --github-release-tag ]" echo @@ -235,16 +284,41 @@ build_patch_version=$PHP_BUILD_VERSION build_base_os=$PHP_BUILD_BASE_OS build_variation=$PHP_BUILD_VARIATION +# Validate inputs early +validate_os_and_variation + # Extract major and minor versions from build_patch_version build_major_version="${build_patch_version%%.*}" -build_minor_version="${build_patch_version%.*}" +if [[ "$build_patch_version" == *"-rc"* ]]; then + # For RC inputs like 8.5-rc, the minor identifier is the whole string + build_minor_version="$build_patch_version" +else + build_minor_version="${build_patch_version%.*}" +fi # Fetch version data from the PHP Versions file latest_global_stable_major=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r '[.php_versions[] | select(.major | test("-rc") | not) | .major | tonumber] | max | tostring') latest_global_stable_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg latest_global_stable_major "$latest_global_stable_major" '.php_versions[] | select(.major == $latest_global_stable_major) | .minor_versions | map(select(.minor | test("-rc") | not) | .minor | split(".") | .[1] | tonumber) | max | $latest_global_stable_major + "." + tostring') latest_minor_within_build_major=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_major "$build_major_version" '.php_versions[] | select(.major == $build_major) | .minor_versions | map(select(.minor | test("-rc") | not) | .minor | split(".") | .[1] | tonumber) | max | $build_major + "." + tostring') -latest_patch_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" '.php_versions[] | .minor_versions[] | select(.minor == $build_minor) | (.patch_versions // []) | map( split(".") | map(tonumber) ) | max | join(".")') -latest_patch_global=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r '[.php_versions[] | .minor_versions[] | select(.minor | test("-rc") | not) | ((.patch_versions // [])[]) | select(test("-rc") | not) | split(".") | map(tonumber) ] | max | join(".")') +latest_patch_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" ' + .php_versions[] + | .minor_versions[] + | select(.minor == $build_minor) + | (.patch_versions // []) as $patches + | ($patches | map(select(test("-rc") | not) | split(".") | map(tonumber))) as $parsed + | if ($parsed | length) > 0 then ($parsed | max | join(".")) else empty end +') +latest_patch_global=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r ' + [ + .php_versions[] + | .minor_versions[] + | select(.minor | test("-rc") | not) + | ((.patch_versions // [])[]) + | select(test("-rc") | not) + | split(".") | map(tonumber) + ] as $all + | if ($all | length) > 0 then ($all | max | join(".")) else empty end +') # Determine default base OS within the build minor using operating_systems default family and highest available version default_base_os_within_build_minor=$(yq -o=json "$PHP_VERSIONS_FILE" | jq -r --arg build_minor "$build_minor_version" ' . as $root @@ -339,26 +413,39 @@ echo "Build Major Version: $build_major_version" echo "Build Minor Version: $build_minor_version" echo_color_message yellow "🧐 Queried results from $PHP_VERSIONS_FILE" -echo "Latest Global Major Version: $latest_global_stable_major" -echo "Latest Global Minor Version: $latest_global_stable_minor" -echo "Latest Minor Version within Build Major: $latest_minor_within_build_major" -echo "Latest Patch Version within Build Minor: $latest_patch_within_build_minor" -echo "Default Base OS within Build Minor: $default_base_os_within_build_minor" -echo "Build Family: $build_family" -echo "Latest Family OS within Build Minor: $latest_family_os_within_build_minor" -echo "Default Supported Base OS within Build Minor: ${default_supported_base_os_within_build_minor:-}" -echo "Latest Supported Family OS within Build Minor: ${latest_family_supported_os_within_build_minor:-}" -echo "Latest Global Patch Version: $latest_patch_global" +echo "Latest Global Major Version: ${latest_global_stable_major:-N/A}" +echo "Latest Global Minor Version: ${latest_global_stable_minor:-N/A}" +echo "Latest Minor Version within Build Major: ${latest_minor_within_build_major:-N/A}" +echo "Latest Patch Version within Build Minor: ${latest_patch_within_build_minor:-N/A}" +echo "Default Base OS within Build Minor: ${default_base_os_within_build_minor:-N/A}" +echo "Build Family: ${build_family:-N/A}" +echo "Latest Family OS within Build Minor: ${latest_family_os_within_build_minor:-N/A}" +echo "Default Supported Base OS within Build Minor: ${default_supported_base_os_within_build_minor:-N/A}" +echo "Latest Supported Family OS within Build Minor: ${latest_family_supported_os_within_build_minor:-N/A}" +echo "Latest Global Patch Version: ${latest_patch_global:-N/A}" + +if is_rc_build; then + echo_color_message yellow "🔶 RC build detected. Stable aliases (minor/major/latest) will be skipped." +fi # Set default tag DOCKER_TAGS="" add_docker_tag "$build_patch_version-$build_variation-$build_base_os" add_family_alias_if_latest "$build_patch_version-$build_variation-$build_base_os" +# Always allow the variation-only alias for the default base OS, including RC builds if is_default_base_os; then add_docker_tag "$build_patch_version-$build_variation" fi +# For RC builds, allow publishing the root RC alias when both default OS and default variation are used +if is_rc_build && is_default_base_os && is_default_variation; then + add_docker_tag "$build_patch_version" + # Also publish the OS-specific RC alias and its family alias + add_docker_tag "$build_patch_version-$build_base_os" + add_family_alias_if_latest "$build_patch_version-$build_base_os" +fi + if is_latest_stable_patch_within_build_minor; then add_docker_tag "$build_minor_version-$build_variation-$build_base_os" add_family_alias_if_latest "$build_minor_version-$build_variation-$build_base_os" diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 3c770475e..f23712545 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -63,6 +63,14 @@ php_versions: - name: trixie patch_versions: # - 8.4.1 # Pull latest from Official PHP source + - minor: "8.5-rc" + base_os: + - name: alpine3.21 + - name: alpine3.22 + - name: bookworm + - name: trixie + patch_versions: + - 8.5-rc operating_systems: - family: alpine From 9467a3ce2190b20b00c801e2fb39f37005a13c96 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 12:22:44 -0500 Subject: [PATCH 070/304] Enhance version weighting logic in matrix generation script to support numeric patches and RC minors --- scripts/generate-matrix.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/generate-matrix.sh b/scripts/generate-matrix.sh index 765c6cf2a..ddf5e7ec0 100644 --- a/scripts/generate-matrix.sh +++ b/scripts/generate-matrix.sh @@ -16,8 +16,14 @@ fi yq -o=json "$PHP_VERSIONS_FILE" | jq -c ' def version_weight: - # Convert "x.y.z" into a sortable integer weight - split(".") | map(tonumber) as $nums | ($nums[0]*10000 + $nums[1]*100 + $nums[2]); + # Support numeric patches x.y.z and RC minors x.y-rc + if test("-rc$") then + capture("^(?[0-9]+)\\.(?[0-9]+)-rc$") as $m + | ($m.maj|tonumber)*10000 + ($m.min|tonumber)*100 + 99 + else + capture("^(?[0-9]+)\\.(?[0-9]+)\\.(?[0-9]+)$") as $m + | ($m.maj|tonumber)*10000 + ($m.min|tonumber)*100 + ($m.pat|tonumber) + end; def os_family_match($os_name; $supported): # Allow listing "alpine" to include any alpine3.xx base_os From ae3a912d9d6370f94a1bc25f5738712d6964ea8d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 12:27:18 -0500 Subject: [PATCH 071/304] Changed matrix to depot runner --- .github/workflows/service_docker-build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 478135569..6296f8d41 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -38,7 +38,7 @@ on: jobs: setup-matrix: - runs-on: ubuntu-24.04 + runs-on: depot-ubuntu-24.04 outputs: php-version-map-json: ${{ steps.get-php-versions.outputs.php-version-map-json }} steps: From 141deb06aa9a13eb20fb4a35c192eec65e658087 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 13:16:20 -0500 Subject: [PATCH 072/304] Update PHP extension installer version to 2.9.4 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index 5685cd921..ddb8eb1bc 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.7.0" +PHP_EXT_INSTALLER_VERSION="2.9.4" ############ # Main From 6642364c6cd9d5c159cb6469d8da276302bb37c5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 13:17:13 -0500 Subject: [PATCH 073/304] Update php-fpm-healthcheck to version 0.6.0 in installation script --- src/s6/usr/local/bin/docker-php-serversideup-s6-install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/s6/usr/local/bin/docker-php-serversideup-s6-install b/src/s6/usr/local/bin/docker-php-serversideup-s6-install index 0458d3a8f..903f1c8b1 100644 --- a/src/s6/usr/local/bin/docker-php-serversideup-s6-install +++ b/src/s6/usr/local/bin/docker-php-serversideup-s6-install @@ -34,5 +34,5 @@ untar ${S6_SRC_URL}/${S6_VERSION}/s6-overlay-${S6_ARCH}.tar.xz # Ensure "php-fpm-healthcheck" is installed echo "⬇️ Downloading php-fpm-healthcheck..." -curl -o /usr/local/bin/php-fpm-healthcheck https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/v0.5.0/php-fpm-healthcheck +curl -o /usr/local/bin/php-fpm-healthcheck https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/v0.6.0/php-fpm-healthcheck chmod +x /usr/local/bin/php-fpm-healthcheck \ No newline at end of file From e52ae67084534507fef0b29b52ff1d76685855a8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 13:17:39 -0500 Subject: [PATCH 074/304] Update S6 version to 3.2.1.0 in installation script --- src/s6/usr/local/bin/docker-php-serversideup-s6-install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/s6/usr/local/bin/docker-php-serversideup-s6-install b/src/s6/usr/local/bin/docker-php-serversideup-s6-install index 903f1c8b1..417d3f509 100644 --- a/src/s6/usr/local/bin/docker-php-serversideup-s6-install +++ b/src/s6/usr/local/bin/docker-php-serversideup-s6-install @@ -9,7 +9,7 @@ set -oue # Be sure to set the S6_SRC_URL, S6_SRC_DEP, and S6_DIR # environment variables before running this script. -S6_VERSION=v3.2.0.2 +S6_VERSION=v3.2.1.0 mkdir -p $S6_DIR export SYS_ARCH=$(uname -m) case "$SYS_ARCH" in From 57340cc5cf59b4af8c0f2e74aa6fde0c36b03fb6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 13:18:38 -0500 Subject: [PATCH 075/304] Update NGINX Unit version to 1.34.2 in Dockerfile --- src/variations/unit/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/unit/Dockerfile b/src/variations/unit/Dockerfile index 41cc5fb99..759fda10a 100644 --- a/src/variations/unit/Dockerfile +++ b/src/variations/unit/Dockerfile @@ -10,7 +10,7 @@ ARG BASE_IMAGE="php:${PHP_VERSION}-cli-${BASE_OS_VERSION}" FROM ${BASE_IMAGE} AS build ARG DEPENDENCY_PACKAGES_ALPINE='build-base curl tar git openssl-dev pcre2-dev shadow' ARG DEPENDENCY_PACKAGES_DEBIAN='ca-certificates git build-essential libssl-dev libpcre2-dev curl pkg-config' -ARG NGINX_UNIT_VERSION='1.33.0-1' +ARG NGINX_UNIT_VERSION='1.34.2' # copy our scripts COPY --chmod=755 src/common/ / From 6aade3ec19f065d7c4e91a65bfaad2b0ab89c88e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 13:27:34 -0500 Subject: [PATCH 076/304] Update php-fpm-healthcheck to version 0.6.0 in Dockerfile --- src/variations/fpm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index e97261604..86494ba5a 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -76,7 +76,7 @@ RUN rm -rf /usr/local/etc/php-fpm.d/*.conf && \ \ # Ensure "php-fpm-healthcheck" is installed echo "⬇️ Downloading php-fpm-healthcheck..." && \ - curl -o /usr/local/bin/php-fpm-healthcheck https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/v0.5.0/php-fpm-healthcheck && \ + curl -o /usr/local/bin/php-fpm-healthcheck https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/v0.6.0/php-fpm-healthcheck && \ chmod +x /usr/local/bin/php-fpm-healthcheck && \ \ # Install default PHP extensions From 74670a89309cf5485ac320563af2543610b44cf0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 13:28:02 -0500 Subject: [PATCH 077/304] Comment out PHP 8.5-rc configuration due to a blocking bug in a dependency (https://github.com/phpredis/phpredis/issues/2688) --- scripts/conf/php-versions-base-config.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index f23712545..c842d057d 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -63,14 +63,16 @@ php_versions: - name: trixie patch_versions: # - 8.4.1 # Pull latest from Official PHP source - - minor: "8.5-rc" - base_os: - - name: alpine3.21 - - name: alpine3.22 - - name: bookworm - - name: trixie - patch_versions: - - 8.5-rc + # PHP 8.5-rc has a blocking bug in a dependency that is not yet fixed. + # + # - minor: "8.5-rc" + # base_os: + # - name: alpine3.21 + # - name: alpine3.22 + # - name: bookworm + # - name: trixie + # patch_versions: + # - 8.5-rc operating_systems: - family: alpine From 26c1f6f59114df5f8e16f3daca1d18e3ce498460 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 14:17:52 -0500 Subject: [PATCH 078/304] Organized script for better readability --- ...cker-php-serversideup-set-file-permissions | 78 ++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index d31c1950a..93e50d7d3 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -70,19 +70,54 @@ case "$OS" in debian) case "$SERVICE" in cli) - DIRS="/var/www/ $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; fpm) - DIRS="/var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; apache) - DIRS="/run /etc/apache2 /etc/ssl/private /var/log/apache2 /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /etc/apache2 + /etc/ssl/private + /run + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + /var/log/apache2 + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; nginx) - DIRS="/run /etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx/ /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /etc/nginx + /etc/ssl/private + /run + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + /var/cache/nginx + /var/log/nginx + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; unit) - DIRS="/var/log/unit /var/run/unit /etc/unit /etc/ssl/private/ /var/lib/unit/ /var/www/ $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /etc/unit + /etc/ssl/private + /var/lib/unit + /var/log/unit + /var/run/unit + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) echo "$script_name: Unsupported service: $SERVICE" @@ -93,16 +128,41 @@ case "$OS" in alpine) case "$SERVICE" in cli) - DIRS="/var/www/ $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; fpm) - DIRS="/var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; apache) - DIRS="/etc/apache2 /etc/ssl/private /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /etc/apache2 + /etc/ssl/private + /run + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; nginx) - DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx /var/www/ /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + DIRS=" + /composer + /etc/nginx/ + /etc/ssl/private + /usr/local/etc/php-fpm.conf + /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf + /var/cache/nginx + /var/log/nginx + /var/www/ + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) echo "$script_name: Unsupported SERVICE: $SERVICE" From a3a11f8157d6021a523edf13a046cd6e2c7774a2 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 21 Aug 2025 14:23:16 -0500 Subject: [PATCH 079/304] Enhance script to require root privileges and improve error handling with 'set -eu' --- .../bin/docker-php-serversideup-set-file-permissions | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 93e50d7d3..56c163883 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -1,5 +1,5 @@ #!/bin/sh -set -e +set -eu ################################################### # Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP --service SERVICE @@ -17,6 +17,12 @@ usage() { exit 1 } +# Check for root privileges +if [ "$(id -u)" -ne 0 ]; then + echo "${script_name}: This script must be run as root within the container. Be sure to set \"USER root\" in your Dockerfile before running this script." + exit 1 +fi + # Check for minimum number of arguments if [ "$#" -ne 4 ]; then usage From 59e31217c12603a659303a40bfdd196f82fb1a17 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 22 Aug 2025 10:08:13 -0500 Subject: [PATCH 080/304] Revert changes to alpine and debian installer --- .../bin/docker-php-serversideup-dep-install-alpine | 10 +++++++++- .../bin/docker-php-serversideup-dep-install-debian | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine b/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine index 2e8265e35..be14e8acd 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine +++ b/src/common/usr/local/bin/docker-php-serversideup-dep-install-alpine @@ -24,14 +24,22 @@ if [ "$NAME" != "Alpine Linux" ] || [ $# -eq 0 ]; then exit 0 fi +############ +# Functions +############ +convert_comma_delimited_to_space_separated() { + echo $1 | tr ',' ' ' +} + ############ # Main ############ -DEP_PACKAGES="$@" +DEP_PACKAGES=$(convert_comma_delimited_to_space_separated "$@") echo "🤖 Installing: $DEP_PACKAGES" apk update apk add --no-cache $DEP_PACKAGES + echo "🧼 Cleaning up installation of: $DEP_PACKAGES" rm -rf /var/cache/apk/* diff --git a/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian b/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian index daa5e1fcb..309a9cf97 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian +++ b/src/common/usr/local/bin/docker-php-serversideup-dep-install-debian @@ -25,14 +25,22 @@ if [ "$NAME" != "Debian GNU/Linux" ] || [ $# -eq 0 ]; then exit 0 fi +############ +# Functions +############ +convert_comma_delimited_to_space_separated() { + echo $1 | tr ',' ' ' +} + ############ # Main ############ -DEP_PACKAGES="$@" +DEP_PACKAGES=$(convert_comma_delimited_to_space_separated "$@") echo "🤖 Installing: $DEP_PACKAGES" apt-get update apt-get install -y $DEP_PACKAGES + echo "🧼 Cleaning up installation of: $DEP_PACKAGES" apt-get clean rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* From 9e9ad9b72e94a31349de018c009b47270c5ede18 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 22 Aug 2025 10:13:03 -0500 Subject: [PATCH 081/304] Add shellcheck label --- scripts/get-nginx-versions.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/get-nginx-versions.sh b/scripts/get-nginx-versions.sh index a57202526..80ec5a76b 100755 --- a/scripts/get-nginx-versions.sh +++ b/scripts/get-nginx-versions.sh @@ -21,8 +21,8 @@ os_config() { echo "yq is required but not found. Install 'yq' (https://github.com/mikefarah/yq) to continue." 1>&2 return 1 fi - # jq is not required; all updates are handled via yq - + + # shellcheck disable=SC2016 yq -r '.operating_systems[] | .family as $f | .versions[] | "\(.version)|\($f)|\(.name)"' "$config_file" \ | while IFS='|' read -r version family name; do if [[ "$family" == "alpine" ]]; then From 0e550f4b24976ca5b18f41c4097ba3ab5ad24d00 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 22 Aug 2025 10:23:31 -0500 Subject: [PATCH 082/304] Enhance generate-matrix.sh to include excluded_minor_versions in PHP variations for improved filtering of minor versions --- scripts/generate-matrix.sh | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/generate-matrix.sh b/scripts/generate-matrix.sh index ddf5e7ec0..20a17b4ea 100755 --- a/scripts/generate-matrix.sh +++ b/scripts/generate-matrix.sh @@ -36,15 +36,14 @@ yq -o=json "$PHP_VERSIONS_FILE" | jq -c ' ((($variation.supported_os // []) | any(os_family_match($os.name; .)))); . as $root - | [ ($root.php_variations[] | {name, supported_os}) as $variation + | [ ($root.php_variations[] | {name, supported_os, excluded_minor_versions}) as $variation | $root.php_versions[] - | .minor_versions[] - | .base_os[] as $os - | .patch_versions[] as $patch + | .minor_versions[] as $minor + | select((($variation.excluded_minor_versions // []) | index($minor.minor)) | not) + | $minor.base_os[] as $os + | $minor.patch_versions[] as $patch | select(is_supported($variation; $os)) | {patch_version: $patch, base_os: $os.name, php_variation: $variation.name} ] | { include: ( . | sort_by(.patch_version | version_weight) | reverse ) } -' - - +' \ No newline at end of file From 64ed90dc68abd8940d53241c05cf6109cefdd1b1 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Sat, 23 Aug 2025 07:25:59 -0500 Subject: [PATCH 083/304] Compile FrankenPHP from source --- scripts/conf/php-versions-base-config.yml | 2 - ...cker-php-serversideup-set-file-permissions | 12 ++ src/variations/frankenphp/Dockerfile | 186 +++++++++++++++--- .../frankenphp/etc/frankenphp/Caddyfile | 59 ++++++ 4 files changed, 225 insertions(+), 34 deletions(-) create mode 100644 src/variations/frankenphp/etc/frankenphp/Caddyfile diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 5d40385df..4a9ede256 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -9,8 +9,6 @@ php_variations: - trixie - name: fpm-nginx - name: frankenphp - supported_os: - - bookworm excluded_minor_versions: - "7.4" - "8.0" diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 56c163883..b9d199080 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -125,6 +125,12 @@ case "$OS" in /var/www $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; + frankenphp) + DIRS=" + /composer + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + ;; *) echo "$script_name: Unsupported service: $SERVICE" exit 1 @@ -170,6 +176,12 @@ case "$OS" in /var/www/ $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; + frankenphp) + DIRS=" + /composer + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + ;; *) echo "$script_name: Unsupported SERVICE: $SERVICE" exit 1 diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 98dddedcc..dbc265e05 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -1,20 +1,118 @@ -ARG BASE_OS_VERSION='bookworm' -ARG PHP_VERSION='8.3' -ARG FRANKEN_PHP_VERSION='1.2.2' -ARG PHP_VARIATION='frankenphp' -ARG BASE_IMAGE="dunglas/frankenphp:${FRANKEN_PHP_VERSION}-php${PHP_VERSION}-${BASE_OS_VERSION}" - -########## -# CLI: Main Image -########## -FROM ${BASE_IMAGE} - -ARG DEPENDENCY_PACKAGES_ALPINE='shadow' -ARG DEPENDENCY_PACKAGES_DEBIAN='zip' +# check=skip=SecretsUsedInArgOrEnv +ARG BASE_OS_VERSION='trixie' +ARG PHP_VERSION='8.4' +ARG BASE_IMAGE="php:${PHP_VERSION}-zts-${BASE_OS_VERSION}" +ARG FRANKENPHP_VERSION='1.9.0' +ARG GOLANG_VERSION='1.25' + +######################## +# Common +######################## +FROM ${BASE_IMAGE} AS common +ARG REPOSITORY_BUILD_VERSION='dev' + +# copy our scripts +COPY --chmod=755 src/common/ / + +RUN set -eux; \ + # Create directories + mkdir -p \ + /var/www/html/public \ + /var/www/config/caddy \ + /var/www/data/caddy \ + /etc/caddy \ + /etc/frankenphp/ssl-modes \ + /etc/frankenphp/caddyfile.d; \ + # Create default index.php + echo ' /var/www/html/public/index.php; \ + # Create symbolic links + ln -sf /var/www/html /app; \ + ln -sf /var/www/config/ /config; \ + ln -sf /var/www/data/ /data; \ + # Ensure /var/www/ has the correct permissions + chown -R www-data:www-data /var/www/; \ + chmod -R 755 /var/www/; \ + \ + # Set the image version + echo "${REPOSITORY_BUILD_VERSION}" > /etc/serversideup-php-version; + # \ + # # Make composer cache directory + # mkdir -p "${COMPOSER_HOME}"; \ + # chown -R www-data:www-data "${COMPOSER_HOME}"; + +#################### +# Go Image +#################### +FROM golang:${GOLANG_VERSION} AS golang-image + +#################### +# FrankenPHP Build +#################### +FROM common AS frankenphp-build +ARG FRANKENPHP_VERSION +ARG GOLANG_VERSION +ARG BUILD_DEPENDENCY_PACKAGES_ALPINE='argon2-dev bash brotli-dev ca-certificates coreutils curl-dev git gnu-libiconv-dev libcap libsodium-dev cmake libstdc++ libxml2-dev linux-headers mailcap oniguruma-dev openssl-dev readline-dev sqlite-dev upx' +ARG BUILD_DEPENDENCY_PACKAGES_DEBIAN='cmake git libargon2-dev libbrotli-dev libcap2-bin libcurl4-openssl-dev libonig-dev libreadline-dev libsodium-dev libsqlite3-dev libssl-dev libxml2-dev mailcap zlib1g-dev' + +COPY --from=golang-image /usr/local/go /usr/local/go +ENV PATH="/usr/local/go/bin:${PATH}" +ENV GOTOOLCHAIN="local" + +# Install dependencies & Download FrankenPHP +RUN docker-php-serversideup-dep-install-alpine "$PHPIZE_DEPS ${BUILD_DEPENDENCY_PACKAGES_ALPINE}" && \ + docker-php-serversideup-dep-install-debian "${BUILD_DEPENDENCY_PACKAGES_DEBIAN}" + +# Install e-dant/watcher (necessary for file watching) +WORKDIR /usr/local/src/watcher +RUN curl -s https://api.github.com/repos/e-dant/watcher/releases/latest | \ + grep tarball_url | \ + awk '{ print $2 }' | \ + sed 's/,$//' | \ + sed 's/"//g' | \ + xargs curl -L | \ + tar xz --strip-components 1 && \ + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \ + cmake --build build && \ + cmake --install build && \ + if cat /etc/os-release | grep -q 'debian'; then \ + ldconfig; \ + fi + +# Download and build FrankenPHP +WORKDIR /go/src/app +ENV CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS" \ + CGO_CPPFLAGS=$PHP_CPPFLAGS \ + GOBIN=/usr/local/bin + +RUN if cat /etc/os-release | grep -q 'debian'; then \ + export CGO_LDFLAGS="-L/usr/local/lib -lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"; \ + export ADDITIONAL_BUILD_FLAGS=''; \ + elif cat /etc/os-release | grep -q 'alpine'; then \ + export CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"; \ + export ADDITIONAL_BUILD_FLAGS="-extldflags '-Wl,-z,stack-size=0x80000'"; \ + else \ + echo "Unsupported OS"; \ + exit 1; \ + fi; \ + git clone --depth 1 --branch v${FRANKENPHP_VERSION} \ + https://github.com/php/frankenphp.git .; \ + go mod download;\ + cd /go/src/app/caddy/frankenphp; \ + ../../go.sh install -ldflags "-w -s $ADDITIONAL_BUILD_FLAGS -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true; \ + frankenphp version; \ + frankenphp build-info; \ + setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp + +# WORKDIR /go/src/app/caddy + +#################### +# FrankenPHP Final +#################### +FROM common AS final ARG DEPENDENCY_PHP_EXTENSIONS='opcache pcntl pdo_mysql pdo_pgsql redis zip' -LABEL org.opencontainers.image.title="serversideup/php (${PHP_VARIATION})" \ - org.opencontainers.image.description="Supercharge your PHP experience. Based off the offical PHP images, serversideup/php includes pre-configured PHP extensions and settings for enhanced performance and security. Optimized for Laravel and WordPress." \ +LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ + org.opencontainers.image.description="Supercharge your PHP experience. Based off the official PHP images, serversideup/php includes pre-configured PHP extensions and settings for enhanced performance and security. Optimized for Laravel and WordPress." \ org.opencontainers.image.url="https://serversideup.net/open-source/docker-php/" \ org.opencontainers.image.source="https://github.com/serversideup/docker-php" \ org.opencontainers.image.documentation="https://serversideup.net/open-source/docker-php/docs/" \ @@ -23,12 +121,13 @@ LABEL org.opencontainers.image.title="serversideup/php (${PHP_VARIATION})" \ org.opencontainers.image.version="${REPOSITORY_BUILD_VERSION}" \ org.opencontainers.image.licenses="GPL-3.0-or-later" -ENV APP_BASE_DIR=/app \ +ENV APP_BASE_DIR=/var/www/html \ COMPOSER_ALLOW_SUPERUSER=1 \ COMPOSER_HOME=/composer \ COMPOSER_MAX_PARALLEL_HTTP=24 \ DISABLE_DEFAULT_CONFIG=false \ LOG_OUTPUT_LEVEL=warn \ + HEALTHCHECK_PATH="/healthcheck" \ PHP_DATE_TIMEZONE="UTC" \ PHP_DISPLAY_ERRORS=Off \ PHP_DISPLAY_STARTUP_ERRORS=Off \ @@ -36,38 +135,61 @@ ENV APP_BASE_DIR=/app \ PHP_ERROR_REPORTING="22527" \ PHP_MAX_EXECUTION_TIME="99" \ PHP_MAX_INPUT_TIME="-1" \ + PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ + PHP_OPCACHE_JIT="off" \ + PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ PHP_OPCACHE_MAX_ACCELERATED_FILES="10000" \ PHP_OPCACHE_MEMORY_CONSUMPTION="128" \ PHP_OPCACHE_REVALIDATE_FREQ="2" \ + PHP_OPCACHE_SAVE_COMMENTS="1" \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ - SHOW_WELCOME_MESSAGE=true + SHOW_WELCOME_MESSAGE=true \ + SSL_MODE=off \ + SSL_CERTIFICATE_FILE=/etc/ssl/private/self-signed-web.crt \ + SSL_PRIVATE_KEY_FILE=/etc/ssl/private/self-signed-web.key \ + XDG_CONFIG_HOME=/config \ + XDG_DATA_HOME=/data -# copy our scripts -COPY --chmod=755 src/common/ / +# install composer from Composer's official Docker image +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -# install pecl extensions & dependencies -RUN docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE}" && \ - docker-php-serversideup-dep-install-debian "${DEPENDENCY_PACKAGES_DEBIAN}" && \ - docker-php-serversideup-install-php-ext-installer && \ - \ - # Make composer cache directory - mkdir -p "${COMPOSER_HOME}" && \ - chown -R www-data:www-data "${COMPOSER_HOME}" && \ - \ +COPY --from=frankenphp-build /usr/local/bin/frankenphp /usr/local/bin/frankenphp +COPY --from=frankenphp-build /usr/local/lib/libwatcher* /usr/local/lib/ + +COPY src/variations/frankenphp/etc/frankenphp/ /etc/frankenphp/ + + +RUN \ + # Fix for the file watcher on arm + docker-php-serversideup-dep-install-alpine "libstdc++"; \ + docker-php-serversideup-dep-install-debian "libstdc++6"; \ + if cat /etc/os-release | grep -q 'alpine'; then \ + ldconfig /usr/local/lib; \ + elif cat /etc/os-release | grep -q 'debian'; then \ + ldconfig; \ + else \ + echo "Unsupported OS"; \ + exit 1; \ + fi; \ + # Install PHP Extension installer + docker-php-serversideup-install-php-ext-installer; \ # Install default PHP extensions install-php-extensions "${DEPENDENCY_PHP_EXTENSIONS}" -# install composer from Composer's official Docker image -COPY --from=composer:2 /usr/bin/composer /usr/bin/composer - WORKDIR ${APP_BASE_DIR} +USER www-data + +EXPOSE 8080 8443 8443/udp 2019 + ENTRYPOINT ["docker-php-serversideup-entrypoint"] -CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] \ No newline at end of file +CMD ["frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"] \ No newline at end of file diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile new file mode 100644 index 000000000..e1a0b8693 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -0,0 +1,59 @@ +# The Caddyfile is an easy way to configure FrankenPHP and the Caddy web server. +# +# https://frankenphp.dev/docs/config +# https://caddyserver.com/docs/caddyfile + +{ + skip_install_trust + + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + {$FRANKENPHP_CONFIG} + } +} + +{$CADDY_EXTRA_CONFIG} + +{$SERVER_NAME:localhost}:8443 { + #log { + # # Redact the authorization query parameter that can be set by Mercure + # format filter { + # request>uri query { + # replace authorization REDACTED + # } + # } + #} + + root {$SERVER_ROOT:public/} + encode zstd br gzip + + # Uncomment the following lines to enable Mercure and Vulcain modules + #mercure { + # # Transport to use (default to Bolt) + # transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} + # # Publisher JWT key + # publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} + # # Subscriber JWT key + # subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} + # # Allow anonymous subscribers (double-check that it's what you want) + # anonymous + # # Enable the subscription API (double-check that it's what you want) + # subscriptions + # # Extra directives + # {$MERCURE_EXTRA_DIRECTIVES} + #} + #vulcain + + {$CADDY_SERVER_EXTRA_DIRECTIVES} + + php_server { + #worker /path/to/your/worker.php + } +} + +# As an alternative to editing the above site block, you can add your own site +# block files in the Caddyfile.d directory, and they will be included as long +# as they use the .caddyfile extension. + +import Caddyfile.d/*.caddyfile \ No newline at end of file From ef48b01ae1a62331096ac928479191793fcd2b55 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Sat, 23 Aug 2025 07:45:18 -0500 Subject: [PATCH 084/304] Exclude 8.2 on FrankenPHP --- scripts/conf/php-versions-base-config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 4a9ede256..b89e0fb46 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -13,6 +13,7 @@ php_variations: - "7.4" - "8.0" - "8.1" + - "8.2" - name: unit supported_os: # Alpine with Unit is not supported yet. Submit a PR if you can help (https://github.com/serversideup/docker-php/issues/233) - bullseye From a3d592c5e94e2076ddf2604fad43b468139b3b27 Mon Sep 17 00:00:00 2001 From: Matt Hook Date: Fri, 29 Aug 2025 06:38:51 +1200 Subject: [PATCH 085/304] Add default Caddyfile for FrankenPHP that mimics the FPM-NGINX experience (#527) * update franken versions and create an initial caddyfile * Caddy likes tabs over spaces * Set default env vars in the dockerfile, create webroot * update dockerfile and add healthcheck * update caddyfile. https not yet working * Refactor dependency installs to support "docker-php-serversideup-set-file-permissions" across OS's * Fix typo in SSL certificate path in 10-init-unit.sh script * Add /etc/ssl/private directory to file permissions in docker-php-serversideup-set-file-permissions script * Add script to generate self-signed SSL certificate and key for container * Update Dockerfile to copy entrypoint scripts to /etc/entrypoint.d/ for improved container initialization * Remove reference file * Add Caddy configuration options and SSL mode handling for FrankenPHP - Introduced new environment variables for Caddy configuration in the Dockerfile, including options for admin interface, public path, and HTTPS settings. - Updated the Caddyfile to support different SSL modes (off, mixed, full) with corresponding configurations. - Enhanced the SSL generation script to skip certificate generation when SSL mode is off. - Added new Caddyfile imports for structured configuration management. - Improved logging and health check responses in the Caddyfile. * Enhance Caddyfile configuration for FrankenPHP with security and caching improvements - Added trusted proxies configuration to support Docker and Cloudflare. - Introduced client IP header handling for better client identification. - Updated static file handling with improved caching headers and logging for specific files. - Implemented security headers to mitigate common web vulnerabilities. - Imported security settings into SSL mode configurations for full and mixed modes. * Add logging configuration options for Caddy in FrankenPHP - Introduced new environment variables for log formatting and log levels. - Updated the Caddyfile to import log level configurations for both global and address-specific logging. - Created separate Caddyfile configurations for various log levels (debug, info, warn, error, crit, alert, emerg) to enhance logging granularity. - Adjusted Dockerfile to include new log-level configuration files in the container. --------- Co-authored-by: Matt Hook Co-authored-by: Jay Rogers <3174134+jaydrogers@users.noreply.github.com> Co-authored-by: Jay Rogers --- .../1.environment-variable-specification.md | 15 ++ ...cker-php-serversideup-set-file-permissions | 2 + src/variations/frankenphp/Dockerfile | 36 +++- .../etc/entrypoint.d/10-generate-ssl.sh | 38 ++++ .../frankenphp/etc/frankenphp/Caddyfile | 163 +++++++++++++----- .../log-level/address/alert.caddyfile | 5 + .../log-level/address/crit.caddyfile | 5 + .../log-level/address/debug.caddyfile | 5 + .../log-level/address/emerg.caddyfile | 5 + .../log-level/address/error.caddyfile | 5 + .../log-level/address/info.caddyfile | 5 + .../log-level/address/notice.caddyfile | 5 + .../log-level/address/warn.caddyfile | 5 + .../log-level/global/alert.caddyfile | 5 + .../log-level/global/crit.caddyfile | 5 + .../log-level/global/debug.caddyfile | 6 + .../log-level/global/emerg.caddyfile | 5 + .../log-level/global/error.caddyfile | 5 + .../log-level/global/info.caddyfile | 5 + .../log-level/global/notice.caddyfile | 5 + .../log-level/global/warn.caddyfile | 5 + .../etc/frankenphp/ssl-mode/full.caddyfile | 9 + .../etc/frankenphp/ssl-mode/mixed.caddyfile | 9 + .../etc/frankenphp/ssl-mode/off.caddyfile | 3 + .../unit/etc/entrypoint.d/10-init-unit.sh | 2 +- 25 files changed, 309 insertions(+), 49 deletions(-) create mode 100644 src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/address/alert.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/address/crit.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/address/debug.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/address/emerg.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/address/error.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/address/info.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/address/notice.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/address/warn.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/global/alert.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/global/crit.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/global/debug.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/global/emerg.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/global/error.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/global/info.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/global/notice.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/log-level/global/warn.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index d0cd376b4..214b898e5 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -32,10 +32,25 @@ We like to customize our images on a per app basis using environment variables. `AUTORUN_LARAVEL_ROUTE_CACHE`
*Default: "true"*|Automatically run "php artisan route:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_STORAGE_LINK`
*Default: "true"*|Automatically run "php artisan storage:link" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_VIEW_CACHE`
*Default: "true"*|Automatically run "php artisan view:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`CADDY_ADMIN`
*Default: "off"*|Enable Caddy admin interface. (Official docs)|frankenphp +`CADDY_APP_PUBLIC_PATH`
*Default: "/var/www/html/public"*|The path to your public directory for your app. (Official docs)|frankenphp +`CADDY_AUTO_HTTPS`
*Default: "off"*|Enable automatic HTTPS. (Official docs)|frankenphp +`CADDY_GLOBAL_OPTIONS`
*Default: ""*|Set global options for the Caddy server. (Official docs)|frankenphp +`CADDY_HTTP_PORT`
*Default: "8080"*|Set the port for HTTP. (Official docs)|frankenphp +`CADDY_HTTPS_PORT`
*Default: "8443"*|Set the port for HTTPS. (Official docs)|frankenphp +`CADDY_HTTP_SERVER_ADDRESS`
*Default: "http://"*|Set the server address for HTTP. (Official docs)|frankenphp +`CADDY_HTTPS_SERVER_ADDRESS`
*Default: "https://"*|Set the server address for HTTPS. (Official docs)|frankenphp +`CADDY_LOG_FORMAT`
*Default: "console"*|Set the format for the Caddy log. (Official docs)|frankenphp +`CADDY_GLOBAL_LOG_LEVEL`
*Default: "warn"*|Set the global log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_GLOBAL_LOG_LEVEL` takes precedence. (Official docs)|frankenphp +`CADDY_SERVER_LOG_LEVEL`
*Default: "warn"*|Set the server log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_SERVER_LOG_LEVEL` takes precedence. (Official docs)|frankenphp +`CADDY_LOG_OUTPUT`
*Default: "stdout"*|Set the output for the Caddy log. (Official docs)|frankenphp +`CADDY_PHP_SERVER_OPTIONS`
*Default: ""*|Set PHP server options for the Caddy server. (Official docs)|frankenphp +`CADDY_SERVER_EXTRA_DIRECTIVES`
*Default: ""*|Set extra directives for the Caddy server. (Official docs)|frankenphp `COMPOSER_ALLOW_SUPERUSER`
*Default: "1"*|Disable warning about running as super-user|all `COMPOSER_HOME`
*Default: "/composer"*|The COMPOSER_HOME variable allows you to change the Composer home directory. This is a hidden, global (per-user on the machine) directory that is shared between all projects.|all `COMPOSER_MAX_PARALLEL_HTTP`
*Default: "24"*|Set to an integer to configure how many files can be downloaded in parallel. Composer ships with 12 by default and must be between 1 and 50. If your proxy has issues with concurrency maybe you want to lower this. Increasing it should generally not result in performance gains.|all `DISABLE_DEFAULT_CONFIG`
*Default: "false"*|Get full customization of the image and disable all default configurations and automations.| all +`FRANKENPHP_CONFIG`
*Default: ""*|Set the configuration for FrankenPHP. (Official docs)|frankenphp `HEALTHCHECK_PATH`
*Default: "/healthcheck"*|Set the path for the health check endpoint. (Official docs)|all (except `cli` and `frankenphp`) `LOG_OUTPUT_LEVEL`
*Default: "warn"*|Set your container output different verbosity levels: debug, info, off |all `NGINX_FASTCGI_BUFFERS`
*Default: "8 8k"*|Sets the number and size of the buffers used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index b9d199080..a15ad6376 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -129,6 +129,7 @@ case "$OS" in DIRS=" /composer /var/www + /etc/ssl/private $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) @@ -180,6 +181,7 @@ case "$OS" in DIRS=" /composer /var/www + /etc/ssl/private $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index dbc265e05..7801b59bd 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -21,7 +21,8 @@ RUN set -eux; \ /var/www/config/caddy \ /var/www/data/caddy \ /etc/caddy \ - /etc/frankenphp/ssl-modes \ + /etc/frankenphp/ssl-mode \ + /etc/frankenphp/log-level \ /etc/frankenphp/caddyfile.d; \ # Create default index.php echo ' /var/www/html/public/index.php; \ @@ -109,6 +110,8 @@ RUN if cat /etc/os-release | grep -q 'debian'; then \ # FrankenPHP Final #################### FROM common AS final +ARG DEPENDENCY_PACKAGES_ALPINE='shadow libstdc++' +ARG DEPENDENCY_PACKAGES_DEBIAN='libstdc++6' ARG DEPENDENCY_PHP_EXTENSIONS='opcache pcntl pdo_mysql pdo_pgsql redis zip' LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ @@ -121,11 +124,22 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ org.opencontainers.image.version="${REPOSITORY_BUILD_VERSION}" \ org.opencontainers.image.licenses="GPL-3.0-or-later" -ENV APP_BASE_DIR=/var/www/html \ + ENV APP_BASE_DIR=/var/www/html \ + CADDY_ADMIN="off" \ + CADDY_APP_PUBLIC_PATH="/var/www/html/public" \ + CADDY_AUTO_HTTPS="off" \ + CADDY_GLOBAL_OPTIONS="" \ + CADDY_HTTP_PORT="8080" \ + CADDY_HTTPS_PORT="8443" \ + CADDY_HTTP_SERVER_ADDRESS="http://" \ + CADDY_HTTPS_SERVER_ADDRESS="https://" \ + CADDY_PHP_SERVER_OPTIONS="" \ + CADDY_SERVER_EXTRA_DIRECTIVES="" \ COMPOSER_ALLOW_SUPERUSER=1 \ COMPOSER_HOME=/composer \ COMPOSER_MAX_PARALLEL_HTTP=24 \ DISABLE_DEFAULT_CONFIG=false \ + FRANKEN_PHP_CONFIG="" \ LOG_OUTPUT_LEVEL=warn \ HEALTHCHECK_PATH="/healthcheck" \ PHP_DATE_TIMEZONE="UTC" \ @@ -153,8 +167,8 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ SHOW_WELCOME_MESSAGE=true \ SSL_MODE=off \ - SSL_CERTIFICATE_FILE=/etc/ssl/private/self-signed-web.crt \ - SSL_PRIVATE_KEY_FILE=/etc/ssl/private/self-signed-web.key \ + SSL_CERTIFICATE_FILE="/etc/ssl/private/self-signed-web.crt" \ + SSL_CERTIFICATE_KEY_FILE="/etc/ssl/private/self-signed-web.key" \ XDG_CONFIG_HOME=/config \ XDG_DATA_HOME=/data @@ -165,12 +179,12 @@ COPY --from=frankenphp-build /usr/local/bin/frankenphp /usr/local/bin/frankenphp COPY --from=frankenphp-build /usr/local/lib/libwatcher* /usr/local/lib/ COPY src/variations/frankenphp/etc/frankenphp/ /etc/frankenphp/ - +COPY src/variations/frankenphp/etc/entrypoint.d/ /etc/entrypoint.d/ RUN \ + docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE}"; \ + docker-php-serversideup-dep-install-debian "${DEPENDENCY_PACKAGES_DEBIAN}"; \ # Fix for the file watcher on arm - docker-php-serversideup-dep-install-alpine "libstdc++"; \ - docker-php-serversideup-dep-install-debian "libstdc++6"; \ if cat /etc/os-release | grep -q 'alpine'; then \ ldconfig /usr/local/lib; \ elif cat /etc/os-release | grep -q 'debian'; then \ @@ -179,10 +193,16 @@ RUN \ echo "Unsupported OS"; \ exit 1; \ fi; \ + # Make composer cache directory + mkdir -p "${COMPOSER_HOME}" && \ + chown -R www-data:www-data "${COMPOSER_HOME}" && \ + \ # Install PHP Extension installer docker-php-serversideup-install-php-ext-installer; \ # Install default PHP extensions - install-php-extensions "${DEPENDENCY_PHP_EXTENSIONS}" + install-php-extensions "${DEPENDENCY_PHP_EXTENSIONS}"; \ + # Ensure permissions are set for www-data + docker-php-serversideup-set-file-permissions --owner www-data:www-data --service frankenphp WORKDIR ${APP_BASE_DIR} diff --git a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh new file mode 100644 index 000000000..abbd96d61 --- /dev/null +++ b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh @@ -0,0 +1,38 @@ +#!/bin/sh +################################################### +# Usage: 10-generate-ssl.sh +################################################### +# This script generates a self-signed SSL certificate and key for the container. +script_name="generate-ssl" + +SSL_CERTIFICATE_FILE=${SSL_CERTIFICATE_FILE:-"/etc/ssl/private/self-signed-web.crt"} +SSL_PRIVATE_KEY_FILE=${SSL_PRIVATE_KEY_FILE:-"/etc/ssl/private/self-signed-web.key"} +SSL_MODE=${SSL_MODE:-"off"} + +if [ "$SSL_MODE" = "off" ]; then + echo "ℹ️ NOTICE ($script_name): SSL mode is off, so we'll not generate a self-signed SSL certificate and key." + return 0 +fi + +if [ -z "$SSL_CERTIFICATE_FILE" ] || [ -z "$SSL_PRIVATE_KEY_FILE" ]; then + echo "🛑 ERROR ($script_name): SSL_CERTIFICATE_FILE or SSL_PRIVATE_KEY_FILE is not set." + return 1 +fi + +if ([ -f "$SSL_CERTIFICATE_FILE" ] && [ ! -f "$SSL_PRIVATE_KEY_FILE" ]) || + ([ ! -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]); then + echo "🛑 ERROR ($script_name): Only one of the SSL certificate or private key exists. Check the SSL_CERTIFICATE_FILE and SSL_PRIVATE_KEY_FILE variables and try again." + echo "🛑 ERROR ($script_name): SSL_CERTIFICATE_FILE: $SSL_CERTIFICATE_FILE" + echo "🛑 ERROR ($script_name): SSL_PRIVATE_KEY_FILE: $SSL_PRIVATE_KEY_FILE" + return 1 +fi + +if [ -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]; then + echo "ℹ️ NOTICE ($script_name): SSL certificate and private key already exist, so we'll use the existing files." + return 0 +fi + +echo "🔐 SSL Keypair not found. Generating self-signed SSL keypair..." +openssl req -x509 -subj "/C=US/ST=Wisconsin/L=Milwaukee/O=IT/CN=*.dev.test,*.test,*.gitpod.io,*.ngrok.io,*.nip.io" -nodes -newkey rsa:2048 -keyout "$SSL_PRIVATE_KEY_FILE" -out "$SSL_CERTIFICATE_FILE" -days 365 >/dev/null 2>&1 + +exit 0 \ No newline at end of file diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index e1a0b8693..de6220bd4 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -1,59 +1,142 @@ -# The Caddyfile is an easy way to configure FrankenPHP and the Caddy web server. -# -# https://frankenphp.dev/docs/config -# https://caddyserver.com/docs/caddyfile - { + # Global Caddy configuration + admin {$CADDY_ADMIN:off} + auto_https {$CADDY_AUTO_HTTPS:off} + + http_port {$CADDY_HTTP_PORT:8080} + https_port {$CADDY_HTTPS_PORT:8443} + skip_install_trust - {$CADDY_GLOBAL_OPTIONS} + # Match serversideup/php log levels to Caddy global log levels + import log-level/global/{$LOG_OUTPUT_LEVEL:info}.caddyfile frankenphp { {$FRANKENPHP_CONFIG} } -} -{$CADDY_EXTRA_CONFIG} + servers { + # Trust Docker/private networks + loopback + Cloudflare ranges + trusted_proxies static \ +10.0.0.0/8 \ +172.16.0.0/12 \ +192.168.0.0/16 \ +127.0.0.1/8 \ +::1 \ +fd00::/8 \ +173.245.48.0/20 \ +103.21.244.0/22 \ +103.22.200.0/22 \ +103.31.4.0/22 \ +141.101.64.0/18 \ +108.162.192.0/18 \ +190.93.240.0/20 \ +188.114.96.0/20 \ +197.234.240.0/22 \ +198.41.128.0/17 \ +162.158.0.0/15 \ +104.16.0.0/13 \ +104.24.0.0/14 \ +172.64.0.0/13 \ +131.0.72.0/22 \ +2400:cb00::/32 \ +2606:4700::/32 \ +2803:f800::/32 \ +2405:b500::/32 \ +2405:8100::/32 \ +2a06:98c0::/29 \ +2c0f:f248::/32 + + # Prefer Cloudflare's header; keep XFF as fallback + client_ip_headers CF-Connecting-IP X-Forwarded-For + } + + {$CADDY_GLOBAL_OPTIONS} +} -{$SERVER_NAME:localhost}:8443 { - #log { - # # Redact the authorization query parameter that can be set by Mercure - # format filter { - # request>uri query { - # replace authorization REDACTED - # } - # } - #} +# Common app logic; reused across all SSL modes +(app-common) { + root * {$CADDY_APP_PUBLIC_PATH:/var/www/html/public} - root {$SERVER_ROOT:public/} encode zstd br gzip - # Uncomment the following lines to enable Mercure and Vulcain modules - #mercure { - # # Transport to use (default to Bolt) - # transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} - # # Publisher JWT key - # publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} - # # Subscriber JWT key - # subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} - # # Allow anonymous subscribers (double-check that it's what you want) - # anonymous - # # Enable the subscription API (double-check that it's what you want) - # subscriptions - # # Extra directives - # {$MERCURE_EXTRA_DIRECTIVES} - #} - #vulcain + # Match serversideup/php log levels to Caddy address log levels + import log-level/address/{$LOG_OUTPUT_LEVEL:info}.caddyfile - {$CADDY_SERVER_EXTRA_DIRECTIVES} + # Healthcheck endpoint + @health { + path /healthcheck + } + respond @health "OK" 200 + log_skip @health php_server { - #worker /path/to/your/worker.php + {$CADDY_PHP_SERVER_OPTIONS} + } + file_server + + import performance + import security + + {$CADDY_SERVER_EXTRA_DIRECTIVES} +} + +(performance) { + # Favicon/robots: skip noisy logs + @meta path /favicon.ico /robots.txt + log_skip @meta + + # Static assets (long cache) + @static { + path *.css *.css.map *.js *.js.map *.jpg *.jpeg *.png *.gif *.ico *.cur *.heic *.webp *.tif *.tiff *.mp3 *.m4a *.aac *.ogg *.midi *.mid *.wav *.mp4 *.mov *.webm *.mpeg *.mpg *.avi *.ogv *.flv *.wmv *.htc *.gz *.svg *.svgz *.woff2 *.woff + } + header @static Cache-Control "public, immutable, stale-while-revalidate, max-age=31536000" + + # Fonts/SVG: allow cross-origin usage (cache header inherited from @static) + @fonts { + path *.svg *.svgz *.ttf *.ttc *.otf *.eot *.woff *.woff2 + } + header @fonts Access-Control-Allow-Origin "*" + + # Short-lived static + @staticshort { + path *.json *.xml *.rss + } + header @staticshort Cache-Control "no-cache, max-age=3600" +} + +(security) { + # Reject dot files and certain file extensions + @rejected path *.bak *.conf *.dist *.fla *.ini *.inc *.inci *.log *.orig *.psd *.sh *.sql *.swo *.swp *.swop */.* + + # Return 403 Forbidden for rejected files + respond @rejected 403 + + # Security headers + header { + defer + # Prevent IFRAME spoofing attacks + X-Frame-Options "SAMEORIGIN" + # Prevent MIME type sniffing + X-Content-Type-Options "nosniff" + # Prevent referrer leakage + Referrer-Policy "strict-origin-when-cross-origin" + # Prevent server header leakage + -Server + # Prevent powered by header leakage + -X-Powered-By + } +} + +(security-https) { + header { + defer + Strict-Transport-Security "max-age=31536000; includeSubDomains" } } -# As an alternative to editing the above site block, you can add your own site -# block files in the Caddyfile.d directory, and they will be included as long -# as they use the .caddyfile extension. +# Pull in the per-mode listeners (off|mixed|full) +import ssl-mode/{$SSL_MODE:off}.caddyfile -import Caddyfile.d/*.caddyfile \ No newline at end of file +# Add your web servers here +import caddyfile.d/*.caddyfile diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/address/alert.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/address/alert.caddyfile new file mode 100644 index 000000000..22f4e0619 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/address/alert.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:ERROR} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/address/crit.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/address/crit.caddyfile new file mode 100644 index 000000000..22f4e0619 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/address/crit.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:ERROR} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/address/debug.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/address/debug.caddyfile new file mode 100644 index 000000000..0d972084b --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/address/debug.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:INFO} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/address/emerg.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/address/emerg.caddyfile new file mode 100644 index 000000000..22f4e0619 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/address/emerg.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:ERROR} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/address/error.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/address/error.caddyfile new file mode 100644 index 000000000..22f4e0619 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/address/error.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:ERROR} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/address/info.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/address/info.caddyfile new file mode 100644 index 000000000..0d972084b --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/address/info.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:INFO} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/address/notice.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/address/notice.caddyfile new file mode 100644 index 000000000..0d972084b --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/address/notice.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:INFO} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/address/warn.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/address/warn.caddyfile new file mode 100644 index 000000000..4755f020e --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/address/warn.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:WARN} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/global/alert.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/global/alert.caddyfile new file mode 100644 index 000000000..22f4e0619 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/global/alert.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:ERROR} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/global/crit.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/global/crit.caddyfile new file mode 100644 index 000000000..22f4e0619 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/global/crit.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:ERROR} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/global/debug.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/global/debug.caddyfile new file mode 100644 index 000000000..90124e286 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/global/debug.caddyfile @@ -0,0 +1,6 @@ +debug +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:DEBUG} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/global/emerg.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/global/emerg.caddyfile new file mode 100644 index 000000000..22f4e0619 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/global/emerg.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:ERROR} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/global/error.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/global/error.caddyfile new file mode 100644 index 000000000..22f4e0619 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/global/error.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:ERROR} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/global/info.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/global/info.caddyfile new file mode 100644 index 000000000..0d972084b --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/global/info.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:INFO} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/global/notice.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/global/notice.caddyfile new file mode 100644 index 000000000..0d972084b --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/global/notice.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:INFO} +} diff --git a/src/variations/frankenphp/etc/frankenphp/log-level/global/warn.caddyfile b/src/variations/frankenphp/etc/frankenphp/log-level/global/warn.caddyfile new file mode 100644 index 000000000..4755f020e --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/log-level/global/warn.caddyfile @@ -0,0 +1,5 @@ +log { + format {$CADDY_LOG_FORMAT:console} + output {$CADDY_LOG_OUTPUT:stdout} + level {$CADDY_SERVER_LOG_LEVEL:WARN} +} diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile new file mode 100644 index 000000000..1d1ff2bbb --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile @@ -0,0 +1,9 @@ +{$CADDY_HTTP_SERVER_ADDRESS:http://} { + redir https://{host}{uri} 308 +} + +{$CADDY_HTTPS_SERVER_ADDRESS:https://} { + tls {$SSL_CERTIFICATE_FILE} {$SSL_CERTIFICATE_KEY_FILE} + import app-common + import security-https +} diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile new file mode 100644 index 000000000..80e1756d8 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile @@ -0,0 +1,9 @@ +{$CADDY_HTTP_SERVER_ADDRESS:http://} { + import app-common +} + +{$CADDY_HTTPS_SERVER_ADDRESS:https://} { + tls {$SSL_CERTIFICATE_FILE} {$SSL_CERTIFICATE_KEY_FILE} + import app-common + import security-https +} diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile new file mode 100644 index 000000000..08ef293c6 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile @@ -0,0 +1,3 @@ +{$CADDY_HTTP_SERVER_ADDRESS:http://} { + import app-common +} \ No newline at end of file diff --git a/src/variations/unit/etc/entrypoint.d/10-init-unit.sh b/src/variations/unit/etc/entrypoint.d/10-init-unit.sh index 85cf82702..88c6e22b3 100644 --- a/src/variations/unit/etc/entrypoint.d/10-init-unit.sh +++ b/src/variations/unit/etc/entrypoint.d/10-init-unit.sh @@ -159,7 +159,7 @@ validate_ssl(){ fi if [ -f "/etc/ssl/private/$UNIT_CERTIFICATE_NAME.crt" ] && [ -f "/etc/ssl/private/$UNIT_CERTIFICATE_NAME.key" ]; then - echo "ℹ️ NOTICE ($script_name): Custom SSL Certificate found in /etc/sss/private, so we'll use that." + echo "ℹ️ NOTICE ($script_name): Custom SSL Certificate found in /etc/ssl/private, so we'll use that." cat "/etc/ssl/private/$UNIT_CERTIFICATE_NAME.key" "/etc/ssl/private/$UNIT_CERTIFICATE_NAME.crt" > "$UNIT_CONFIG_DIRECTORY/$UNIT_CERTIFICATE_NAME.pem" return 0 fi From 78f2b40c77f0f480a92adcd271862a37cce3d135 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 3 Sep 2025 12:40:26 -0500 Subject: [PATCH 086/304] Enhance get-php-versions.sh script to include an optional --output parameter for specifying the output file path. Default output path remains unchanged. Improved error handling for missing output file path argument. --- scripts/get-php-versions.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 03aab323b..39e522663 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -1,6 +1,6 @@ #!/bin/bash ################################################### -# Usage: get-php-versions.sh [--skip-download] [--skip-dockerhub-validation] +# Usage: get-php-versions.sh [--skip-download] [--skip-dockerhub-validation] [--output PATH] ################################################### # This file takes the official latest PHP releases from php.net merges them with our # "base php configuration". These files get merged into a final file called "php-versions.yml" @@ -22,6 +22,7 @@ # 👉 OPTIONS # --skip-download: Skip downloading from php.net and use existing base config # --skip-dockerhub-validation: Skip DockerHub validation (useful for testing/development) +# --output: Output file path (defaults to scripts/conf/php-versions.yml) set -oue pipefail @@ -167,6 +168,15 @@ while [[ "$#" -gt 0 ]]; do case $1 in --skip-download) SKIP_DOWNLOAD=true ;; --skip-dockerhub-validation) SKIP_DOCKERHUB_VALIDATION=true ;; + --output) + if [ -z "$2" ]; then + echo "Error: --output requires a file path." + exit 1 + fi + FINAL_PHP_VERSIONS_CONFIG_FILE="$2" + shift 2 + continue + ;; *) echo "Unknown parameter passed: $1"; exit 1 ;; esac shift @@ -184,7 +194,7 @@ PHP_VERSIONS_ACTIVE_JSON_FEED="${PHP_VERSIONS_ACTIVE_JSON_FEED:-"https://www.php # File settings BASE_PHP_VERSIONS_CONFIG_FILE="${BASE_PHP_VERSIONS_CONFIG_FILE:-"$SCRIPT_DIR/conf/php-versions-base-config.yml"}" DOWNLOADED_PHP_VERSIONS_CONFIG_FILE="$SCRIPT_DIR/conf/php-versions-downloaded.yml.tmp" -FINAL_PHP_VERSIONS_CONFIG_FILE="$SCRIPT_DIR/conf/php-versions.yml" +FINAL_PHP_VERSIONS_CONFIG_FILE="${FINAL_PHP_VERSIONS_CONFIG_FILE:-"$SCRIPT_DIR/conf/php-versions.yml"}" # UI Colors function ui_set_yellow { From 0c769d4e9001619a07bb8cedf23ed94bd21925c9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 3 Sep 2025 12:44:49 -0500 Subject: [PATCH 087/304] Update get-php-versions.sh to add --input parameter for specifying the input file path. Improved error handling for missing input file argument. Updated usage documentation accordingly. --- scripts/get-php-versions.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 39e522663..fb4d588b1 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -1,6 +1,6 @@ #!/bin/bash ################################################### -# Usage: get-php-versions.sh [--skip-download] [--skip-dockerhub-validation] [--output PATH] +# Usage: get-php-versions.sh [--skip-download] [--skip-dockerhub-validation] [--input PATH] [--output PATH] ################################################### # This file takes the official latest PHP releases from php.net merges them with our # "base php configuration". These files get merged into a final file called "php-versions.yml" @@ -22,6 +22,7 @@ # 👉 OPTIONS # --skip-download: Skip downloading from php.net and use existing base config # --skip-dockerhub-validation: Skip DockerHub validation (useful for testing/development) +# --input: Input file path (defaults to scripts/conf/php-versions-base-config.yml) # --output: Output file path (defaults to scripts/conf/php-versions.yml) set -oue pipefail @@ -168,6 +169,15 @@ while [[ "$#" -gt 0 ]]; do case $1 in --skip-download) SKIP_DOWNLOAD=true ;; --skip-dockerhub-validation) SKIP_DOCKERHUB_VALIDATION=true ;; + --input) + if [ -z "$2" ]; then + echo "Error: --input requires a file path." + exit 1 + fi + BASE_PHP_VERSIONS_CONFIG_FILE="$2" + shift 2 + continue + ;; --output) if [ -z "$2" ]; then echo "Error: --output requires a file path." From 5019265ed52c8333140cb4c779621afa9de46925 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 3 Sep 2025 13:08:48 -0500 Subject: [PATCH 088/304] Refactor to get the truth of the default variation from the php-versions.yml file --- .../workflows/service_docker-build-and-publish.yml | 5 ----- scripts/assemble-docker-tags.sh | 11 +++++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 6296f8d41..8dddbcad1 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -15,10 +15,6 @@ on: type: string default: 'scripts/conf/php-versions.yml' description: 'The path to the PHP versions file.' - default-image-variation: - type: string - default: 'cli' - description: 'The default PHP variation to use for the Docker image.' registry-repositories: type: string required: true @@ -127,7 +123,6 @@ jobs: ./scripts/assemble-docker-tags.sh env: PHP_VERSIONS_FILE: "${{ inputs.php-versions-file }}" - DEFAULT_IMAGE_VARIATION: ${{ inputs.default-image-variation }} PHP_BUILD_VERSION: ${{ matrix.patch_version }} PHP_BUILD_VARIATION: ${{ matrix.php_variation }} PHP_BUILD_BASE_OS: ${{ matrix.base_os }} diff --git a/scripts/assemble-docker-tags.sh b/scripts/assemble-docker-tags.sh index aa248b296..2894724fe 100755 --- a/scripts/assemble-docker-tags.sh +++ b/scripts/assemble-docker-tags.sh @@ -17,9 +17,17 @@ set -oe pipefail # Environment Settings # Required variables to set -DEFAULT_IMAGE_VARIATION="${DEFAULT_IMAGE_VARIATION:-"cli"}" PHP_VERSIONS_FILE="${PHP_VERSIONS_FILE:-"scripts/conf/php-versions.yml"}" +# Validate PHP Versions file exists +if [ ! -f "$PHP_VERSIONS_FILE" ]; then + echo "🛑 ERROR: PHP Versions file not found at $PHP_VERSIONS_FILE" + exit 1 +fi + +# Determine default image variation +DEFAULT_IMAGE_VARIATION="${DEFAULT_IMAGE_VARIATION:-$(yq e -r '.php_variations[] | select(.default == true) | .name' "$PHP_VERSIONS_FILE" 2>/dev/null | head -n1 || true)}" + # Convert comma-separated DOCKER_REGISTRY_REPOSITORIES string to an array IFS=',' read -ra DOCKER_REGISTRY_REPOSITORIES <<< "${DOCKER_REGISTRY_REPOSITORIES:-"docker.io/serversideup/php,ghcr.io/serversideup/php"}" DOCKER_TAG_PREFIX="${DOCKER_TAG_PREFIX:-""}" @@ -222,7 +230,6 @@ help_menu() { echo " --stable-release Flag the tags for a stable release" echo echo "Environment Variables (Defaults):" - echo " DEFAULT_IMAGE_VARIATION The default PHP image variation (default: cli)" echo " DOCKER_REGISTRY_REPOSITORIES Names of images to tag (default: 'docker.io/serversideup/php' 'ghcr.io/serversideup/php')" echo " PHP_VERSIONS_FILE Path to PHP versions file (default: scripts/conf/php-versions.yml)" } From 02fa1f7586c2e5f4428b12700497671846673c5e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 3 Sep 2025 15:02:34 -0500 Subject: [PATCH 089/304] Set the repository build version --- src/variations/frankenphp/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 7801b59bd..5547c8876 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -113,6 +113,7 @@ FROM common AS final ARG DEPENDENCY_PACKAGES_ALPINE='shadow libstdc++' ARG DEPENDENCY_PACKAGES_DEBIAN='libstdc++6' ARG DEPENDENCY_PHP_EXTENSIONS='opcache pcntl pdo_mysql pdo_pgsql redis zip' +ARG REPOSITORY_BUILD_VERSION='dev' LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ org.opencontainers.image.description="Supercharge your PHP experience. Based off the official PHP images, serversideup/php includes pre-configured PHP extensions and settings for enhanced performance and security. Optimized for Laravel and WordPress." \ @@ -197,6 +198,9 @@ RUN \ mkdir -p "${COMPOSER_HOME}" && \ chown -R www-data:www-data "${COMPOSER_HOME}" && \ \ + # Set the image version + echo "${REPOSITORY_BUILD_VERSION}" > /etc/serversideup-php-version && \ + \ # Install PHP Extension installer docker-php-serversideup-install-php-ext-installer; \ # Install default PHP extensions From f281c6ad1cb89b97b64109f09dd19069c9c0d9d8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 3 Sep 2025 15:18:52 -0500 Subject: [PATCH 090/304] Update docker-php-serversideup-set-file-permissions script to support additional PHP variations (fpm-apache and fpm-nginx) for improved file permission management across different server configurations. --- .../local/bin/docker-php-serversideup-set-file-permissions | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index a15ad6376..136c23cd4 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -89,7 +89,7 @@ case "$OS" in /var/www $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; - apache) + apache | fpm-apache) DIRS=" /composer /etc/apache2 @@ -154,7 +154,7 @@ case "$OS" in /var/www $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; - apache) + apache | fpm-apache) DIRS=" /composer /etc/apache2 @@ -165,7 +165,7 @@ case "$OS" in /var/www $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; - nginx) + nginx | fpm-nginx) DIRS=" /composer /etc/nginx/ From 186fe8f8300d9f412a60243bb9175e5392ab31cd Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 4 Sep 2025 05:38:32 -0500 Subject: [PATCH 091/304] Update GitHub Actions workflows to use actions/checkout@v5 for improved performance and compatibility. --- .github/workflows/action_update-dockerhub-readme.yml | 2 +- .github/workflows/scheduled-task_update-sponsors.yml | 2 +- .github/workflows/service_docker-build-and-publish.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/action_update-dockerhub-readme.yml b/.github/workflows/action_update-dockerhub-readme.yml index 904e195b6..b7ebb6c98 100644 --- a/.github/workflows/action_update-dockerhub-readme.yml +++ b/.github/workflows/action_update-dockerhub-readme.yml @@ -14,7 +14,7 @@ jobs: name: Push README to Docker Hub steps: - name: git checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: main diff --git a/.github/workflows/scheduled-task_update-sponsors.yml b/.github/workflows/scheduled-task_update-sponsors.yml index c76e90246..6461482da 100644 --- a/.github/workflows/scheduled-task_update-sponsors.yml +++ b/.github/workflows/scheduled-task_update-sponsors.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Generate Sponsors 💖 uses: JamesIves/github-sponsors-readme-action@v1 diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 8dddbcad1..baa478fb4 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -39,7 +39,7 @@ jobs: php-version-map-json: ${{ steps.get-php-versions.outputs.php-version-map-json }} steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ inputs.ref }} @@ -80,7 +80,7 @@ jobs: steps: - name: Check out code. - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ inputs.ref }} From ec59633a1ee9749024e4079c01ac0555ded96ae8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 4 Sep 2025 05:38:57 -0500 Subject: [PATCH 092/304] Update GitHub Actions workflow to use actions/download-artifact@v5 for improved functionality and compatibility. --- .github/workflows/service_docker-build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index baa478fb4..dbd4d7fa5 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -85,7 +85,7 @@ jobs: ref: ${{ inputs.ref }} - name: Download PHP Versions file - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: php-versions.yml path: ./artifacts From b85a7cb08529e29716668070ff81dc0be6c92412 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 4 Sep 2025 06:16:30 -0500 Subject: [PATCH 093/304] Update PHP extension installer version to 2.9.7 for improved functionality and compatibility. --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index ddb8eb1bc..64c6b882c 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.9.4" +PHP_EXT_INSTALLER_VERSION="2.9.7" ############ # Main From f58e227168d940166b5face54ec9e70aa76fb2d6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 4 Sep 2025 11:22:32 -0500 Subject: [PATCH 094/304] Refactor dev.sh to replace CUSTOM_REGISTRY with PUSH_TO_REGISTRY flag for improved image pushing logic. Update help menu to reflect changes in registry options. --- scripts/dev.sh | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/scripts/dev.sh b/scripts/dev.sh index 860a7b402..f8b207540 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -26,7 +26,7 @@ PHP_BUILD_BASE_OS="" PHP_BUILD_PREFIX="" DOCKER_REPOSITORY="${DOCKER_REPOSITORY:-"serversideup/php"}" DOCKER_ADDITIONAL_BUILD_ARGS=() -CUSTOM_REGISTRY="" +PUSH_TO_REGISTRY=false PLATFORM="" # UI Colors @@ -113,14 +113,11 @@ build_docker_image() { --file "$PROJECT_ROOT_DIR/src/variations/$PHP_BUILD_VARIATION/Dockerfile" \ "$PROJECT_ROOT_DIR" echo_color_message green "✅ Docker Image Built: $build_tag" - - if [ -n "$CUSTOM_REGISTRY" ]; then - registry_tag="${CUSTOM_REGISTRY}/${build_tag}" - echo_color_message yellow "🏷️ Tagging image for custom registry: $registry_tag" - docker tag "$build_tag" "$registry_tag" - echo_color_message yellow "🚀 Pushing image to custom registry: $registry_tag" - docker push "$registry_tag" - echo_color_message green "✅ Image pushed to custom registry: $registry_tag" + + if [ "$PUSH_TO_REGISTRY" = true ]; then + echo_color_message yellow "🚀 Pushing image to registry: $build_tag" + docker push "$build_tag" + echo_color_message green "✅ Image pushed to registry: $build_tag" fi } @@ -139,10 +136,8 @@ help_menu() { echo " --prefix Set the prefix for the Docker image (e.g., beta)" echo " --registry Set a custom registry (e.g., localhost:5000)" echo " --platform Set the platform (default: detected from system architecture)" + echo " --push Push the image to the registry" echo " --* Any additional options will be passed to the docker buildx command" - echo - echo "Environment Variables:" - echo " DOCKER_REPOSITORY The Docker repository (default: serversideup/php)" } ########################## @@ -167,9 +162,10 @@ while [[ $# -gt 0 ]]; do shift 2 ;; --registry) - CUSTOM_REGISTRY="$2" + DOCKER_REPOSITORY="$2" shift 2 ;; + --platform) PLATFORM="$2" shift 2 From 25a5fcef1fc2ac3674d801ed8b9b28c20201f49c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 4 Sep 2025 11:22:43 -0500 Subject: [PATCH 095/304] Enhance docker-php-serversideup-set-file-permissions script to support additional --dir parameter for specifying extra directories. Update usage documentation accordingly for improved clarity. --- ...cker-php-serversideup-set-file-permissions | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 136c23cd4..6cd359391 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -2,7 +2,7 @@ set -eu ################################################### -# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP --service SERVICE +# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP --service SERVICE [--dir PATH]... ################################################### # This script is intended to be called on build for sysadmins who want to # change the UID and GID of a specific user. This is useful for when you @@ -13,7 +13,7 @@ script_name="docker-php-serversideup-set-file-permissions" # Usage function usage() { - echo "Usage: $0 --owner USER:GROUP --service SERVICE" + echo "Usage: $0 --owner USER:GROUP --service SERVICE [--dir PATH]..." exit 1 } @@ -23,10 +23,10 @@ if [ "$(id -u)" -ne 0 ]; then exit 1 fi -# Check for minimum number of arguments -if [ "$#" -ne 4 ]; then - usage -fi +# Initialize variables to avoid unbound variable errors with 'set -u' +OWNER="" +SERVICE="" +EXTRA_DIRS="" # Parse arguments while [ "$#" -gt 0 ]; do @@ -35,10 +35,26 @@ while [ "$#" -gt 0 ]; do OWNER="$2" shift 2 ;; + --owner=*) + OWNER="${1#--owner=}" + shift 1 + ;; --service) SERVICE="$2" shift 2 ;; + --service=*) + SERVICE="${1#--service=}" + shift 1 + ;; + --dir) + EXTRA_DIRS="$EXTRA_DIRS $2" + shift 2 + ;; + --dir=*) + EXTRA_DIRS="$EXTRA_DIRS ${1#--dir=}" + shift 1 + ;; *) usage ;; @@ -196,5 +212,10 @@ case "$OS" in ;; esac +# Append any extra directories provided via --dir flags +if [ -n "$EXTRA_DIRS" ]; then + DIRS="$DIRS $EXTRA_DIRS" +fi + # Change ownership of the directories change_ownership $DIRS \ No newline at end of file From e4c1718e1ef23397e3594db5ced89f84bea249fc Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 30 Sep 2025 13:28:15 -0500 Subject: [PATCH 096/304] Updated FrankenPHP version to 1.9.1 --- src/variations/frankenphp/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 5547c8876..817705fce 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -2,7 +2,7 @@ ARG BASE_OS_VERSION='trixie' ARG PHP_VERSION='8.4' ARG BASE_IMAGE="php:${PHP_VERSION}-zts-${BASE_OS_VERSION}" -ARG FRANKENPHP_VERSION='1.9.0' +ARG FRANKENPHP_VERSION='1.9.1' ARG GOLANG_VERSION='1.25' ######################## From ecc47649a535bf8153703ec3acb1b457e16d18e6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 30 Sep 2025 15:10:52 -0500 Subject: [PATCH 097/304] Changed source compile method to use xcaddy --- src/variations/frankenphp/Dockerfile | 35 ++++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 817705fce..fc95df6d5 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -10,6 +10,7 @@ ARG GOLANG_VERSION='1.25' ######################## FROM ${BASE_IMAGE} AS common ARG REPOSITORY_BUILD_VERSION='dev' +ARG FRANKENPHP_VERSION # copy our scripts COPY --chmod=755 src/common/ / @@ -35,7 +36,8 @@ RUN set -eux; \ chmod -R 755 /var/www/; \ \ # Set the image version - echo "${REPOSITORY_BUILD_VERSION}" > /etc/serversideup-php-version; + echo "${REPOSITORY_BUILD_VERSION}" > /etc/serversideup-php-version; \ + echo "${FRANKENPHP_VERSION}" > /etc/serversideup-php-frankenphp-version; # \ # # Make composer cache directory # mkdir -p "${COMPOSER_HOME}"; \ @@ -59,6 +61,9 @@ COPY --from=golang-image /usr/local/go /usr/local/go ENV PATH="/usr/local/go/bin:${PATH}" ENV GOTOOLCHAIN="local" +# Copy xcaddy in the builder image +COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy + # Install dependencies & Download FrankenPHP RUN docker-php-serversideup-dep-install-alpine "$PHPIZE_DEPS ${BUILD_DEPENDENCY_PACKAGES_ALPINE}" && \ docker-php-serversideup-dep-install-debian "${BUILD_DEPENDENCY_PACKAGES_DEBIAN}" @@ -81,28 +86,28 @@ RUN curl -s https://api.github.com/repos/e-dant/watcher/releases/latest | \ # Download and build FrankenPHP WORKDIR /go/src/app -ENV CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS" \ - CGO_CPPFLAGS=$PHP_CPPFLAGS \ - GOBIN=/usr/local/bin +ENV GOBIN=/usr/local/bin RUN if cat /etc/os-release | grep -q 'debian'; then \ - export CGO_LDFLAGS="-L/usr/local/lib -lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"; \ export ADDITIONAL_BUILD_FLAGS=''; \ elif cat /etc/os-release | grep -q 'alpine'; then \ - export CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"; \ export ADDITIONAL_BUILD_FLAGS="-extldflags '-Wl,-z,stack-size=0x80000'"; \ - else \ - echo "Unsupported OS"; \ - exit 1; \ fi; \ git clone --depth 1 --branch v${FRANKENPHP_VERSION} \ https://github.com/php/frankenphp.git .; \ - go mod download;\ - cd /go/src/app/caddy/frankenphp; \ - ../../go.sh install -ldflags "-w -s $ADDITIONAL_BUILD_FLAGS -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true; \ - frankenphp version; \ - frankenphp build-info; \ - setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp + CGO_ENABLED=1 \ + XCADDY_SETCAP=1 \ + XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ + CGO_CFLAGS="$(php-config --includes) $ADDITIONAL_BUILD_FLAGS" \ + CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ + xcaddy build \ + --output /usr/local/bin/frankenphp \ + --with github.com/dunglas/frankenphp=./ \ + --with github.com/dunglas/frankenphp/caddy=./caddy/ \ + --with github.com/dunglas/caddy-cbrotli \ + # Mercure and Vulcain are included in the official build, but feel free to remove them + --with github.com/dunglas/mercure/caddy \ + --with github.com/dunglas/vulcain/caddy # WORKDIR /go/src/app/caddy From 1ff27893e22886bd33fda3910d69e4cdd636227a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 30 Sep 2025 15:28:56 -0500 Subject: [PATCH 098/304] Update PHP extension installer version to 2.9.9 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index 64c6b882c..ced09ad7d 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.9.7" +PHP_EXT_INSTALLER_VERSION="2.9.9" ############ # Main From 35db83fdccb3b4a7dc46cc1d7664f5172a948a9b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 30 Sep 2025 15:54:11 -0500 Subject: [PATCH 099/304] Refactor docker-php-serversideup-set-file-permissions script to auto-detect service type and update usage documentation for clarity. Adjust related documentation to reflect changes in file permission management. --- .../3.guide/100.migrating-from-v2-to-v3.md | 2 +- .../2.understanding-file-permissions.md | 6 +- .../2.installing-additional-php-extensions.md | 2 +- .../docs/7.reference/2.command-reference.md | 6 +- ...cker-php-serversideup-set-file-permissions | 116 +++++++++++++++--- 5 files changed, 106 insertions(+), 26 deletions(-) diff --git a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md index 6a3869027..1c3d85d60 100644 --- a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md +++ b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md @@ -151,7 +151,7 @@ ARG GROUP_ID # Switch to root so we can set the user ID and group ID USER root RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ - docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID USER www-data ############################################ diff --git a/docs/content/docs/3.guide/2.understanding-file-permissions.md b/docs/content/docs/3.guide/2.understanding-file-permissions.md index 80b50de37..a76a384d9 100644 --- a/docs/content/docs/3.guide/2.understanding-file-permissions.md +++ b/docs/content/docs/3.guide/2.understanding-file-permissions.md @@ -29,7 +29,7 @@ It's a bummer that we can only set the user and group ID at build time, but it's #### How it works - By default, all our images run `www-data` as the user (`33:33` for Debian and `82:82` for Alpine) - We provide a script that can be called at build time to change the UID and GID of `www-data` (called `docker-php-serversideup-set-id`) -- If you need to update permissions of service files (example: NGINX, Apache, Unit, etc), you can run the `docker-php-serversideup-set-file-permissions` at build +- If you need to update permissions of service files (example: NGINX, Apache, Unit, etc), you can run the `docker-php-serversideup-set-file-permissions` at build. This will automatically detect the service and update the file permissions accordingly. - We will use a multi-stage build to ensure that the `docker-php-serversideup-set-id` script is not executed in the construction of the final image ## Example @@ -62,8 +62,8 @@ ARG GROUP_ID # the file permissions for NGINX RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ \ - # Update the file permissions for our NGINX service to match the new UID/GID - docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + # Update the file permissions to match the new UID/GID + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID # Drop back to our unprivileged user USER www-data diff --git a/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md b/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md index b6db667b9..c66c16367 100644 --- a/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md +++ b/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md @@ -157,7 +157,7 @@ ARG GROUP_ID # Switch to root so we can set the user ID and group ID USER root RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ - docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID # Switch back to the unprivileged www-data user USER www-data diff --git a/docs/content/docs/7.reference/2.command-reference.md b/docs/content/docs/7.reference/2.command-reference.md index a1b4e437c..676126621 100644 --- a/docs/content/docs/7.reference/2.command-reference.md +++ b/docs/content/docs/7.reference/2.command-reference.md @@ -68,11 +68,13 @@ This command is used to set the file permissions of a service in the container. label: Example - Setting the file permissions of the NGINX service --- ```bash -# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP --service SERVICE -docker-php-serversideup-set-file-permissions --owner 1000:1000 --service nginx +# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP +docker-php-serversideup-set-file-permissions --owner 1000:1000 ``` :: +The above command will automatically detect the service and update the file permissions accordingly. + ## docker-php-serversideup-set-id This command is used to set the user and group ID of the `www-data` user in the container. This is useful for development environments where you want to match the user and group ID of the host machine. diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 6cd359391..45e0b504d 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -2,7 +2,7 @@ set -eu ################################################### -# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP --service SERVICE [--dir PATH]... +# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP [--service SERVICE] [--dir PATH]... ################################################### # This script is intended to be called on build for sysadmins who want to # change the UID and GID of a specific user. This is useful for when you @@ -11,12 +11,83 @@ set -eu # so developers don't need to worry about permissions issues. script_name="docker-php-serversideup-set-file-permissions" -# Usage function +############################ +# Functions +############################ usage() { - echo "Usage: $0 --owner USER:GROUP --service SERVICE [--dir PATH]..." - exit 1 + echo "Usage: $0 --owner USER:GROUP [--service SERVICE] [--dir PATH]..." + echo "" + echo "Options:" + echo " --owner USER:GROUP Set the owner (required)" + echo " --service SERVICE Specify service type (optional, auto-detected if not provided)" + echo " Valid services: cli, fpm, apache, fpm-apache, nginx, fpm-nginx, unit, frankenphp" + echo " --dir PATH Additional directory to change ownership (can be used multiple times)" +} + +detect_service() { + # Detection priority order (most specific to least specific): + + # Check for FrankenPHP (binary in path) + if command -v frankenphp >/dev/null 2>&1; then + echo "frankenphp" + return 0 + fi + + # Check for NGINX Unit + if [ -d "/etc/unit" ] || command -v unitd >/dev/null 2>&1; then + echo "unit" + return 0 + fi + + # Check for Apache (with PHP-FPM config indicates fpm-apache) + if command -v apache2 >/dev/null 2>&1 || command -v httpd >/dev/null 2>&1; then + if [ -f "/usr/local/etc/php-fpm.conf" ] || command -v php-fpm >/dev/null 2>&1; then + echo "fpm-apache" + return 0 + else + echo "apache" + return 0 + fi + fi + + # Check for Nginx (with PHP-FPM config indicates fpm-nginx) + if [ -d "/etc/nginx" ] || command -v nginx >/dev/null 2>&1; then + if [ -f "/usr/local/etc/php-fpm.conf" ] || command -v php-fpm >/dev/null 2>&1; then + echo "fpm-nginx" + return 0 + else + echo "nginx" + return 0 + fi + fi + + # Check for standalone PHP-FPM + if [ -f "/usr/local/etc/php-fpm.conf" ] || command -v php-fpm >/dev/null 2>&1; then + echo "fpm" + return 0 + fi + + # Default to CLI if nothing else is detected + echo "cli" + return 0 +} + +change_ownership() { + for path in "$@"; do + if [ -e "$path" ]; then + chown -R "$OWNER" "$path" + echo "$script_name: Ownership of $path changed to $OWNER." + else + echo "$script_name: Directory not found: $path" + exit 1 + fi + done } +############################ +# Initialize +############################ + # Check for root privileges if [ "$(id -u)" -ne 0 ]; then echo "${script_name}: This script must be run as root within the container. Be sure to set \"USER root\" in your Dockerfile before running this script." @@ -61,9 +132,16 @@ while [ "$#" -gt 0 ]; do esac done -# Ensure OWNER and SERVICE are set -if [ -z "$OWNER" ] || [ -z "$SERVICE" ]; then +# Ensure OWNER is set +if [ -z "$OWNER" ]; then usage + exit 1 +fi + +# Auto-detect service if not provided +if [ -z "$SERVICE" ]; then + SERVICE=$(detect_service) + echo "$script_name: Auto-detected service: $SERVICE" fi # Detect the operating system using /etc/os-release @@ -75,18 +153,6 @@ else exit 1 fi -change_ownership() { - for path in "$@"; do - if [ -e "$path" ]; then - chown -R "$OWNER" "$path" - echo "$script_name: Ownership of $path changed to $OWNER." - else - echo "$script_name: Directory not found: $path" - exit 1 - fi - done -} - # Determine directories based on OS and SERVICE case "$OS" in debian) @@ -117,7 +183,7 @@ case "$OS" in /var/www $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; - nginx) + nginx | fpm-nginx) DIRS=" /composer /etc/nginx @@ -193,6 +259,17 @@ case "$OS" in /var/www/ $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; + unit) + DIRS=" + /composer + /etc/unit + /etc/ssl/private + /var/lib/unit + /var/log/unit + /var/run/unit + /var/www + $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" + ;; frankenphp) DIRS=" /composer @@ -218,4 +295,5 @@ if [ -n "$EXTRA_DIRS" ]; then fi # Change ownership of the directories +# shellcheck disable=SC2086 change_ownership $DIRS \ No newline at end of file From 1017afa47c878b54ef44efa0437776d07a1a8d2c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 30 Sep 2025 16:29:09 -0500 Subject: [PATCH 100/304] Update Apache configuration to use dynamic HEALTHCHECK_PATH variable for healthcheck logging. Adjust logging settings in ssl-full.conf, http.conf, and https.conf to improve request handling and reduce log noise for healthcheck requests. --- .../fpm-apache/etc/apache2/sites-available/ssl-full.conf | 5 ++++- .../fpm-apache/etc/apache2/vhost-templates/http.conf | 2 +- .../fpm-apache/etc/apache2/vhost-templates/https.conf | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf index 8b5b3af0f..180a477ae 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf @@ -17,10 +17,13 @@ RewriteCond %{SERVER_NAME} !=localhost RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L] + # Set environment variable for healthcheck requests + SetEnvIf Request_URI "^${HEALTHCHECK_PATH}$" dontlog + # Configure Log Settings LogFormat "%l %u %t %v %a \"%r\" %>s %b" comonvhost ErrorLog /dev/stderr - TransferLog /dev/stdout + CustomLog /dev/stdout comonvhost env=!dontlog LogLevel ${LOG_OUTPUT_LEVEL} diff --git a/src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf b/src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf index 56101aa74..964d94235 100644 --- a/src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf +++ b/src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf @@ -31,7 +31,7 @@ ProxyPassReverse "/healthcheck" "fcgi://localhost:9000" ProxyTimeout 1800 # Set environment variable for healthcheck requests -SetEnvIf Request_URI "^/healthcheck$" dontlog +SetEnvIf Request_URI "^${HEALTHCHECK_PATH}$" dontlog # CustomLog directive to conditionally log requests LogFormat "%l %u %t %v %a \"%r\" %>s %b" comonvhost diff --git a/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf b/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf index 79277b6b9..5abefdeaa 100644 --- a/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf +++ b/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf @@ -36,7 +36,7 @@ ProxyPassReverse "/healthcheck" "fcgi://localhost:9000" ProxyTimeout 1800 # Set environment variable for healthcheck requests -SetEnvIf Request_URI "^/healthcheck$" dontlog +SetEnvIf Request_URI "^${HEALTHCHECK_PATH}$" dontlog # CustomLog directive to conditionally log requests LogFormat "%l %u %t %v %a \"%r\" %>s %b" comonvhost From f868e0ab11ac980b067844abbc2ee7aecaa6e414 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 1 Oct 2025 11:31:31 -0500 Subject: [PATCH 101/304] Update NGINX access logging to conditionally disable logs for healthcheck requests --- src/s6/etc/entrypoint.d/10-init-webserver-config.sh | 4 ++-- src/variations/fpm-nginx/etc/nginx/nginx.conf.template | 8 +++++++- .../fpm-nginx/etc/nginx/site-opts.d/http.conf.template | 2 +- .../fpm-nginx/etc/nginx/site-opts.d/https.conf.template | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index 3151f8ab3..4d277f6c3 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -42,8 +42,8 @@ process_template() { return 1 fi - # Get all environment variables starting with 'NGINX_', 'SSL_', `LOG_`, and 'APACHE_' - subst_vars=$(env | grep -E '^(PHP_|NGINX_|SSL_|LOG_|APACHE_)' | cut -d= -f1 | awk '{printf "${%s},",$1}' | sed 's/,$//') + # Get all environment variables starting with 'NGINX_', 'SSL_', `LOG_`, 'APACHE_', and 'HEALTHCHECK_PATH' + subst_vars=$(env | grep -E '^(PHP_|NGINX_|SSL_|LOG_|APACHE_|HEALTHCHECK_PATH)' | cut -d= -f1 | awk '{printf "${%s},",$1}' | sed 's/,$//') # Validate that all required variables are set for var_name in $(echo "$subst_vars" | tr ',' ' '); do diff --git a/src/variations/fpm-nginx/etc/nginx/nginx.conf.template b/src/variations/fpm-nginx/etc/nginx/nginx.conf.template index 4fe4f8e4a..8d166c498 100644 --- a/src/variations/fpm-nginx/etc/nginx/nginx.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/nginx.conf.template @@ -19,7 +19,13 @@ http { '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; - access_log /dev/stdout main; + # Disable access logs for healthcheck endpoint + map $request_uri $loggable { + $HEALTHCHECK_PATH 0; + default 1; + } + + access_log /dev/stdout main if=$loggable; sendfile on; #tcp_nopush on; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template index 91b7498b5..9cad559f9 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template @@ -13,7 +13,7 @@ charset utf-8; # Set max upload to 2048M client_max_body_size 2048M; -# Healthchecks: Set /healthcheck to be the healthcheck URL +# Healthcheck: Set /healthcheck to be the static health check URL location /healthcheck { access_log off; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template index f98a71f12..7b8eaca4e 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template @@ -19,7 +19,7 @@ charset utf-8; # Set max upload to 2048M client_max_body_size 2048M; -# Healthchecks: Set /healthcheck to be the healthcheck URL +# Healthcheck: Set /healthcheck to be the static health check URL location /healthcheck { access_log off; From 5b4f04ceefdf4546b5fb8d0799c84034fb44e9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robson=20Ten=C3=B3rio?= Date: Wed, 1 Oct 2025 14:02:21 -0300 Subject: [PATCH 102/304] Add `NGINX_ERROR_LOG` and `NGINX_ACCESS_LOG`. (#534) Co-authored-by: Jay Rogers <3174134+jaydrogers@users.noreply.github.com> --- .../docs/7.reference/1.environment-variable-specification.md | 2 ++ src/variations/fpm-nginx/Dockerfile | 2 ++ src/variations/fpm-nginx/etc/nginx/nginx.conf.template | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 214b898e5..4d8cd56c3 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -53,6 +53,8 @@ We like to customize our images on a per app basis using environment variables. `FRANKENPHP_CONFIG`
*Default: ""*|Set the configuration for FrankenPHP. (Official docs)|frankenphp `HEALTHCHECK_PATH`
*Default: "/healthcheck"*|Set the path for the health check endpoint. (Official docs)|all (except `cli` and `frankenphp`) `LOG_OUTPUT_LEVEL`
*Default: "warn"*|Set your container output different verbosity levels: debug, info, off |all +`NGINX_ACCESS_LOG`
*Default: "/dev/stdout"*|Set the default output stream for access log.|fpm-nginx +`NGINX_ERROR_LOG`
*Default: "/dev/stderr"*|Set the default output stream for error log.|fpm-nginx `NGINX_FASTCGI_BUFFERS`
*Default: "8 8k"*|Sets the number and size of the buffers used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx `NGINX_FASTCGI_BUFFER_SIZE`
*Default: "8k"*|Sets the size of the buffer used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx `NGINX_SERVER_TOKENS`
*Default: "off"*|Display NGINX version in responses. (Official Docs)|fpm-nginx diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index 29ebcb707..c4476e523 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -120,6 +120,8 @@ ENV APP_BASE_DIR=/var/www/html \ DISABLE_DEFAULT_CONFIG=false \ LOG_OUTPUT_LEVEL=warn \ HEALTHCHECK_PATH="/healthcheck" \ + NGINX_ACCESS_LOG="/dev/stdout" \ + NGINX_ERROR_LOG="/dev/stderr" \ NGINX_FASTCGI_BUFFERS="8 8k" \ NGINX_FASTCGI_BUFFER_SIZE="8k" \ NGINX_SERVER_TOKENS=off \ diff --git a/src/variations/fpm-nginx/etc/nginx/nginx.conf.template b/src/variations/fpm-nginx/etc/nginx/nginx.conf.template index 8d166c498..092c1370a 100644 --- a/src/variations/fpm-nginx/etc/nginx/nginx.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/nginx.conf.template @@ -1,6 +1,6 @@ worker_processes auto; -error_log /dev/stderr $LOG_OUTPUT_LEVEL; +error_log $NGINX_ERROR_LOG $LOG_OUTPUT_LEVEL; pid /var/run/nginx.pid; daemon off; @@ -25,7 +25,7 @@ http { default 1; } - access_log /dev/stdout main if=$loggable; + access_log $NGINX_ACCESS_LOG main if=$loggable; sendfile on; #tcp_nopush on; From a3431d974f459aa32387f927eab4bdb85f9dc103 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 1 Oct 2025 12:51:43 -0500 Subject: [PATCH 103/304] Add`PHP_FPM_PM_STATUS_PATH`, `PHP_OPCACHE_ENABLE_FILE_OVERRIDE`, `PHP_REALPATH_CACHE_TTL`, `PHP_ZEND_DETECT_UNICODE`, and `PHP_ZEND_MULTIBYTE`. Ref https://github.com/serversideup/docker-php/discussions/545#discussioncomment-14565990 --- .../1.environment-variable-specification.md | 5 +++++ .../local/etc/php/conf.d/serversideup-docker-php.ini | 11 ++++++++--- .../etc/php-fpm.d/docker-php-serversideup-pool.conf | 2 +- src/variations/cli/Dockerfile | 4 ++++ src/variations/fpm-apache/Dockerfile | 5 +++++ src/variations/fpm-nginx/Dockerfile | 5 +++++ src/variations/fpm/Dockerfile | 5 +++++ src/variations/frankenphp/Dockerfile | 4 ++++ 8 files changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 4d8cd56c3..6c45ccbdd 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -70,6 +70,7 @@ We like to customize our images on a per app basis using environment variables. `PHP_FPM_PM_MAX_SPARE_SERVERS`
*Default: "3"*|The desired maximum number of idle server processes. Used only when pm is set to dynamic. (Official docs)|fpm* `PHP_FPM_PM_MIN_SPARE_SERVERS`
*Default: "1"*|The desired minimum number of idle server processes. Used only when pm is set to dynamic. (Official docs)|fpm* `PHP_FPM_PM_START_SERVERS`
*Default: "2"*|The number of child processes created on startup. Used only when pm is set to dynamic. (Official docs)|fpm* +`PHP_FPM_PM_STATUS_PATH`
*Default: ""*|The path to the PHP-FPM status page. (Official docs)|fpm* `PHP_FPM_POOL_NAME`
*Default: "www"*|Set the name of your PHP-FPM pool (helpful when running multiple sites on a single server).|fpm* `PHP_FPM_PROCESS_CONTROL_TIMEOUT`
*Default: "10s"*|Set the timeout for the process control commands. (Official docs)|fpm* `PHP_MAX_EXECUTION_TIME`
*Default: "99"*|Set the maximum time in seconds a script is allowed to run before it is terminated by the parser. (Official docs)|all @@ -77,6 +78,7 @@ We like to customize our images on a per app basis using environment variables. `PHP_MAX_INPUT_VARS`
*Default: "1000"*|Set the limits for number of input variables (e.g., POST, GET, or COOKIE variables) that PHP will process in a single request. (Official docs)|all `PHP_MEMORY_LIMIT`
*Default: "256M"*|Set the maximum amount of memory in bytes that a script is allowed to allocate. (Official docs)|all `PHP_OPCACHE_ENABLE`
*Default: "0" (to keep developers sane)*|Enable or disable OPcache. ⚠️ This will set **both values** for `opcache.enable` and `opcache.enable_cli`. (Official docs)|all +`PHP_OPCACHE_ENABLE_FILE_OVERRIDE`
*Default: "0"*|Enable or disable file existence override (file_exists, etc.). (Official docs)|all `PHP_OPCACHE_FORCE_RESTART_TIMEOUT`
*Default: "180"*|The number of seconds to wait for a scheduled restart to begin if the cache isn't active, in seconds. If the timeout is hit, then OPcache assumes that something is wrong and will kill the processes holding locks on the cache to permit a restart. (Official docs)|all `PHP_OPCACHE_INTERNED_STRINGS_BUFFER`
*Default: "8"*|The amount of memory used to store interned strings, in megabytes. (Official docs)|all `PHP_OPCACHE_JIT`
*Default: "off"*|Enable or disable the JIT compiler. (Official docs)|all @@ -88,8 +90,11 @@ We like to customize our images on a per app basis using environment variables. `PHP_OPCACHE_VALIDATE_TIMESTAMPS`
*Default: "1"*|Whether OPcache checks for changes to files, or requires reload of PHP to revalidate OPcache. (Official docs)|all `PHP_OPEN_BASEDIR`
*Default: "None"* |Limit the files that can be accessed by PHP to the specified directory-tree, including the file itself. `open_basedir` is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed. (Official docs)| all `PHP_POST_MAX_SIZE`
*Default: "100M"*|Sets max size of post data allowed. (Official docs)|all +`PHP_REALPATH_CACHE_TTL`
*Default: "120"*|The duration of time, in seconds for which to cache realpath information for a given file or directory. (Official docs)|all `PHP_SESSION_COOKIE_SECURE`
*Default: 1 (true)*|Specifies whether cookies should only be sent over secure connections. (Official docs)|all `PHP_UPLOAD_MAX_FILE_SIZE`
*Default: "100M"*|The maximum size of an uploaded file. (Official docs)|all +`PHP_ZEND_DETECT_UNICODE`
*Default: ""*|Check for BOM (Byte Order Mark) and see if the file contains valid multibyte characters. This detection is performed before processing of __halt_compiler(). Available only in Zend Multibyte mode. (Official docs)|all +`PHP_ZEND_MULTIBYTE`
*Default: "Off"*|Enable or disable Zend Multibyte. (Official docs)|all `S6_BEHAVIOUR_IF_STAGE2_FAILS`
*Default: "2" (stop the container)*|Determines what the container should do if one of the service scripts fails (Official docs)|fpm-nginx,
fpm-apache `S6_CMD_WAIT_FOR_SERVICES_MAXTIME`
*Default: "0"*|The maximum time (in milliseconds) the services could take to bring up before proceeding to CMD executing (Official docs)|fpm-nginx,
fpm-apache `S6_VERBOSITY`
*Default: "1"*|Set the verbosity of "S6 Overlay" (the init system these images are based on). The default is "1" (print warnings and errors). The scale goes from 1 to 5, but the output will quickly become very noisy. If you're having issues, start here. You can also customize many other variables. (Official docs)|fpm-nginx,
fpm-apache diff --git a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini index 2b2d0a642..78563ba83 100644 --- a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini +++ b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini @@ -354,7 +354,7 @@ disable_classes = ; file or directory. For systems with rarely changing files, consider increasing this ; value. ; https://php.net/realpath-cache-ttl -;realpath_cache_ttl = 120 +realpath_cache_ttl = ${PHP_REALPATH_CACHE_TTL} ; Enables or disables the circular reference collector. ; https://php.net/zend.enable-gc @@ -363,13 +363,18 @@ zend.enable_gc = On ; If enabled, scripts may be written in encodings that are incompatible with ; the scanner. CP936, Big5, CP949 and Shift_JIS are the examples of such ; encodings. To use this feature, mbstring extension must be enabled. -;zend.multibyte = Off +zend.multibyte = ${PHP_ZEND_MULTIBYTE} ; Allows to set the default encoding for the scripts. This value will be used ; unless "declare(encoding=...)" directive appears at the top of the script. ; Only affects if zend.multibyte is set. ;zend.script_encoding = +; Check for BOM (Byte Order Mark) and see if the file contains valid +; multibyte characters. This detection is performed before processing +; of __halt_compiler(). Available only in Zend Multibyte mode. +zend.detect_unicode = ${PHP_ZEND_DETECT_UNICODE} + ; Allows to include or exclude arguments from stack traces generated for exceptions. ; In production, it is recommended to turn this setting on to prohibit the output ; of sensitive information in stack traces @@ -1836,7 +1841,7 @@ opcache.jit_buffer_size=${PHP_OPCACHE_JIT_BUFFER_SIZE} ;opcache.record_warnings=0 ; Allow file existence override (file_exists, etc.) performance feature. -;opcache.enable_file_override=0 +opcache.enable_file_override=${PHP_OPCACHE_ENABLE_FILE_OVERRIDE} ; A bitmask, where each bit enables or disables the appropriate OPcache ; passes diff --git a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf index 500808839..8c95f181c 100644 --- a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf +++ b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf @@ -247,7 +247,7 @@ pm.max_requests = ${PHP_FPM_PM_MAX_REQUESTS} ; anything, but it may not be a good idea to use the .php extension or it ; may conflict with a real PHP file. ; Default Value: not set -pm.status_path = /status +pm.status_path = ${PHP_FPM_PM_STATUS_PATH} ; The ping URI to call the monitoring page of FPM. If this value is not set, no ; URI will be recognized as a ping page. This could be used to test from outside diff --git a/src/variations/cli/Dockerfile b/src/variations/cli/Dockerfile index 68b49e477..f7fa19a6d 100644 --- a/src/variations/cli/Dockerfile +++ b/src/variations/cli/Dockerfile @@ -38,6 +38,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ PHP_OPCACHE_JIT="off" \ @@ -49,8 +50,11 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ + PHP_REALPATH_CACHE_TTL="120" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ + PHP_ZEND_DETECT_UNICODE="" \ + PHP_ZEND_MULTIBYTE="Off" \ SHOW_WELCOME_MESSAGE=true # copy our scripts diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 466f8acfd..b54074346 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -65,6 +65,7 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ + PHP_FPM_PM_STATUS_PATH="/status" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ @@ -72,6 +73,7 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ PHP_OPCACHE_JIT="off" \ @@ -83,8 +85,11 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ + PHP_REALPATH_CACHE_TTL="120" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ + PHP_ZEND_DETECT_UNICODE="" \ + PHP_ZEND_MULTIBYTE="Off" \ S6_BEHAVIOUR_IF_STAGE2_FAILS=2 \ S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \ S6_KEEP_ENV=1 \ diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index c4476e523..bb0250b33 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -137,6 +137,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ + PHP_FPM_PM_STATUS_PATH="/status" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ @@ -144,6 +145,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ PHP_OPCACHE_JIT="off" \ @@ -155,8 +157,11 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ + PHP_REALPATH_CACHE_TTL="120" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ + PHP_ZEND_DETECT_UNICODE="" \ + PHP_ZEND_MULTIBYTE="Off" \ S6_BEHAVIOUR_IF_STAGE2_FAILS=2 \ S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \ S6_KEEP_ENV=1 \ diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index 1f0423837..403793295 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -39,6 +39,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_FPM_PM_MAX_SPARE_SERVERS="3" \ PHP_FPM_PM_MIN_SPARE_SERVERS="1" \ PHP_FPM_PM_START_SERVERS="2" \ + PHP_FPM_PM_STATUS_PATH="/status" \ PHP_FPM_POOL_NAME="www" \ PHP_FPM_PROCESS_CONTROL_TIMEOUT="10s" \ PHP_MAX_EXECUTION_TIME="99" \ @@ -46,6 +47,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ PHP_OPCACHE_JIT="off" \ @@ -57,8 +59,11 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ + PHP_REALPATH_CACHE_TTL="120" \ PHP_SESSION_COOKIE_SECURE=Off \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ + PHP_ZEND_DETECT_UNICODE="" \ + PHP_ZEND_MULTIBYTE="Off" \ SHOW_WELCOME_MESSAGE=true # copy our scripts diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index fc95df6d5..8f51e26f7 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -158,6 +158,7 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ PHP_OPCACHE_INTERNED_STRINGS_BUFFER="8" \ PHP_OPCACHE_JIT="off" \ @@ -169,8 +170,11 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ PHP_OPCACHE_VALIDATE_TIMESTAMPS="1" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ + PHP_REALPATH_CACHE_TTL="120" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ + PHP_ZEND_DETECT_UNICODE="" \ + PHP_ZEND_MULTIBYTE="Off" \ SHOW_WELCOME_MESSAGE=true \ SSL_MODE=off \ SSL_CERTIFICATE_FILE="/etc/ssl/private/self-signed-web.crt" \ From f4d237f1056d338c240e8fb6b115f343f91f1d4f Mon Sep 17 00:00:00 2001 From: Dimas Farhan <67664879+yuuzukatsu@users.noreply.github.com> Date: Thu, 2 Oct 2025 02:06:19 +0700 Subject: [PATCH 104/304] FEAT: add `NGINX_IP_LISTENING_PROTOCOL` (#539) * add NGINX_IPV6 env for toggling nginx IPv6 * Add `NGINX_LISTEN_IP_PROTOCOL` environment variable to configure IP protocol for NGINX. Update entrypoint script to handle IPv4 and IPv6 settings based on this variable. Enhance documentation to reflect the new configuration option. --------- Co-authored-by: Jay Rogers <3174134+jaydrogers@users.noreply.github.com> Co-authored-by: Jay Rogers --- .../1.environment-variable-specification.md | 1 + .../entrypoint.d/10-init-webserver-config.sh | 29 +++++++++++++++++++ src/variations/fpm-nginx/Dockerfile | 1 + 3 files changed, 31 insertions(+) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 6c45ccbdd..1a9b26fff 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -57,6 +57,7 @@ We like to customize our images on a per app basis using environment variables. `NGINX_ERROR_LOG`
*Default: "/dev/stderr"*|Set the default output stream for error log.|fpm-nginx `NGINX_FASTCGI_BUFFERS`
*Default: "8 8k"*|Sets the number and size of the buffers used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx `NGINX_FASTCGI_BUFFER_SIZE`
*Default: "8k"*|Sets the size of the buffer used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx +`NGINX_LISTEN_IP_PROTOCOL`
*Default: "all"*|Set the IP protocol for NGINX to listen on. Valid values are "all", "ipv4", and "ipv6". (Official Docs)|fpm-nginx `NGINX_SERVER_TOKENS`
*Default: "off"*|Display NGINX version in responses. (Official Docs)|fpm-nginx `NGINX_WEBROOT`
*Default: "`/var/www/html/public"*|Sets the root directory for requests. (Official Docs)|fpm-nginx `PHP_DATE_TIMEZONE`
*Default: "UTC"*|Control your timezone. (Official Docs)|all diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index 4d277f6c3..1b1652103 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -171,6 +171,35 @@ if [ "$DISABLE_DEFAULT_CONFIG" = false ]; then process_template /etc/nginx/nginx.conf.template /etc/nginx/nginx.conf process_template /etc/nginx/site-opts.d/http.conf.template /etc/nginx/site-opts.d/http.conf process_template /etc/nginx/site-opts.d/https.conf.template /etc/nginx/site-opts.d/https.conf + + # Configure NGINX IP listening protocol if NGINX is installed + nginx_config_files="/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/https.conf /etc/nginx/sites-available/ssl-full" + case "$NGINX_LISTEN_IP_PROTOCOL" in + all) + # Do nothing, keep both IPv4 and IPv6 + ;; + ipv4) + echo "ℹ️ NOTICE (init-webserver-config): Setting IPv4 only for NGINX configuration..." + for config_file in $nginx_config_files; do + if [ -f "$config_file" ]; then + sed -i '/listen \[::\]:/d' "$config_file" + fi + done + ;; + ipv6) + echo "ℹ️ NOTICE (init-webserver-config): Setting IPv6 only for NGINX configuration..." + for config_file in $nginx_config_files; do + if [ -f "$config_file" ]; then + sed -i '/^[[:space:]]*listen [0-9]/d' "$config_file" + fi + done + ;; + *) + echo "🛑 ERROR ($script_name): Invalid NGINX_LISTEN_IP_PROTOCOL value: $NGINX_LISTEN_IP_PROTOCOL" + return 1 + ;; + esac + enable_nginx_site "$SSL_MODE" else echo "🛑 ERROR ($script_name): Neither Apache nor NGINX could be detected." diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index bb0250b33..af94b368b 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -124,6 +124,7 @@ ENV APP_BASE_DIR=/var/www/html \ NGINX_ERROR_LOG="/dev/stderr" \ NGINX_FASTCGI_BUFFERS="8 8k" \ NGINX_FASTCGI_BUFFER_SIZE="8k" \ + NGINX_LISTEN_IP_PROTOCOL="all" \ NGINX_SERVER_TOKENS=off \ NGINX_WEBROOT=/var/www/html/public \ PHP_DATE_TIMEZONE="UTC" \ From 4bb3173a811e50824fc89ec4a5a597cfa32246f4 Mon Sep 17 00:00:00 2001 From: David Lundgren Date: Wed, 1 Oct 2025 16:14:07 -0500 Subject: [PATCH 105/304] feat: allow nginx `client_max_body_size` to be configurable (#558) * feat: allow nginx `client_max_body_size` to be configurable * Update documentation for `NGINX_CLIENT_MAX_BODY_SIZE` * Update NGINX_CLIENT_MAX_BODY_SIZE default value to 100M in documentation and Dockerfile --------- Co-authored-by: Jay Rogers <3174134+jaydrogers@users.noreply.github.com> Co-authored-by: Jay Rogers --- .../docs/7.reference/1.environment-variable-specification.md | 1 + src/variations/fpm-nginx/Dockerfile | 1 + src/variations/fpm-nginx/etc/nginx/nginx.conf.template | 2 ++ .../fpm-nginx/etc/nginx/site-opts.d/http.conf.template | 3 --- .../fpm-nginx/etc/nginx/site-opts.d/https.conf.template | 3 --- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 1a9b26fff..7b2a6ab44 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -60,6 +60,7 @@ We like to customize our images on a per app basis using environment variables. `NGINX_LISTEN_IP_PROTOCOL`
*Default: "all"*|Set the IP protocol for NGINX to listen on. Valid values are "all", "ipv4", and "ipv6". (Official Docs)|fpm-nginx `NGINX_SERVER_TOKENS`
*Default: "off"*|Display NGINX version in responses. (Official Docs)|fpm-nginx `NGINX_WEBROOT`
*Default: "`/var/www/html/public"*|Sets the root directory for requests. (Official Docs)|fpm-nginx +`NGINX_CLIENT_MAX_BODY_SIZE`
*Default: "100M"*|Sets the max body size for requests. (Official Docs*Default: "UTC"*|Control your timezone. (Official Docs)|all `PHP_DISPLAY_ERRORS`
*Default: Off*|Show PHP errors on screen. (Official docs)|all `PHP_DISPLAY_STARTUP_ERRORS`
*Default: Off*|Even when display_errors is on, errors that occur during PHP's startup sequence are not displayed. (Official docs)| all diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index af94b368b..5430c1666 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -127,6 +127,7 @@ ENV APP_BASE_DIR=/var/www/html \ NGINX_LISTEN_IP_PROTOCOL="all" \ NGINX_SERVER_TOKENS=off \ NGINX_WEBROOT=/var/www/html/public \ + NGINX_CLIENT_MAX_BODY_SIZE="100M" \ PHP_DATE_TIMEZONE="UTC" \ PHP_DISPLAY_ERRORS=Off \ PHP_DISPLAY_STARTUP_ERRORS=Off \ diff --git a/src/variations/fpm-nginx/etc/nginx/nginx.conf.template b/src/variations/fpm-nginx/etc/nginx/nginx.conf.template index 092c1370a..40dee1b6f 100644 --- a/src/variations/fpm-nginx/etc/nginx/nginx.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/nginx.conf.template @@ -27,6 +27,8 @@ http { access_log $NGINX_ACCESS_LOG main if=$loggable; + client_max_body_size $NGINX_CLIENT_MAX_BODY_SIZE; + sendfile on; #tcp_nopush on; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template index 9cad559f9..9ce8a8868 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template @@ -10,9 +10,6 @@ server_name _; charset utf-8; -# Set max upload to 2048M -client_max_body_size 2048M; - # Healthcheck: Set /healthcheck to be the static health check URL location /healthcheck { access_log off; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template index 7b8eaca4e..db3d9e6c9 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template @@ -16,9 +16,6 @@ server_name _; charset utf-8; -# Set max upload to 2048M -client_max_body_size 2048M; - # Healthcheck: Set /healthcheck to be the static health check URL location /healthcheck { access_log off; From 7837fd1d7030de3fcf6a15ef67c84842635039f9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 1 Oct 2025 16:31:52 -0500 Subject: [PATCH 106/304] Fix deprecation notices for `session.sid_length` and `session.sid_bits_per_character`. Uncomment to use defaults. Fixes #560 --- .../usr/local/etc/php/conf.d/serversideup-docker-php.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini index 78563ba83..0bbeec28c 100644 --- a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini +++ b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini @@ -1503,7 +1503,7 @@ session.use_trans_sid = 0 ; Default Value: 32 ; Development Value: 26 ; Production Value: 26 -session.sid_length = 26 +;session.sid_length = 26 ; The URL rewriter will look for URLs in a defined set of HTML tags. ;
is special; if you include them here, the rewriter will @@ -1540,7 +1540,7 @@ session.trans_sid_tags = "a=href,area=href,frame=src,form=" ; Development Value: 5 ; Production Value: 5 ; https://php.net/session.hash-bits-per-character -session.sid_bits_per_character = 5 +;session.sid_bits_per_character = 5 ; Enable upload progress tracking in $_SESSION ; Default Value: On From 87286afbb8b2f48024f9243a8b0a23e18a5b6a0b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 2 Oct 2025 08:34:33 -0500 Subject: [PATCH 107/304] Update PHP extension installer version to 2.9.11 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index ced09ad7d..706f06226 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.9.9" +PHP_EXT_INSTALLER_VERSION="2.9.11" ############ # Main From 2b5cd7b299756adc45c2be28037eb4070b1533e3 Mon Sep 17 00:00:00 2001 From: Ricardo MM Date: Thu, 2 Oct 2025 17:37:49 +0200 Subject: [PATCH 108/304] Update performance.conf (#530) Co-authored-by: Jay Rogers <3174134+jaydrogers@users.noreply.github.com> --- .../fpm-nginx/etc/nginx/server-opts.d/performance.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/variations/fpm-nginx/etc/nginx/server-opts.d/performance.conf b/src/variations/fpm-nginx/etc/nginx/server-opts.d/performance.conf index 46c35f34a..2039879b8 100644 --- a/src/variations/fpm-nginx/etc/nginx/server-opts.d/performance.conf +++ b/src/variations/fpm-nginx/etc/nginx/server-opts.d/performance.conf @@ -11,7 +11,7 @@ location = /robots.txt { } # assets, media -location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ { +location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv|svgz?)$ { add_header Cache-Control "public, max-age=31536000, immutable"; access_log off; log_not_found off; @@ -19,8 +19,8 @@ location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp try_files $uri /index.php?$query_string; } -# svg, fonts -location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ { +# fonts +location ~* \.(?:ttf|ttc|otf|eot|woff2?)$ { add_header Access-Control-Allow-Origin "*"; add_header Cache-Control "public, max-age=31536000, immutable"; access_log off; From ad5fe47c6fcaf778ecaabc8d30fe738079497f48 Mon Sep 17 00:00:00 2001 From: Rob Lyons <39706150+aSeriousDeveloper@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:55:59 +0100 Subject: [PATCH 109/304] introduce start-period and start-interval parameters to HEALTHCHECK (#547) * introduce start-period and start-interval parameters to HEALTHCHECK - start period allows for a grace period for the container to boot - start-interval allows for more frequent checking during this grace period - also slightly increased the standard interval once the container is healthy * introduce new healthcheck periods in apache * introduce healthcheck start period in Nginx Unit * introduce healthcheck start-period in FPM --------- Co-authored-by: Jay Rogers <3174134+jaydrogers@users.noreply.github.com> --- src/variations/fpm-apache/Dockerfile | 2 +- src/variations/fpm-nginx/Dockerfile | 2 +- src/variations/fpm/Dockerfile | 2 +- src/variations/unit/Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index b54074346..0171a05bf 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -174,5 +174,5 @@ WORKDIR ${APP_BASE_DIR} CMD ["/init"] -HEALTHCHECK --interval=5s --timeout=3s --retries=3 \ +HEALTHCHECK --start-period=60s --start-interval=3s --interval=10s --timeout=3s --retries=3 \ CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH || exit 1" ] diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index 5430c1666..db2ff711e 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -258,5 +258,5 @@ WORKDIR ${APP_BASE_DIR} CMD ["/init"] -HEALTHCHECK --interval=5s --timeout=3s --retries=3 \ +HEALTHCHECK --start-period=60s --start-interval=3s --interval=10s --timeout=3s --retries=3 \ CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH || exit 1" ] diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index 403793295..36ce52f86 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -114,5 +114,5 @@ USER www-data CMD ["php-fpm"] -HEALTHCHECK --interval=5s --timeout=3s --retries=3 \ +HEALTHCHECK --start-period=60s --start-interval=3s --interval=10s --timeout=3s --retries=3 \ CMD php-fpm-healthcheck || exit 1 diff --git a/src/variations/unit/Dockerfile b/src/variations/unit/Dockerfile index b41c19d7f..00bd788b2 100644 --- a/src/variations/unit/Dockerfile +++ b/src/variations/unit/Dockerfile @@ -174,5 +174,5 @@ EXPOSE 8080 8443 CMD ["unitd", "--no-daemon"] -HEALTHCHECK --interval=5s --timeout=3s --retries=3 \ +HEALTHCHECK --start-period=60s --start-interval=3s --interval=10s --timeout=3s --retries=3 \ CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH || exit 1" ] From 077fd620711a565dc99341a7137888dbe077275a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 2 Oct 2025 11:18:20 -0500 Subject: [PATCH 110/304] Add absolute_redirect off to Nginx configuration files Ref https://github.com/serversideup/docker-php/discussions/567 --- .../fpm-nginx/etc/nginx/site-opts.d/http.conf.template | 2 ++ .../fpm-nginx/etc/nginx/site-opts.d/https.conf.template | 2 ++ src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template index 9ce8a8868..341e4b188 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template @@ -10,6 +10,8 @@ server_name _; charset utf-8; +absolute_redirect off; + # Healthcheck: Set /healthcheck to be the static health check URL location /healthcheck { access_log off; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template index db3d9e6c9..78f024953 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template @@ -16,6 +16,8 @@ server_name _; charset utf-8; +absolute_redirect off; + # Healthcheck: Set /healthcheck to be the static health check URL location /healthcheck { access_log off; diff --git a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full index 85c8df4d4..850f5449b 100644 --- a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full +++ b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full @@ -6,6 +6,8 @@ server { server_name _; + absolute_redirect off; + location / { set $redirect_to_local_https 0; From 46b232eafbd7da7938ce4ef15ea7b9902caaf33e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 2 Oct 2025 13:20:29 -0500 Subject: [PATCH 111/304] Update log format in Apache vhost templates to include Referer and User-Agent headers (Fixes #540) --- src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf | 2 +- .../fpm-apache/etc/apache2/vhost-templates/https.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf b/src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf index 964d94235..5684f34da 100644 --- a/src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf +++ b/src/variations/fpm-apache/etc/apache2/vhost-templates/http.conf @@ -34,7 +34,7 @@ ProxyTimeout 1800 SetEnvIf Request_URI "^${HEALTHCHECK_PATH}$" dontlog # CustomLog directive to conditionally log requests -LogFormat "%l %u %t %v %a \"%r\" %>s %b" comonvhost +LogFormat "%l %u %t %v %a \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" comonvhost CustomLog /dev/stdout comonvhost env=!dontlog # Configure Log Settings diff --git a/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf b/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf index 5abefdeaa..f2831f45f 100644 --- a/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf +++ b/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf @@ -39,7 +39,7 @@ ProxyTimeout 1800 SetEnvIf Request_URI "^${HEALTHCHECK_PATH}$" dontlog # CustomLog directive to conditionally log requests -LogFormat "%l %u %t %v %a \"%r\" %>s %b" comonvhost +LogFormat "%l %u %t %v %a \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" comonvhost CustomLog /dev/stdout comonvhost env=!dontlog # Configure Log Settings From 15b309134e1115f350a8290efd16bdfe4a2f8e26 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 2 Oct 2025 14:06:51 -0500 Subject: [PATCH 112/304] Update environment variable documentation for Laravel optimizations and add new seeder option --- .../docs/7.reference/1.environment-variable-specification.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 7b2a6ab44..ea42758cf 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -22,7 +22,7 @@ We like to customize our images on a per app basis using environment variables. `APP_BASE_DIR`
*Default: "/var/www/html"*|Change this only if you mount your application to a different directory within the container. ℹ️ Be sure to change `NGINX_WEBROOT`, `APACHE_DOCUMENT_ROOT`, `UNIT_WEBROOT`, etc if it applies to your use case as well.|all `AUTORUN_DEBUG`
*Default: "false"*|Enable debug mode for the Laravel automations. | all `AUTORUN_ENABLED`
*Default: "false"*|Enable or disable all automations. It's advised to set this to `false` in certain CI environments (especially during a composer install). If this is set to `false`, all `AUTORUN_*` behaviors will also be disabled.| all -`AUTORUN_LARAVEL_OPTIMIZE`
*Default: "false"*|Automatically run "php artisan optimize" on container, attempting to `--except` in Laravel > `v11.38.0` (Official docs)
ℹ️ Requires `AUTORUN_ENABLED = true` to run. | all +`AUTORUN_LARAVEL_OPTIMIZE`
*Default: "true"*|Automatically run "php artisan optimize" on container, attempting to `--except` in Laravel > `v11.38.0` (Official docs)
ℹ️ Requires `AUTORUN_ENABLED = true` to run. | all `AUTORUN_LARAVEL_CONFIG_CACHE`
*Default: "true"*|Automatically run "php artisan config:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_EVENT_CACHE`
*Default: "true"*|Automatically run "php artisan event:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION`
*Default: "true"*|Automatically run `php artisan migrate --force` on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all @@ -30,6 +30,7 @@ We like to customize our images on a per app basis using environment variables. `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK`
*Default: "false"*|Skip the database connection check before running migrations.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION_TIMEOUT`
*Default: "30"*|The number of seconds to wait for the database to come online before attempting `php artisan migrate`..
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_ROUTE_CACHE`
*Default: "true"*|Automatically run "php artisan route:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_SEED`
*Default: "false"*|Automatically run database seeder on container start. Set to `true` to run the default seeder (`php artisan db:seed --force`), or provide a custom seeder class name (e.g., `DatabaseSeeder`) to run a specific seeder (`php artisan db:seed --seeder=DatabaseSeeder`).
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_STORAGE_LINK`
*Default: "true"*|Automatically run "php artisan storage:link" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_VIEW_CACHE`
*Default: "true"*|Automatically run "php artisan view:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `CADDY_ADMIN`
*Default: "off"*|Enable Caddy admin interface. (Official docs)|frankenphp From 99ecb51244d4c9e647ae66a8beb37171c896f78e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 2 Oct 2025 14:12:55 -0500 Subject: [PATCH 113/304] Update Laravel documentation links to reflect version 12.x changes for health checks, task scheduler, and Reverb setup instructions. --- docs/content/docs/3.guide/3.using-healthchecks-with-laravel.md | 2 +- docs/content/docs/4.laravel/2.laravel-task-scheduler.md | 2 +- docs/content/docs/4.laravel/4.laravel-reverb.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/3.guide/3.using-healthchecks-with-laravel.md b/docs/content/docs/3.guide/3.using-healthchecks-with-laravel.md index 7b80142c8..0d9eecdb4 100644 --- a/docs/content/docs/3.guide/3.using-healthchecks-with-laravel.md +++ b/docs/content/docs/3.guide/3.using-healthchecks-with-laravel.md @@ -35,7 +35,7 @@ By default, you can see our [Environment Variable Specification](/docs/reference **This only validates that FPM-NGINX or FPM-APACHE are running and ready to accept connections. It does not validate that Laravel is running or healthy.** -If you are using Laravel, [modern versions of Laravel will ship with a `/up` route](https://laravel.com/docs/11.x/deployment#the-health-route) that you can use to validate that Laravel is running and healthy. +If you are using Laravel, [modern versions of Laravel will ship with a `/up` route](https://laravel.com/docs/12.x/deployment#the-health-route) that you can use to validate that Laravel is running and healthy. You can even create your own custom path in your application if you want. As long as the path returns a 200 status code, the health check will be successful. diff --git a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md index acd6693de..a3e9a8b1d 100644 --- a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md +++ b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md @@ -15,7 +15,7 @@ Running a Laravel task scheduler with Docker can be a little different from the 1. The actual time trigger itself is set within Laravel ## More detail -We need to run the [schedule:work](https://laravel.com/docs/11.x/scheduling#running-the-scheduler-locally) command from Laravel. Although the docs say "Running the scheduler locally", this is what we want in production. It will run the scheduler in the foreground and execute it every minute. You can configure your Laravel app for the exact time that a command should run through a [scheduled task](https://laravel.com/docs/11.x/scheduling#scheduling-artisan-commands). +We need to run the [schedule:work](https://laravel.com/docs/12.x/scheduling#running-the-scheduler-locally) command from Laravel. Although the docs say "Running the scheduler locally", this is what we want in production. It will run the scheduler in the foreground and execute it every minute. You can configure your Laravel app for the exact time that a command should run through a [scheduled task](https://laravel.com/docs/12.x/scheduling#scheduling-artisan-commands). ## Examples diff --git a/docs/content/docs/4.laravel/4.laravel-reverb.md b/docs/content/docs/4.laravel/4.laravel-reverb.md index 3c50ae1fc..811636db6 100644 --- a/docs/content/docs/4.laravel/4.laravel-reverb.md +++ b/docs/content/docs/4.laravel/4.laravel-reverb.md @@ -17,7 +17,7 @@ php artisan reverb:start :: ## Important concepts -1. You will need to follow the [Laravel Reverb setup instructions](https://laravel.com/docs/11.x/reverb) to install the Laravel Reverb package into your Laravel application. +1. You will need to follow the [Laravel Reverb setup instructions](https://laravel.com/docs/12.x/reverb) to install the Laravel Reverb package into your Laravel application. 1. In most cases, you probably want to run this as a separate container from your web container 1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) 1. Be sure to set the health check From 48383554404f5501e02ae5b992d4d7e0f22aa0e3 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 2 Oct 2025 14:13:10 -0500 Subject: [PATCH 114/304] Refactor Laravel migration script to build migration flags dynamically and improve error handling for isolated migrations. Added debug logs for new environment variables related to migrations. --- .../entrypoint.d/50-laravel-automations.sh | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 5ec637c2f..956f6bd14 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -94,15 +94,19 @@ artisan_migrate() { return 1 fi + # Build migration flags + migrate_flags="--force" + if [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ] && laravel_version_is_at_least "9.38.0"; then - debug_log "Running migrations with --isolated flag" - echo "🚀 Running migrations: \"php artisan migrate --force --isolated\"..." - php "$APP_BASE_DIR/artisan" migrate --force --isolated - else - debug_log "Running standard migrations" - echo "🚀 Running migrations: \"php artisan migrate --force\"..." - php "$APP_BASE_DIR/artisan" migrate --force + migrate_flags="$migrate_flags --isolated" + elif [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ] && ! laravel_version_is_at_least "9.38.0"; then + echo "❌ $script_name: Isolated migrations require Laravel v9.38.0 or above. Detected version: $(get_laravel_version)" + return 1 fi + + # Execute migration with accumulated flags + echo "🚀 Running migrations: \"php artisan migrate $migrate_flags\"..." + php "$APP_BASE_DIR/artisan" migrate $migrate_flags } artisan_storage_link() { @@ -324,6 +328,9 @@ if laravel_is_installed; then debug_log "- Route Cache: $AUTORUN_LARAVEL_ROUTE_CACHE" debug_log "- View Cache: $AUTORUN_LARAVEL_VIEW_CACHE" debug_log "- Event Cache: $AUTORUN_LARAVEL_EVENT_CACHE" + debug_log "- Seed: $AUTORUN_LARAVEL_SEED" + debug_log "- Skip DB Check: $AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK" + debug_log "- Migration Timeout: $AUTORUN_LARAVEL_MIGRATION_TIMEOUT" echo "🤔 Checking for Laravel automations..." if [ "$AUTORUN_LARAVEL_STORAGE_LINK" = "true" ]; then From 3f1f52734852a5e8448a94c1595b51079f2aeb0e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 2 Oct 2025 16:09:12 -0500 Subject: [PATCH 115/304] Fixed notice of `/package/admin/s6-overlay/libexec/preinit: info: /run belongs to uid X instead of Y` for FPM-NGINX on Alpine --- .../usr/local/bin/docker-php-serversideup-set-file-permissions | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 45e0b504d..4d039889b 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -251,6 +251,7 @@ case "$OS" in DIRS=" /composer /etc/nginx/ + /run /etc/ssl/private /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.d/zzz-docker-php-serversideup-fpm-debug.conf From bfb00cdd522d31c6148d29a42c3657a92a3ebdaf Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 7 Oct 2025 10:52:48 -0500 Subject: [PATCH 116/304] Enhance Laravel automation scripts with new migration options and improved error handling. Added support for multiple databases, migration modes, and seeding. Updated documentation to reflect these changes and added a new script for testing database connections. --- .../docs/4.laravel/1.laravel-automations.md | 44 ++- .../1.environment-variable-specification.md | 7 +- .../entrypoint.d/50-laravel-automations.sh | 349 +++++++++++------- .../lib/laravel/test-db-connection.php | 160 ++++++++ 4 files changed, 412 insertions(+), 148 deletions(-) create mode 100644 src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md index b1f7bee47..4cacc12a6 100644 --- a/docs/content/docs/4.laravel/1.laravel-automations.md +++ b/docs/content/docs/4.laravel/1.laravel-automations.md @@ -20,10 +20,14 @@ In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once th | `AUTORUN_LARAVEL_CONFIG_CACHE` | `true` | `php artisan config:cache`: Caches the configuration files into a single file. | | `AUTORUN_LARAVEL_EVENT_CACHE` | `true` | `php artisan event:cache`: Creates a manifest of all your application's events and listeners. | | `AUTORUN_LARAVEL_MIGRATION` | `true` | `php artisan migrate`: Runs migrations. | -| `AUTORUN_LARAVEL_MIGRATION_ISOLATION` | `false` | Run your migrations with the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag.
**ℹ️ Note:** Requires Laravel v9.38.0+ | +| `AUTORUN_LARAVEL_MIGRATION_DATABASE` | `null` | Run migrations on a specific database. In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). If `null`, it will use the default database connection. | +| `AUTORUN_LARAVEL_MIGRATION_FORCE` | `true` | Force migrations to run in production without confirmation. Set to `false` to disable the `--force` flag. | +| `AUTORUN_LARAVEL_MIGRATION_ISOLATION` | `false` | Run your migrations with the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag.
**ℹ️ Note:** Requires Laravel v9.38.0+. Only works with `default` migration mode. | +| `AUTORUN_LARAVEL_MIGRATION_MODE` | `default` | Migration mode: `default`, `fresh`, or `refresh`.
**⚠️ Warning:** `fresh` and `refresh` drop all tables. | +| `AUTORUN_LARAVEL_MIGRATION_SEED` | `false` | Automatically seed the database after migrations using the `--seed` flag. | +| `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK` | `false` | Skip the database connection check before running migrations. | | `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` | `30` | Number of seconds to wait for database connection before timing out during migrations. | | `AUTORUN_LARAVEL_OPTIMIZE` | `true` | `php artisan optimize`: Optimizes the application. | -| `AUTORUN_LARAVEL_SEED` | `false` | `php artisan db:seed`: Runs the default seeder.
**ℹ️ Note:** Set to `true` to run the default seeder. If you want to run a custom seeder, set this to the name of the seeder class. | | `AUTORUN_LARAVEL_ROUTE_CACHE` | `true` | `php artisan route:cache`: Caches the routes. | | `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | | `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | @@ -42,10 +46,41 @@ Creates a symbolic link from `public/storage` to `storage/app/public`. ## php artisan migrate Before running migrations, we ensure the database is online and ready to accept connections. By default, we will wait 30 seconds before timing out. +### Migration Modes +You can control how migrations run using `AUTORUN_LARAVEL_MIGRATION_MODE`: +- `default` (default): Runs `php artisan migrate` - standard forward migrations +- `fresh`: Runs `php artisan migrate:fresh` - drops all tables and re-runs migrations +- `refresh`: Runs `php artisan migrate:refresh` - rolls back and re-runs migrations + +::note +**⚠️ Warning:** Using `fresh` or `refresh` modes will **drop all tables** in your database. Only use these in development or testing environments. +:: + +### Force Flag +By default, migrations run with the `--force` flag to bypass production warnings. You can disable this by setting `AUTORUN_LARAVEL_MIGRATION_FORCE` to `false`. + +### Seeding +You can automatically seed your database after migrations by setting `AUTORUN_LARAVEL_MIGRATION_SEED` to `true`. This adds the `--seed` flag to your migration command. + +### Specific Database Migrations +If you need to specify the exact database connection to use for migrations, you can set `AUTORUN_LARAVEL_MIGRATION_DATABASE` to the name of the database connection you want to use. + +```bash +AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql +``` + +In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). + +```bash +AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql,pgsql +``` + +### Isolated Migrations You can enable the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. **Special Notes for Isolated Migrations:** - Requires Laravel v9.38.0+ +- Only works with `default` migration mode (not compatible with `fresh` or `refresh`) - Your application must be using the memcached, redis, dynamodb, database, file, or array cache driver as your application's default cache driver. In addition, all servers must be communicating with the same central cache server. [Read more about migrations →](https://laravel.com/docs/12.x/migrations#running-migrations) @@ -66,11 +101,6 @@ This command caches all configuration files into a single file, which can then b [Read more about configuration caching →](https://laravel.com/docs/12.x/configuration#configuration-caching) -## php artisan db:seed -This command runs the default seeder. If you want to run a custom seeder, set `AUTORUN_LARAVEL_SEED` to the name of the seeder class. - -[Read more about seeding →](https://laravel.com/docs/12.x/seeding) - ## php artisan route:cache This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index ea42758cf..db66670c9 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -26,11 +26,14 @@ We like to customize our images on a per app basis using environment variables. `AUTORUN_LARAVEL_CONFIG_CACHE`
*Default: "true"*|Automatically run "php artisan config:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_EVENT_CACHE`
*Default: "true"*|Automatically run "php artisan event:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION`
*Default: "true"*|Automatically run `php artisan migrate --force` on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all -`AUTORUN_LARAVEL_MIGRATION_ISOLATION`
*Default: "false"*|Requires Laravel v9.38.0 or higher and a database that supports table locks. Automatically run `php artisan migrate --force --isolated` on container start.

ℹ️ Requires `AUTORUN_ENABLED = true` to run.
ℹ️ Does not work with SQLite.| all +`AUTORUN_LARAVEL_MIGRATION_DATABASE`
*Default: null*| Run migrations on a specific database. In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). If `null`, it will use the default database connection.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_MIGRATION_FORCE`
*Default: "true"*|Force migrations to run in production without confirmation. Set to `false` to disable the `--force` flag.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_MIGRATION_ISOLATION`
*Default: "false"*|Requires Laravel v9.38.0 or higher and a database that supports table locks. Automatically run `php artisan migrate --force --isolated` on container start.

ℹ️ Requires `AUTORUN_ENABLED = true` to run.
ℹ️ Does not work with SQLite.
ℹ️ Only works with `AUTORUN_LARAVEL_MIGRATION_MODE = default`.| all +`AUTORUN_LARAVEL_MIGRATION_MODE`
*Default: "default"*|Set the migration mode. Valid options: `default` (runs `php artisan migrate`), `fresh` (runs `php artisan migrate:fresh`), or `refresh` (runs `php artisan migrate:refresh`).
ℹ️ Requires `AUTORUN_ENABLED = true` to run.
⚠️ WARNING:`fresh` and `refresh` are destructive and will drop all tables. Only use these in development or testing environments.| all +`AUTORUN_LARAVEL_MIGRATION_SEED`
*Default: "false"*|Automatically seed the database after migrations by adding the `--seed` flag.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK`
*Default: "false"*|Skip the database connection check before running migrations.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_MIGRATION_TIMEOUT`
*Default: "30"*|The number of seconds to wait for the database to come online before attempting `php artisan migrate`..
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_ROUTE_CACHE`
*Default: "true"*|Automatically run "php artisan route:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all -`AUTORUN_LARAVEL_SEED`
*Default: "false"*|Automatically run database seeder on container start. Set to `true` to run the default seeder (`php artisan db:seed --force`), or provide a custom seeder class name (e.g., `DatabaseSeeder`) to run a specific seeder (`php artisan db:seed --seeder=DatabaseSeeder`).
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_STORAGE_LINK`
*Default: "true"*|Automatically run "php artisan storage:link" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_VIEW_CACHE`
*Default: "true"*|Automatically run "php artisan view:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `CADDY_ADMIN`
*Default: "off"*|Enable Caddy admin interface. (Official docs)|frankenphp diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 956f6bd14..1e86e0348 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -4,6 +4,7 @@ script_name="laravel-automations" # Global configurations : "${DISABLE_DEFAULT_CONFIG:=false}" : "${APP_BASE_DIR:=/var/www/html}" +: "${AUTORUN_LIB_DIR:=/etc/entrypoint.d/lib}" # Set default values for Laravel automations : "${AUTORUN_ENABLED:=false}" @@ -21,12 +22,13 @@ script_name="laravel-automations" # Set default values for Migrations : "${AUTORUN_LARAVEL_MIGRATION:=true}" +: "${AUTORUN_LARAVEL_MIGRATION_DATABASE:=}" +: "${AUTORUN_LARAVEL_MIGRATION_FORCE:=true}" : "${AUTORUN_LARAVEL_MIGRATION_ISOLATION:=false}" -: "${AUTORUN_LARAVEL_MIGRATION_TIMEOUT:=30}" +: "${AUTORUN_LARAVEL_MIGRATION_MODE:=default}" +: "${AUTORUN_LARAVEL_MIGRATION_SEED:=false}" : "${AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK:=false}" - -# Set default values for seeders -: "${AUTORUN_LARAVEL_SEED:=false}" +: "${AUTORUN_LARAVEL_MIGRATION_TIMEOUT:=30}" # Set default values for Laravel version INSTALLED_LARAVEL_VERSION="" @@ -51,147 +53,183 @@ fi ############################################################################ artisan_migrate() { - count=0 - timeout=$AUTORUN_LARAVEL_MIGRATION_TIMEOUT + migrate_flags="" - debug_log "Starting migrations (timeout: ${timeout}s, isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION)" + debug_log "Starting migrations (isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION)" echo "🚀 Clearing Laravel cache before attempting migrations..." php "$APP_BASE_DIR/artisan" config:clear - - # Do not exit on error for this loop - set +e - echo "⚡️ Attempting database connection..." - while [ $count -lt "$timeout" ]; do - if [ "$AUTORUN_DEBUG" = "true" ]; then - # Show output when debug is enabled - test_db_connection - else - # Otherwise suppress output - test_db_connection > /dev/null 2>&1 + + # Determine the migration command to use + case "$AUTORUN_LARAVEL_MIGRATION_MODE" in + default) + migration_command="migrate" + ;; + fresh) + migration_command="migrate:fresh" + ;; + refresh) + migration_command="migrate:refresh" + ;; + esac + + # Build migration flags (used for all databases) + if [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ]; then + # Isolation only works in default mode + if [ "$AUTORUN_LARAVEL_MIGRATION_MODE" != "default" ]; then + echo "❌ $script_name: Isolated migrations are only supported in default mode." + return 1 fi - status=$? - if [ $status -eq 0 ]; then - echo "✅ Database connection successful." - break - else - # Only log every 5 attempts to reduce noise - if [ $((count % 5)) -eq 0 ]; then - debug_log "Connection attempt $((count + 1))/$timeout failed (status: $status)" - fi - echo "Waiting on database connection, retrying... $((timeout - count)) seconds left" - count=$((count + 1)) - sleep 1 + + # Isolation requires Laravel 9.38.0+ + if ! laravel_version_is_at_least "9.38.0"; then + echo "❌ $script_name: Isolated migrations require Laravel v9.38.0 or above. Detected version: $(get_laravel_version)" + return 1 fi - done + + migrate_flags="$migrate_flags --isolated" + fi - # Re-enable exit on error - set -e + if [ "$AUTORUN_LARAVEL_MIGRATION_FORCE" = "true" ]; then + migrate_flags="$migrate_flags --force" + fi - if [ $count -eq "$timeout" ]; then - echo "❌ $script_name: Database connection failed after multiple attempts." - debug_log "Database connection timed out after $timeout seconds" - return 1 + if [ "$AUTORUN_LARAVEL_MIGRATION_SEED" = "true" ]; then + migrate_flags="$migrate_flags --seed" fi - - # Build migration flags - migrate_flags="--force" - - if [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ] && laravel_version_is_at_least "9.38.0"; then - migrate_flags="$migrate_flags --isolated" - elif [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ] && ! laravel_version_is_at_least "9.38.0"; then - echo "❌ $script_name: Isolated migrations require Laravel v9.38.0 or above. Detected version: $(get_laravel_version)" - return 1 + + # Determine if multiple databases are specified + if [ -n "$AUTORUN_LARAVEL_MIGRATION_DATABASE" ]; then + databases=$(convert_comma_delimited_to_space_separated "$AUTORUN_LARAVEL_MIGRATION_DATABASE") + database_list=$(echo "$databases" | tr ',' ' ') + + for db in $database_list; do + # Wait for this specific database to be ready + if ! wait_for_database_connection "$db"; then + echo "❌ $script_name: Failed to connect to database: $db" + return 1 + fi + + echo "🚀 Running migrations for database: $db" + php "$APP_BASE_DIR/artisan" $migration_command --database=$db $migrate_flags + done + else + # Wait for default database connection + if ! wait_for_database_connection; then + echo "❌ $script_name: Failed to connect to default database" + return 1 + fi + + # Run migration with default database connection + php "$APP_BASE_DIR/artisan" $migration_command $migrate_flags fi - - # Execute migration with accumulated flags - echo "🚀 Running migrations: \"php artisan migrate $migrate_flags\"..." - php "$APP_BASE_DIR/artisan" migrate $migrate_flags } artisan_storage_link() { if [ -d "$APP_BASE_DIR/public/storage" ]; then echo "✅ Storage already linked..." + return 0 else echo "🔐 Running storage link: \"php artisan storage:link\"..." - php "$APP_BASE_DIR/artisan" storage:link + if ! php "$APP_BASE_DIR/artisan" storage:link; then + echo "❌ $script_name: Storage link failed" + return 1 + fi fi } artisan_optimize() { - # Case 1: All optimizations are enabled - use simple optimize command + debug_log "Starting Laravel optimizations..." + + # Determine which optimizations are requested + all_opts_enabled="false" if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && \ [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ] && \ [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ] && \ [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "true" ] && \ [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "true" ]; then + all_opts_enabled="true" + fi + + # Case 1: All optimizations enabled - use simple optimize command + if [ "$all_opts_enabled" = "true" ]; then + debug_log "All optimizations enabled, using 'php artisan optimize'" echo "🚀 Running optimize command: \"php artisan optimize\"..." if ! php "$APP_BASE_DIR/artisan" optimize; then - echo "❌ Laravel optimize failed" + echo "❌ $script_name: Laravel optimize failed" return 1 fi return 0 fi - - # Case 2: AUTORUN_LARAVEL_OPTIMIZE is true but some optimizations disabled - if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && laravel_version_is_at_least "11.38.0"; then - echo "🛠️ Preparing optimizations..." - except="" - - # Build except string for disabled optimizations - [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "false" ] && except="${except:+${except},}config" - [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "false" ] && except="${except:+${except},}routes" - [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "false" ] && except="${except:+${except},}views" - [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "false" ] && except="${except:+${except},}events" - - echo "🚀 Running optimizations: \"php artisan optimize ${except:+--except=${except}}\"..." - if ! php "$APP_BASE_DIR/artisan" optimize ${except:+--except=${except}}; then - echo "$script_name: ❌ Laravel optimize failed" - return 1 + + # Case 2: AUTORUN_LARAVEL_OPTIMIZE is true with selective optimizations (Laravel 11.38.0+) + if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ]; then + if laravel_version_is_at_least "11.38.0"; then + debug_log "Using 'php artisan optimize --except' for selective optimizations" + echo "🛠️ Preparing selective optimizations..." + except="" + + # Build except string for disabled optimizations + [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "false" ] && except="${except:+${except},}config" + [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "false" ] && except="${except:+${except},}routes" + [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "false" ] && except="${except:+${except},}views" + [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "false" ] && except="${except:+${except},}events" + + echo "🚀 Running optimizations: \"php artisan optimize ${except:+--except=${except}}\"..." + if ! php "$APP_BASE_DIR/artisan" optimize ${except:+--except=${except}}; then + echo "❌ $script_name: Laravel optimize failed" + return 1 + fi + return 0 + else + debug_log "Laravel version < 11.38.0, falling back to individual optimization commands" + echo "ℹ️ Selective optimizations with 'php artisan optimize --except' require Laravel v11.38.0 or above, using individual commands instead..." fi - return 0 - fi - - if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] && ! laravel_version_is_at_least "11.38.0"; then - echo "ℹ️ Granular optimizations require Laravel v11.38.0 or above, using individual optimizations instead..." fi - - # Case 3: Individual optimizations when AUTORUN_LARAVEL_OPTIMIZE is false - has_error=0 + + # Case 3: Run individual optimization commands + # This runs when: + # - AUTORUN_LARAVEL_OPTIMIZE is false (user wants granular control), OR + # - AUTORUN_LARAVEL_OPTIMIZE is true but Laravel < 11.38.0 (fallback) + debug_log "Running individual optimization commands" if [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ]; then echo "🚀 Caching config: \"php artisan config:cache\"..." - php "$APP_BASE_DIR/artisan" config:cache || has_error=1 + if ! php "$APP_BASE_DIR/artisan" config:cache; then + echo "❌ $script_name: Config cache failed" + return 1 + fi fi if [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ]; then echo "🚀 Caching routes: \"php artisan route:cache\"..." - php "$APP_BASE_DIR/artisan" route:cache || has_error=1 + if ! php "$APP_BASE_DIR/artisan" route:cache; then + echo "❌ $script_name: Route cache failed" + return 1 + fi fi if [ "$AUTORUN_LARAVEL_VIEW_CACHE" = "true" ]; then echo "🚀 Caching views: \"php artisan view:cache\"..." - php "$APP_BASE_DIR/artisan" view:cache || has_error=1 + if ! php "$APP_BASE_DIR/artisan" view:cache; then + echo "❌ $script_name: View cache failed" + return 1 + fi fi if [ "$AUTORUN_LARAVEL_EVENT_CACHE" = "true" ]; then echo "🚀 Caching events: \"php artisan event:cache\"..." - php "$APP_BASE_DIR/artisan" event:cache || has_error=1 + if ! php "$APP_BASE_DIR/artisan" event:cache; then + echo "❌ $script_name: Event cache failed" + return 1 + fi fi - - return $has_error + + return 0 } -artisan_seed(){ - # Run the default seeder if "true", otherwise use value as custom seeder - if [ "${AUTORUN_LARAVEL_SEED}" = "true" ]; then - echo "🚀 Running default seeder: \"php artisan db:seed\"" - php "${APP_BASE_DIR}/artisan" db:seed --force - else - echo "🚀 Running custom seeder: \"php artisan db:seed --seeder=${AUTORUN_LARAVEL_SEED}\"" - echo "ℹ️ Your application must have a seeder class named \"${AUTORUN_LARAVEL_SEED}\" or this command will fail." - php "${APP_BASE_DIR}/artisan" db:seed --seeder="${AUTORUN_LARAVEL_SEED}" - fi +convert_comma_delimited_to_space_separated() { + echo $1 | tr ',' ' ' } get_laravel_version() { @@ -283,34 +321,73 @@ test_db_connection() { return 0 fi - php -r " - require '$APP_BASE_DIR/vendor/autoload.php'; - use Illuminate\Support\Facades\DB; - - \$app = require_once '$APP_BASE_DIR/bootstrap/app.php'; - \$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class); - \$kernel->bootstrap(); - - \$driver = DB::getDriverName(); - - if( \$driver === 'sqlite' ){ - echo 'SQLite detected'; - exit(0); // Assume SQLite is always ready - } - - try { - DB::connection()->getPdo(); // Attempt to get PDO instance - if (DB::connection()->getDatabaseName()) { - exit(0); // Database exists and can be connected to, exit with status 0 (success) - } else { - echo 'Database name not found.'; - exit(1); // Database name not found, exit with status 1 (failure) - } - } catch (Exception \$e) { - echo 'Database connection error: ' . \$e->getMessage(); - exit(1); // Connection error, exit with status 1 (failure) - } - " + # Pass database connection name only if specified (not empty) + database_arg="${1:-}" + if [ -n "$database_arg" ]; then + php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" "$database_arg" + else + php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" + fi +} + +wait_for_database_connection() { + database_name="${1:-}" + count=0 + timeout=$AUTORUN_LARAVEL_MIGRATION_TIMEOUT + + # Determine display name based on whether a specific connection was provided + if [ -z "$database_name" ]; then + display_name="default database" + connection_label="" + else + display_name="database connection: $database_name" + connection_label=": $database_name" + fi + + debug_log "Waiting for connection to $display_name (timeout: ${timeout}s)" + + # Do not exit on error for this loop + set +e + echo "⚡️ Attempting connection to $display_name..." + while [ $count -lt "$timeout" ]; do + if [ "$AUTORUN_DEBUG" = "true" ]; then + # Show output when debug is enabled + # Only pass database_name if it's not empty + if [ -z "$database_name" ]; then + test_db_connection + else + test_db_connection "$database_name" + fi + else + # Otherwise suppress output + if [ -z "$database_name" ]; then + test_db_connection > /dev/null 2>&1 + else + test_db_connection "$database_name" > /dev/null 2>&1 + fi + fi + status=$? + if [ $status -eq 0 ]; then + echo "✅ Database connection successful$connection_label" + set -e + return 0 + else + # Only log every 5 attempts to reduce noise + if [ $((count % 5)) -eq 0 ]; then + debug_log "Connection attempt $((count + 1))/$timeout failed for $display_name (status: $status)" + fi + echo "Waiting on $display_name connection, retrying... $((timeout - count)) seconds left" + count=$((count + 1)) + sleep 1 + fi + done + + # Re-enable exit on error + set -e + + echo "❌ $script_name: Database connection to $display_name failed after $timeout seconds." + debug_log "Database connection timed out for $display_name after $timeout seconds" + return 1 } ############################################################################ @@ -318,19 +395,15 @@ test_db_connection() { ############################################################################ if laravel_is_installed; then - debug_log "Laravel detected: v$(get_laravel_version)" - debug_log "Automation settings:" - debug_log "- Storage Link: $AUTORUN_LARAVEL_STORAGE_LINK" - debug_log "- Migrations: $AUTORUN_LARAVEL_MIGRATION" - debug_log "- Migrations Isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION" - debug_log "- Optimize: $AUTORUN_LARAVEL_OPTIMIZE" - debug_log "- Config Cache: $AUTORUN_LARAVEL_CONFIG_CACHE" - debug_log "- Route Cache: $AUTORUN_LARAVEL_ROUTE_CACHE" - debug_log "- View Cache: $AUTORUN_LARAVEL_VIEW_CACHE" - debug_log "- Event Cache: $AUTORUN_LARAVEL_EVENT_CACHE" - debug_log "- Seed: $AUTORUN_LARAVEL_SEED" - debug_log "- Skip DB Check: $AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK" - debug_log "- Migration Timeout: $AUTORUN_LARAVEL_MIGRATION_TIMEOUT" + if [ "$LOG_OUTPUT_LEVEL" = "debug" ] || [ "$AUTORUN_DEBUG" = "true" ]; then + echo "Laravel detected: v$(get_laravel_version)" + echo "Automation settings:" + echo "--------------------------------" + # Dynamically display all AUTORUN_* environment variables + env | grep '^AUTORUN_' | sort | while IFS='=' read -r var_name var_value; do + debug_log "- ${var_name}: ${var_value}" + done + fi echo "🤔 Checking for Laravel automations..." if [ "$AUTORUN_LARAVEL_STORAGE_LINK" = "true" ]; then @@ -341,10 +414,6 @@ if laravel_is_installed; then artisan_migrate fi - if [ "$AUTORUN_LARAVEL_SEED" != "false" ]; then - artisan_seed - fi - if [ "$AUTORUN_LARAVEL_OPTIMIZE" = "true" ] || \ [ "$AUTORUN_LARAVEL_CONFIG_CACHE" = "true" ] || \ [ "$AUTORUN_LARAVEL_ROUTE_CACHE" = "true" ] || \ @@ -353,5 +422,7 @@ if laravel_is_installed; then artisan_optimize fi else - echo "👉 $script_name: Skipping Laravel automations because Laravel is not installed." + echo "❌ $script_name: Could not detect Laravel installation." + echo "ℹ️ Check that the application is installed in $APP_BASE_DIR" + exit 1 fi \ No newline at end of file diff --git a/src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php b/src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php new file mode 100644 index 000000000..e4817fd3b --- /dev/null +++ b/src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php @@ -0,0 +1,160 @@ + 5) { + fwrite(STDERR, "Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [migration_isolation] [database_connection]\n"); + exit(1); +} + +$appBaseDir = $argv[1]; +$migrationMode = $argc >= 3 ? $argv[2] : 'default'; +$migrationIsolation = $argc >= 4 ? $argv[3] : 'false'; +$databaseConnection = $argc >= 5 ? $argv[4] : null; + +// Validate migration mode +$validModes = ['default', 'fresh', 'refresh']; +if (!in_array($migrationMode, $validModes)) { + fwrite(STDERR, "Error: Invalid migration mode '{$migrationMode}'. Must be one of: " . implode(', ', $validModes) . "\n"); + exit(1); +} + +// Validate migration isolation +$validIsolations = ['true', 'false']; +if (!in_array($migrationIsolation, $validIsolations)) { + fwrite(STDERR, "Error: Invalid migration isolation '{$migrationIsolation}'. Must be one of: " . implode(', ', $validIsolations) . "\n"); + exit(1); +} + +// Validate that the app base directory exists +if (!is_dir($appBaseDir)) { + fwrite(STDERR, "Error: App base directory does not exist: {$appBaseDir}\n"); + exit(1); +} + +// Validate that required Laravel files exist +$vendorAutoload = "{$appBaseDir}/vendor/autoload.php"; +$bootstrapApp = "{$appBaseDir}/bootstrap/app.php"; + +if (!file_exists($vendorAutoload)) { + fwrite(STDERR, "Error: Composer autoload file not found: {$vendorAutoload}\n"); + exit(1); +} + +if (!file_exists($bootstrapApp)) { + fwrite(STDERR, "Error: Laravel bootstrap file not found: {$bootstrapApp}\n"); + exit(1); +} + +// Bootstrap Laravel +try { + require $vendorAutoload; + + $app = require_once $bootstrapApp; + $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); + $kernel->bootstrap(); + +} catch (Exception $e) { + fwrite(STDERR, "Error bootstrapping Laravel: {$e->getMessage()}\n"); + exit(1); +} + +// Test database connection +try { + // Use specific database connection if provided + $connection = $databaseConnection ? DB::connection($databaseConnection) : DB::connection(); + $driver = $connection->getDriverName(); + + // SQLite special handling + if ($driver === 'sqlite') { + $dbPath = $connection->getDatabaseName(); + + // Handle in-memory SQLite databases + if ($dbPath === ':memory:') { + fwrite(STDOUT, "SQLite in-memory database detected - ready\n"); + exit(0); + } + + $dbDirectory = dirname($dbPath); + + // Check if database file already exists + if (file_exists($dbPath)) { + fwrite(STDOUT, "SQLite database file exists: {$dbPath}\n"); + exit(0); + } + + // Database file doesn't exist - check if directory exists and is writable + if (!is_dir($dbDirectory)) { + fwrite(STDERR, "SQLite database directory does not exist: {$dbDirectory}\n"); + fwrite(STDERR, "Please create the directory before running migrations.\n"); + fwrite(STDERR, "Example: mkdir -p {$dbDirectory}\n"); + exit(1); + } + + if (!is_writable($dbDirectory)) { + fwrite(STDERR, "SQLite database directory is not writable: {$dbDirectory}\n"); + fwrite(STDERR, "Please check directory permissions.\n"); + exit(1); + } + + // For 'fresh' and 'refresh' modes, the database file must already exist + if ($migrationMode === 'fresh' || $migrationMode === 'refresh') { + fwrite(STDERR, "SQLite database file does not exist: {$dbPath}\n"); + fwrite(STDERR, "Migration mode '{$migrationMode}' requires the database file to exist.\n"); + fwrite(STDERR, "Either:\n"); + fwrite(STDERR, " 1. Create the database (ensure it has read and write permissions for your user): touch {$dbPath}\n"); + fwrite(STDERR, " 2. Use AUTORUN_LARAVEL_MIGRATION_MODE=default to let Laravel create it\n"); + exit(1); + } + + // For isolated migrations, the database file must exist (even in default mode) + if ($migrationIsolation === 'true') { + fwrite(STDERR, "SQLite database file does not exist: {$dbPath}\n"); + fwrite(STDERR, "Isolated migrations require the database file to exist before running.\n"); + fwrite(STDERR, "Either:\n"); + fwrite(STDERR, " 1. Create the database (ensure it has read and write permissions for your user): touch {$dbPath}\n"); + fwrite(STDERR, " 2. Set AUTORUN_LARAVEL_MIGRATION_ISOLATION=false to let migrations create it\n"); + exit(1); + } + + // Directory exists and is writable - migrations can create the database file (default mode only) + fwrite(STDOUT, "SQLite database directory is ready - migrations will create database\n"); + exit(0); + } + + // Test connection for other database drivers + $connection->getPdo(); + + if ($connection->getDatabaseName()) { + $connectionName = $databaseConnection ? " ({$databaseConnection})" : ''; + fwrite(STDOUT, "Database connection successful ({$driver}){$connectionName}\n"); + exit(0); + } else { + fwrite(STDERR, "Database name not found\n"); + exit(1); + } + +} catch (Exception $e) { + $connectionName = $databaseConnection ? " ({$databaseConnection})" : ''; + fwrite(STDERR, "Database connection error{$connectionName}: {$e->getMessage()}\n"); + exit(1); +} \ No newline at end of file From c465d9b22a59633e96c5292d9b23daad5fd2ad8c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 8 Oct 2025 14:16:31 -0500 Subject: [PATCH 117/304] Add Caddy logging configuration to Dockerfile for frankenphp variation --- src/variations/frankenphp/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 8f51e26f7..4c27bb197 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -139,6 +139,8 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ CADDY_HTTPS_PORT="8443" \ CADDY_HTTP_SERVER_ADDRESS="http://" \ CADDY_HTTPS_SERVER_ADDRESS="https://" \ + CADDY_LOG_FORMAT="console" \ + CADDY_LOG_OUTPUT="stdout" \ CADDY_PHP_SERVER_OPTIONS="" \ CADDY_SERVER_EXTRA_DIRECTIVES="" \ COMPOSER_ALLOW_SUPERUSER=1 \ From ded236fec6aee10294ff331b6094135b99ef346c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 8 Oct 2025 15:14:54 -0500 Subject: [PATCH 118/304] Add healthcheck configuration to Dockerfile and Caddyfile for frankenphp variation - Introduced a HEALTHCHECK command in the Dockerfile with specified parameters. - Updated Caddyfile to define healthcheck endpoints and log skipping for both default and custom healthcheck paths. - Enhanced full Caddyfile to redirect localhost healthcheck requests to HTTPS. --- src/variations/frankenphp/Dockerfile | 5 ++++- src/variations/frankenphp/etc/frankenphp/Caddyfile | 14 ++++++++++---- .../etc/frankenphp/ssl-mode/full.caddyfile | 10 ++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 4c27bb197..fcb738fc1 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -227,4 +227,7 @@ EXPOSE 8080 8443 8443/udp 2019 ENTRYPOINT ["docker-php-serversideup-entrypoint"] -CMD ["frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"] \ No newline at end of file +CMD ["frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"] + +HEALTHCHECK --start-period=60s --start-interval=3s --interval=10s --timeout=3s --retries=3 \ + CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH || exit 1" ] \ No newline at end of file diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index de6220bd4..e177e29ac 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -63,12 +63,18 @@ fd00::/8 \ # Match serversideup/php log levels to Caddy address log levels import log-level/address/{$LOG_OUTPUT_LEVEL:info}.caddyfile - # Healthcheck endpoint - @health { + # Define the Caddy healthcheck endpoint + @caddy-healthcheck { path /healthcheck } - respond @health "OK" 200 - log_skip @health + respond @caddy-healthcheck "OK" 200 + log_skip @caddy-healthcheck + + # Define the custom healthcheck endpoint + @healthcheckpath { + path {$HEALTHCHECK_PATH:/healthcheck} + } + log_skip @healthcheckpath php_server { {$CADDY_PHP_SERVER_OPTIONS} diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile index 1d1ff2bbb..3ef2652d5 100644 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile @@ -1,4 +1,14 @@ {$CADDY_HTTP_SERVER_ADDRESS:http://} { + # Redirect localhost healthcheck requests to HTTPS with the correct port + @healthcheck { + remote_ip 127.0.0.1/8 ::1 + path /healthcheck # Caddy healthcheck endpoint + path {$HEALTHCHECK_PATH:/healthcheck} # Custom healthcheck endpoint + } + log_skip @healthcheck + redir @healthcheck https://localhost:{$CADDY_HTTPS_PORT:8443}{uri} 308 + + # Redirect all other traffic to HTTPS (without explicit port) redir https://{host}{uri} 308 } From 14aacd1e15a1dc758b47cb0d6dd65a081ddff755 Mon Sep 17 00:00:00 2001 From: Arnaud Ritti <77437157+arnaud-ritti@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:59:55 +0200 Subject: [PATCH 119/304] Fix missing procps on FrankenPHP Variation (#571) * Fix missing gettext, procps and zip on FrankenPHP Variation * Remove gettext (we don't need envsubst for FrankenPHP) * Update default configurations to include libstdc++6 and clarify procps requirements for Debian images * Removed zip -- we don't need it --------- Co-authored-by: Jay Rogers --- .../docs/2.getting-started/3.default-configurations.md | 4 +++- src/variations/frankenphp/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/2.getting-started/3.default-configurations.md b/docs/content/docs/2.getting-started/3.default-configurations.md index 98df5e76e..ff48f91b3 100644 --- a/docs/content/docs/2.getting-started/3.default-configurations.md +++ b/docs/content/docs/2.getting-started/3.default-configurations.md @@ -85,9 +85,11 @@ The following packages are installed by default: |-------------|-----------------|----------------------|------------------------| | `libfcgi-bin`
(Debian)
`fcgi`
(Alpine) | FastCGI is a protocol for interfacing interactive programs with a web server. | *-fpm
*-fpm-nginx
*-fpm-apache | This is required for the webserver to interface with PHP-FPM and the [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck) project. | | `gettext-base` (Debian)
`gettext` (Alpine) | GNU gettext is a framework for translating user interfaces. | *-fpm-nginx
*-fpm-apache | This is required for the `envsubst` command. We use this command to process templates on container initialization. | -| `procps` (Debian) | The procps package contains programs for monitoring your system and its processes. | * (Debian images) | This is required for `pgrep` so we can use that for our native health checks. | +| `libstdc++6`
(Debian)
`libstdc++` (Alpine) | The GNU Standard C++ Library is a C++ standard library. | *-frankenphp | This is [required for the watcher to run](https://github.com/php/frankenphp/blob/e917ab79742c9e4703023861fdc7a86cdb59da1e/Dockerfile#L135-L138) with FrankenPHP. | +| `procps` | The procps package contains programs for monitoring your system and its processes. | * (only Debian images) | This is required for `pgrep` so we can use that for our native health checks. | | `shadow` | Shadow is required for the `usermod` command. | *-alpine | This is required to change the UID and GID of the `www-data` user in `docker-php-serversideup-set-id`. | + ## Health Checks By default, all health checks for web servers (Apache, Unit, NGINX, etc.) are located at `/healthcheck`. You should receive an `OK` response if the container is healthy. diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index fcb738fc1..dcb860d12 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -116,7 +116,7 @@ RUN if cat /etc/os-release | grep -q 'debian'; then \ #################### FROM common AS final ARG DEPENDENCY_PACKAGES_ALPINE='shadow libstdc++' -ARG DEPENDENCY_PACKAGES_DEBIAN='libstdc++6' +ARG DEPENDENCY_PACKAGES_DEBIAN='procps libstdc++6' ARG DEPENDENCY_PHP_EXTENSIONS='opcache pcntl pdo_mysql pdo_pgsql redis zip' ARG REPOSITORY_BUILD_VERSION='dev' From 20dd93cb79fc11a6000321404ec8d8954a9dbfc8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 14 Oct 2025 11:16:18 -0500 Subject: [PATCH 120/304] Add instructions for computing the Alpine key hash and building with the new hash in contributing documentation --- docs/content/docs/2.getting-started/7.contributing.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/content/docs/2.getting-started/7.contributing.md b/docs/content/docs/2.getting-started/7.contributing.md index 54547ea6f..df62e9643 100644 --- a/docs/content/docs/2.getting-started/7.contributing.md +++ b/docs/content/docs/2.getting-started/7.contributing.md @@ -117,6 +117,10 @@ label: "Update NGINX versions" Compute the Alpine key hash when updating: +::code-panel +--- +label: "Compute the Alpine key hash" +--- ```bash curl -sS https://nginx.org/keys/nginx_signing.rsa.pub -o /tmp/nginx_signing.rsa.pub # macOS @@ -124,9 +128,14 @@ openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | sha # Linux openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | sha256sum | awk '{print $1}' ``` +:: Then build with the new hash (optionally include the old hash during rotation): +::code-panel +--- +label: "Build with the new hash" +--- ```bash docker build \ --build-arg SIGNING_ALPINE_RSA_PUB_SHA256="," \ From 582b16f3e399df61baba7e049f88efc9c48660ac Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 14 Oct 2025 11:30:15 -0500 Subject: [PATCH 121/304] Update documentation to replace 'NGINX Unit variation' with 'FrankenPHP variation' in the getting started guide. --- docs/content/docs/2.getting-started/1.these-images-vs-others.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/2.getting-started/1.these-images-vs-others.md b/docs/content/docs/2.getting-started/1.these-images-vs-others.md index d46afdb67..36074ce18 100644 --- a/docs/content/docs/2.getting-started/1.these-images-vs-others.md +++ b/docs/content/docs/2.getting-started/1.these-images-vs-others.md @@ -22,7 +22,7 @@ layout: docs | Built-in security optimizations | ❌ | ✅ | | Optimized for Laravel & WordPress| ❌ | ✅ | | NGINX + FPM variation| ❌ | ✅ | -| NGINX Unit variation| ❌ | ✅ | +| FrankenPHP variation| ❌ | ✅ | | Native health checks | ❌ | ✅ | ## Unprivileged by Default From 6ccd468ec96c2f2f6a457f21c9776896d62037b8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 14 Oct 2025 14:26:22 -0500 Subject: [PATCH 122/304] Add Docker introduction and benefits to installation documentation - Introduced a new section explaining what Docker is and its advantages for containerizing applications. - Added a demo video link for the Spin project to help users get started with Docker. - Updated the FPM-NGINX section to reflect its historical significance and current relevance. - Marked the NGINX Unit variation as deprecated due to its discontinuation. - Added a new section for the FrankenPHP variation, highlighting its features and benefits. --- .../docs/2.getting-started/2.installation.md | 53 +++++++++++++++++- docs/public/images/docs/docker-layers.png | Bin 0 -> 76743 bytes 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 docs/public/images/docs/docker-layers.png diff --git a/docs/content/docs/2.getting-started/2.installation.md b/docs/content/docs/2.getting-started/2.installation.md index 8babb9e56..d27c7ec82 100644 --- a/docs/content/docs/2.getting-started/2.installation.md +++ b/docs/content/docs/2.getting-started/2.installation.md @@ -13,6 +13,42 @@ layout: docs All images are hosted on [DockerHub](https://hub.docker.com/r/serversideup/php) and [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php) for free. Containers default to running Debian, but Alpine images are also available. :: +## What's Docker? +[Docker](https://www.docker.com/) is an open source utility for building, shipping, and running applications in containers. Containers are isolated environments that can run on any host. + +![Docker Layers with Laravel Application](/images/docs/docker-layers.png) + +### Why containerize with Docker? +Going through the efforts of containerizing your application gives you one extremely powerful capability: + +👉 **You can run your application anywhere** + +Once you're at this level, you unlock a ton of new possibilities: + +- **Your application runs the same, everywhere** - no more "it works on my machine". Run 100% of your application on any operating system. All they need is Docker installed. +- **Ship with more confidence** - all infrastructure configurations are *centrally managed* in Git, so you can change, test and rollback with ease +- **No more vendor lock-in** - if a host raises their prices on you, you can migrate with very little effort +- **Scaling is a breeze** - once you get your application in one container, it's so easy to scale up to any number of containers +- **Improved security** - containerized apps are more secure than traditional apps because they are isolated from the host +- **Better uptime** - if something fails during deployment, you can roll back your application to a previous version with ease + +The even crazier part is you don't need a Platform as a Service (PaaS) to get all these benefits. Everything can be done with 100% free and open source tools. + +## How to get started with Docker +At first, Docker may seem a little intimidating, but don't let that stop you. Docker is one of the most powerful skill-sets that we've learned in our career. + +Although teaching you how to use Docker is beyond the scope of serversideup/php, we've actually created another open source project called [Spin](https://serversideup.net/open-source/spin/) that dramatically reduces the learning curve for getting started with Docker. + +Here's a quick demo of Spin of what an app looks like when we use Docker from development to production: + +::video-embed +--- +src: https://www.youtube.com/watch?v=5z2JoEt5XIk +--- +:: + +[Learn more about Spin →](https://serversideup.net/open-source/spin/) + ## Our most popular images All images are intelligently tagged with the PHP version and variation. This allows you to easily select the right image for your use case. @@ -42,15 +78,26 @@ The `fpm-apache` variation is meant for users who want to run something like Wor [Learn more about using Docker with WordPress →](/docs/guide/using-wordpress-with-docker) ### FPM-NGINX -The `fpm-nginx` variation is great for people who want to run Laravel applications or similar. This allows you to serve static content quickly with NGINX but also pass PHP requests to PHP-FPM. Similar to PHP-Apache, there are two processes required to run this variation. We use S6 Overlay to ensure the container health is accurate. +Over the last 15+ years `fpm-nginx` has been "the way" to run PHP web applications, including Laravel. Since around 2010, it's been adopted and trusted by some of the best PHP system administrators out there. As of 2025, we're starting to see other options like FrankenPHP push the industry forward, but `fpm-nginx` is very stable and still widely used today. + +The only caveat with running FPM + NGINX together with Docker is it requires two processes (which can lead to strange container behaviors). Thankfully, we took this all in mind with our design by using 's been trusted and deployed millions of times and there's a large adoption This allows you to serve static content quickly with NGINX but also pass PHP requests to PHP-FPM. Similar to PHP-Apache, there are two processes required to run this variation. We use S6 Overlay to ensure the container health is accurate. [Learn more about S6 Overlay →](/docs/guide/using-s6-overlay) -### Unit -The `unit` variation is for NGINX Unit, which is a modern approach to delivering containerized web applications. Instead of relying on the complexities of two processes running NGINX + PHP-FPM, Unit replaces both the NGINX Web Server and PHP-FPM to run everything under one process. Unit is open source and maintained by the NGINX Unit team. +### Unit (deprecated) +::note +In October 2025, NGINX stopped supporting NGINX Unit and archived the project. NGINX Unit will eventually be removed from our project. [See the official announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit) +:: +The `unit` variation is for NGINX Unit, which is a NGINX's modern approach to delivering containerized web applications. [Learn more about Unit →](https://unit.nginx.org/) +### FrankenPHP + +The `frankenphp` variation is a modern application server for PHP built on top of the Caddy web server. Instead of relying on the complexities of two processes running NGINX + PHP-FPM, FrankenPHP replaces both to run everything under one process. FrankenPHP supports worker mode for Laravel and Symfony applications, automatic HTTPS, and modern protocols like HTTP/2 and HTTP/3. + +[Learn more about FrankenPHP →](https://frankenphp.dev/) + ## Selecting the version of PHP Selecting the best version of PHP is highly dependent on your use case. In a perfect world, running the latest version of PHP will give you the latest and greatest, but it all depends on the libraries that your application uses. diff --git a/docs/public/images/docs/docker-layers.png b/docs/public/images/docs/docker-layers.png new file mode 100644 index 0000000000000000000000000000000000000000..76526583fe991934700bb9e870bf9919d73eafe4 GIT binary patch literal 76743 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 Date: Tue, 14 Oct 2025 17:26:19 -0500 Subject: [PATCH 123/304] Renamed getting started folder --- .../1.these-images-vs-others.md | 0 .../{2.getting-started => 1.getting-started}/2.installation.md | 0 .../3.default-configurations.md | 1 + .../{2.getting-started => 1.getting-started}/3.upgrade-guide.md | 0 .../4.choosing-a-host.md | 0 .../docs/{2.getting-started => 1.getting-started}/5.changelog.md | 0 .../docs/{2.getting-started => 1.getting-started}/6.about.md | 0 .../{2.getting-started => 1.getting-started}/7.contributing.md | 0 8 files changed, 1 insertion(+) rename docs/content/docs/{2.getting-started => 1.getting-started}/1.these-images-vs-others.md (100%) rename docs/content/docs/{2.getting-started => 1.getting-started}/2.installation.md (100%) rename docs/content/docs/{2.getting-started => 1.getting-started}/3.default-configurations.md (99%) rename docs/content/docs/{2.getting-started => 1.getting-started}/3.upgrade-guide.md (100%) rename docs/content/docs/{2.getting-started => 1.getting-started}/4.choosing-a-host.md (100%) rename docs/content/docs/{2.getting-started => 1.getting-started}/5.changelog.md (100%) rename docs/content/docs/{2.getting-started => 1.getting-started}/6.about.md (100%) rename docs/content/docs/{2.getting-started => 1.getting-started}/7.contributing.md (100%) diff --git a/docs/content/docs/2.getting-started/1.these-images-vs-others.md b/docs/content/docs/1.getting-started/1.these-images-vs-others.md similarity index 100% rename from docs/content/docs/2.getting-started/1.these-images-vs-others.md rename to docs/content/docs/1.getting-started/1.these-images-vs-others.md diff --git a/docs/content/docs/2.getting-started/2.installation.md b/docs/content/docs/1.getting-started/2.installation.md similarity index 100% rename from docs/content/docs/2.getting-started/2.installation.md rename to docs/content/docs/1.getting-started/2.installation.md diff --git a/docs/content/docs/2.getting-started/3.default-configurations.md b/docs/content/docs/1.getting-started/3.default-configurations.md similarity index 99% rename from docs/content/docs/2.getting-started/3.default-configurations.md rename to docs/content/docs/1.getting-started/3.default-configurations.md index ff48f91b3..26ee7c1a3 100644 --- a/docs/content/docs/2.getting-started/3.default-configurations.md +++ b/docs/content/docs/1.getting-started/3.default-configurations.md @@ -28,6 +28,7 @@ Since these images are not privileged, that means they are not running on ports | fpm-nginx | HTTP: 8080, HTTPS: 8443 | | fpm-apache | HTTP: 8080, HTTPS: 8443 | | unit | HTTP: 8080, HTTPS: 8443 | +| frankenphp | HTTP: 8080, HTTPS: 8443 | ### How do I run these services on ports 80 and/or 443? Almost everyone will want to run these services on ports 80 and 443. If you have an advanced setup, you can use a reverse proxy like Caddy or Traefik to handle the SSL termination and forward the traffic to the container on the non-privileged port. diff --git a/docs/content/docs/2.getting-started/3.upgrade-guide.md b/docs/content/docs/1.getting-started/3.upgrade-guide.md similarity index 100% rename from docs/content/docs/2.getting-started/3.upgrade-guide.md rename to docs/content/docs/1.getting-started/3.upgrade-guide.md diff --git a/docs/content/docs/2.getting-started/4.choosing-a-host.md b/docs/content/docs/1.getting-started/4.choosing-a-host.md similarity index 100% rename from docs/content/docs/2.getting-started/4.choosing-a-host.md rename to docs/content/docs/1.getting-started/4.choosing-a-host.md diff --git a/docs/content/docs/2.getting-started/5.changelog.md b/docs/content/docs/1.getting-started/5.changelog.md similarity index 100% rename from docs/content/docs/2.getting-started/5.changelog.md rename to docs/content/docs/1.getting-started/5.changelog.md diff --git a/docs/content/docs/2.getting-started/6.about.md b/docs/content/docs/1.getting-started/6.about.md similarity index 100% rename from docs/content/docs/2.getting-started/6.about.md rename to docs/content/docs/1.getting-started/6.about.md diff --git a/docs/content/docs/2.getting-started/7.contributing.md b/docs/content/docs/1.getting-started/7.contributing.md similarity index 100% rename from docs/content/docs/2.getting-started/7.contributing.md rename to docs/content/docs/1.getting-started/7.contributing.md From 9bf823fa7a244da1074002e5b4e8e4a9e7344006 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 14 Oct 2025 17:27:03 -0500 Subject: [PATCH 124/304] Renamed folders --- .../docs/3.laravel/1.laravel-automations.md | 135 ++++++++++++++++++ .../3.laravel/2.laravel-task-scheduler.md | 119 +++++++++++++++ .../content/docs/3.laravel/3.laravel-queue.md | 55 +++++++ .../docs/3.laravel/4.laravel-horizon.md | 58 ++++++++ .../docs/3.laravel/4.laravel-reverb.md | 78 ++++++++++ .../1.migrating-from-official-php-images.md | 0 .../100.migrating-from-v2-to-v3.md | 0 .../2.understanding-file-permissions.md | 0 .../2.using-s6-overlay.md | 0 .../3.using-healthchecks-with-laravel.md | 12 +- .../4.using-wordpress-with-docker.md | 0 11 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 docs/content/docs/3.laravel/1.laravel-automations.md create mode 100644 docs/content/docs/3.laravel/2.laravel-task-scheduler.md create mode 100644 docs/content/docs/3.laravel/3.laravel-queue.md create mode 100644 docs/content/docs/3.laravel/4.laravel-horizon.md create mode 100644 docs/content/docs/3.laravel/4.laravel-reverb.md rename docs/content/docs/{3.guide => 4.guide}/1.migrating-from-official-php-images.md (100%) rename docs/content/docs/{3.guide => 4.guide}/100.migrating-from-v2-to-v3.md (100%) rename docs/content/docs/{3.guide => 4.guide}/2.understanding-file-permissions.md (100%) rename docs/content/docs/{3.guide => 4.guide}/2.using-s6-overlay.md (100%) rename docs/content/docs/{3.guide => 4.guide}/3.using-healthchecks-with-laravel.md (91%) rename docs/content/docs/{3.guide => 4.guide}/4.using-wordpress-with-docker.md (100%) diff --git a/docs/content/docs/3.laravel/1.laravel-automations.md b/docs/content/docs/3.laravel/1.laravel-automations.md new file mode 100644 index 000000000..4cacc12a6 --- /dev/null +++ b/docs/content/docs/3.laravel/1.laravel-automations.md @@ -0,0 +1,135 @@ +--- +head.title: 'Laravel Automations Script - Docker PHP - Server Side Up' +description: 'Automate your deployments and minimize your efforts with Laravel.' +layout: docs +--- + +# Laravel Automations +`serversideup/php` has a "Laravel Automations" script that helps automate common tasks to maintain your Laravel application and improve it's performance. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. + +## What the script does + +::note +In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once the main part of the script is enabled, you can control the individual tasks by setting the corresponding environment variables to `true` or `false`. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. +:: + +| Environment Variable | Default | Description | +| -------------------- | -------------- | ----------- | +| `AUTORUN_ENABLED` | `false` | Enables the Laravel Automations script.
**ℹ️ Note:** This must be set to `true` for the script to run. | +| `AUTORUN_DEBUG` | `false` | Enables a special debug mode, specifically for the Laravel Automations script. | +| `AUTORUN_LARAVEL_CONFIG_CACHE` | `true` | `php artisan config:cache`: Caches the configuration files into a single file. | +| `AUTORUN_LARAVEL_EVENT_CACHE` | `true` | `php artisan event:cache`: Creates a manifest of all your application's events and listeners. | +| `AUTORUN_LARAVEL_MIGRATION` | `true` | `php artisan migrate`: Runs migrations. | +| `AUTORUN_LARAVEL_MIGRATION_DATABASE` | `null` | Run migrations on a specific database. In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). If `null`, it will use the default database connection. | +| `AUTORUN_LARAVEL_MIGRATION_FORCE` | `true` | Force migrations to run in production without confirmation. Set to `false` to disable the `--force` flag. | +| `AUTORUN_LARAVEL_MIGRATION_ISOLATION` | `false` | Run your migrations with the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag.
**ℹ️ Note:** Requires Laravel v9.38.0+. Only works with `default` migration mode. | +| `AUTORUN_LARAVEL_MIGRATION_MODE` | `default` | Migration mode: `default`, `fresh`, or `refresh`.
**⚠️ Warning:** `fresh` and `refresh` drop all tables. | +| `AUTORUN_LARAVEL_MIGRATION_SEED` | `false` | Automatically seed the database after migrations using the `--seed` flag. | +| `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK` | `false` | Skip the database connection check before running migrations. | +| `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` | `30` | Number of seconds to wait for database connection before timing out during migrations. | +| `AUTORUN_LARAVEL_OPTIMIZE` | `true` | `php artisan optimize`: Optimizes the application. | +| `AUTORUN_LARAVEL_ROUTE_CACHE` | `true` | `php artisan route:cache`: Caches the routes. | +| `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | +| `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | + +## Database Connection Checks +Before running migrations, the script performs database connection checks to ensure the database is ready. It will: +- Clear the Laravel configuration cache before attempting migrations +- Try to connect to the database for up to `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` seconds (default: 30) +- Show detailed connection attempts when `AUTORUN_DEBUG` is enabled + +## php artisan storage:link +Creates a symbolic link from `public/storage` to `storage/app/public`. + +[Read more about storage links →](https://laravel.com/docs/12.x/filesystem#the-public-disk) + +## php artisan migrate +Before running migrations, we ensure the database is online and ready to accept connections. By default, we will wait 30 seconds before timing out. + +### Migration Modes +You can control how migrations run using `AUTORUN_LARAVEL_MIGRATION_MODE`: +- `default` (default): Runs `php artisan migrate` - standard forward migrations +- `fresh`: Runs `php artisan migrate:fresh` - drops all tables and re-runs migrations +- `refresh`: Runs `php artisan migrate:refresh` - rolls back and re-runs migrations + +::note +**⚠️ Warning:** Using `fresh` or `refresh` modes will **drop all tables** in your database. Only use these in development or testing environments. +:: + +### Force Flag +By default, migrations run with the `--force` flag to bypass production warnings. You can disable this by setting `AUTORUN_LARAVEL_MIGRATION_FORCE` to `false`. + +### Seeding +You can automatically seed your database after migrations by setting `AUTORUN_LARAVEL_MIGRATION_SEED` to `true`. This adds the `--seed` flag to your migration command. + +### Specific Database Migrations +If you need to specify the exact database connection to use for migrations, you can set `AUTORUN_LARAVEL_MIGRATION_DATABASE` to the name of the database connection you want to use. + +```bash +AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql +``` + +In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). + +```bash +AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql,pgsql +``` + +### Isolated Migrations +You can enable the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. + +**Special Notes for Isolated Migrations:** +- Requires Laravel v9.38.0+ +- Only works with `default` migration mode (not compatible with `fresh` or `refresh`) +- Your application must be using the memcached, redis, dynamodb, database, file, or array cache driver as your application's default cache driver. In addition, all servers must be communicating with the same central cache server. + +[Read more about migrations →](https://laravel.com/docs/12.x/migrations#running-migrations) + +## php artisan optimize +Laravel comes with an artisan command called `optimize`, which will optimize the application by caching the configuration, routes, views, and events all in one command. + +You can disable any cache features by setting the corresponding environment variable to `false` (for example, `AUTORUN_LARAVEL_CONFIG_CACHE` would disable configuration caching). + +If your application is running Laravel v11.38.0 or higher, we will utilize the `optimize --except` parameter to exclude any cache features you have disabled. Otherwise, we will run the individual optimizations separately. + +It's possible to disable the `optimize` command by setting `AUTORUN_LARAVEL_OPTIMIZE` to `false`, but the major advantage of using the `optimize` command is other dependencies may hook into this action and run other commands. + +[Read more about optimizing Laravel →](https://laravel.com/docs/12.x/deployment#optimization) + +## php artisan config:cache +This command caches all configuration files into a single file, which can then be quickly loaded by Laravel. Once the configuration is cache, the `.env` file will no longer be loaded. + +[Read more about configuration caching →](https://laravel.com/docs/12.x/configuration#configuration-caching) + +## php artisan route:cache +This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. + +[Read more about route caching →](https://laravel.com/docs/12.x/routing#route-caching) + +## php artisan view:cache +This command caches all of the views in your application, which can greatly decrease the time it takes to render your views. + +[Read more about view caching →](https://laravel.com/docs/12.x/views#optimizing-views) + +## php artisan event:cache +This command creates a manifest of all your application's events and listeners, which can greatly speed up the process of registering them with Laravel. + +[Read more about event caching →](https://laravel.com/docs/12.x/events#event-discovery-in-production) + +## Debugging the AUTORUN script +It's very important to understand the nature of how containerized environments work when debugging the AUTORUN script. In some cases, some users may become frustrated when they push an update but their changes are never deployed. + +In most cases, this is due to a bug in their application code that causes a migration or some other process to fail. + +::note +If a failure occurs in the Laravel Automations script, it will exit with a non-zero exit code -- preventing the container from starting. +:: + +If you are experiencing issues, you can enable the `AUTORUN_DEBUG` environment variable to get more detailed ouput of what could be going wrong. + +If you need even more information, you can set `LOG_OUTPUT_LEVEL` to `debug` to get **A TON** of output of what's exactly happening. + +### Preventing issues with the AUTORUN script +- Ensure you are running the latest version of `serversideup/php` +- Ensure you have dependencies installed before calling this script +- Use automated testing to catch issues before deploying \ No newline at end of file diff --git a/docs/content/docs/3.laravel/2.laravel-task-scheduler.md b/docs/content/docs/3.laravel/2.laravel-task-scheduler.md new file mode 100644 index 000000000..a3e9a8b1d --- /dev/null +++ b/docs/content/docs/3.laravel/2.laravel-task-scheduler.md @@ -0,0 +1,119 @@ +--- +head.title: 'Laravel Task Scheduler with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure a Laravel Task Scheduler with Docker.' +layout: docs +--- + +# Laravel Task Scheduler with Docker +Running a Laravel task scheduler with Docker can be a little different from the traditional methods. + +## Important concepts +1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) +1. Be sure to set the health check +1. We will **not** use `cron` to run the scheduler +1. By default `schedule:work` checks every minute, so we will use that to run the system process +1. The actual time trigger itself is set within Laravel + +## More detail +We need to run the [schedule:work](https://laravel.com/docs/12.x/scheduling#running-the-scheduler-locally) command from Laravel. Although the docs say "Running the scheduler locally", this is what we want in production. It will run the scheduler in the foreground and execute it every minute. You can configure your Laravel app for the exact time that a command should run through a [scheduled task](https://laravel.com/docs/12.x/scheduling#scheduling-artisan-commands). + + +## Examples +Here is a simplified example of how you can achieve this with Docker Compose: + +::note +Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. +:: + +::code-panel +--- +label: Example & Simplified Docker Compose File +--- +```yaml +services: + php: + image: my/laravel-app + + task: + image: my/laravel-app + command: ["php", "/var/www/html/artisan", "schedule:work"] + stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx + healthcheck: + # This is our native healthcheck script for the scheduler + test: ["CMD", "healthcheck-schedule"] + start_period: 10s +``` +:: + + +This is an example how we would set the actual execution time within Laravel itself: +::code-panel +--- +label: Example in Laravel (version <= 10) using `Kernel.php` +--- +```php +command('process:invoices')->daily()->at('02:00')->timezone('America/Chicago'); + $schedule->command('process:latefees')->daily()->at('04:00')->timezone('America/Chicago'); + } + + /** + * Register the commands for the application. + * + * @return void + */ + protected function commands() + { + $this->load(__DIR__.'/Commands'); + + require base_path('routes/console.php'); + } +} +``` +:: + +::code-panel +--- +label: Example in Laravel (version >= 11) using `routes/console.php` +--- +```php +delete(); +})->daily()->at('04:00')->timezone('America/Chicago'); +``` +:: + +## Get Up and Running The Easy Way +We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with the Task Scheduler on Spin Pro. + +[Learn more about Laravel Task Scheduler + Spin Pro →](https://getspin.pro/docs/services/laravel-scheduler) diff --git a/docs/content/docs/3.laravel/3.laravel-queue.md b/docs/content/docs/3.laravel/3.laravel-queue.md new file mode 100644 index 000000000..1c3800485 --- /dev/null +++ b/docs/content/docs/3.laravel/3.laravel-queue.md @@ -0,0 +1,55 @@ +--- +head.title: 'Laravel Queue with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure a Laravel Queue with Docker.' +layout: docs +--- + +# Laravel Queue with Docker +All you need to do is pass the Laravel Queue command to the container and Laravel will start the queue worker. + + +::code-panel +--- +label: Usual Laravel Queue Command +--- +```sh +php artisan queue:work --tries=3 +``` +:: + +## Important concepts +1. It's usually best to run the queue as a separate container (but using the same image) +1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) +1. Be sure to set the health check +1. Notice we're using the same `my/laravel-app` image for both the PHP and Queue services. This is a common practice to keep the image consistent. +1. If you need to run the queue in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process) to manage and monitor multiple processes in one container. + +## Run it with Docker +::note +Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. +:: + +::code-panel +--- +label: Example & Simplified Docker Compose File +--- +```yaml +services: + php: + image: my/laravel-app + + queue: + image: my/laravel-app + command: ["php", "/var/www/html/artisan", "queue:work", "--tries=3"] + stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx + healthcheck: + # This is our native healthcheck script for the queue + test: ["CMD", "healthcheck-queue"] + start_period: 10s +``` +:: + +## Get Up and Running The Easy Way +We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Queues on Spin Pro. + +[Learn more about Laravel Queues + Spin Pro →](https://getspin.pro/docs/services/laravel-queues) \ No newline at end of file diff --git a/docs/content/docs/3.laravel/4.laravel-horizon.md b/docs/content/docs/3.laravel/4.laravel-horizon.md new file mode 100644 index 000000000..fe6ae57c0 --- /dev/null +++ b/docs/content/docs/3.laravel/4.laravel-horizon.md @@ -0,0 +1,58 @@ +--- +head.title: 'Laravel Horizon with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure Laravel Horizon with Docker.' +layout: docs +--- + +# Laravel Horizon with Docker +We simply pass the command to the Docker container and Laravel will start the Horizon process. + +::code-panel +--- +label: Usual Laravel Horizon Command +--- +```sh +php artisan horizon +``` +:: + +## Important concepts +1. In most cases, you probably want to run this as a separate container from your web container +1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) +1. Be sure to set the health check +1. Ensure that you have your `.env` configured correctly to authenticate with Redis +1. Ensure Redis is running before you attempt to connect Horizon to Redis +1. If you need to run horizon in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process) to manage and monitor multiple processes in one container. + +## Run it with Docker +::note +Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. +:: +::code-panel +--- +label: Example Docker Compose File +--- +```yaml +services: + php: + image: my/laravel-app + + redis: + image: redis:6 + command: "redis-server --appendonly yes --requirepass redispassword" + + horizon: + image: my/laravel-app + command: ["php", "/var/www/html/artisan", "horizon"] + stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx + healthcheck: + # This is our native healthcheck script for Horizon + test: ["CMD", "healthcheck-horizon"] + start_period: 10s +``` +:: + +## Get Up and Running The Easy Way +We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Horizon on Spin Pro. + +[Learn more about Laravel Horizon + Spin Pro →](https://getspin.pro/docs/services/laravel-horizon) \ No newline at end of file diff --git a/docs/content/docs/3.laravel/4.laravel-reverb.md b/docs/content/docs/3.laravel/4.laravel-reverb.md new file mode 100644 index 000000000..811636db6 --- /dev/null +++ b/docs/content/docs/3.laravel/4.laravel-reverb.md @@ -0,0 +1,78 @@ +--- +head.title: 'Laravel Reverb with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure Laravel Reverb with Docker.' +layout: docs +--- + +# Laravel Reverb with Docker +We simply pass the command to the Docker container and Laravel will start the Reverb process. + +::code-panel +--- +label: Usual Laravel Reverb Command +--- +```sh +php artisan reverb:start +``` +:: + +## Important concepts +1. You will need to follow the [Laravel Reverb setup instructions](https://laravel.com/docs/12.x/reverb) to install the Laravel Reverb package into your Laravel application. +1. In most cases, you probably want to run this as a separate container from your web container +1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) +1. Be sure to set the health check +1. You may need a proxy like Traefik to correctly route traffic to the right container +1. If you need to run Reverb in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process) to manage and monitor multiple processes in one container. + +## Run it with Docker +::note +Notice Laravel Reverb is running on port `8000`, where as Laravel is running on port `8080`. You may need to set additional environment variables and configure a reverse proxy like Traefik to correctly route traffic to the right container. +:: +::code-panel +--- +label: Example Docker Compose File +--- +```yaml +services: + php: + image: my/laravel-app + labels: + - "traefik.enable=true" + - "traefik.http.routers.laravel.tls=true" + - "traefik.http.routers.laravel.entrypoints=websecure" + - "traefik.http.routers.laravel.rule=Host(`https://app.example.com`)" + - "traefik.http.services.laravel.loadbalancer.server.port=8080" + - "traefik.http.services.laravel.loadbalancer.server.scheme=http" + + reverb: + image: my/laravel-app + command: ["php", "/var/www/html/artisan", "--port=8000", "reverb:start"] + stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx + healthcheck: + # This is our native healthcheck script for Reverb + test: ["CMD", "healthcheck-reverb"] + start_period: 10s + labels: + - "traefik.enable=true" + - "traefik.http.routers.reverb.tls=true" + - "traefik.http.routers.reverb.entrypoints=websecure" + - "traefik.http.routers.reverb.rule=Host(`https://reverb.example.com`)" + - "traefik.http.services.reverb.loadbalancer.server.port=8000" + - "traefik.http.services.reverb.loadbalancer.server.scheme=http" +``` +:: + +## Laravel ENV updates +Reverb may require a few ENV variables to be set in your Laravel application. +| **Laravel ENV Variable** | **Description** | **Value if matching example above** | +| ------------------------- | --------- | --------- | +| `REVERB_HOST` | The hostname the **CLIENT** will connect to. | `reverb.example.com` | +| `REVERB_PORT` | The port the **CLIENT** will connect to. | `443` | +| `REVERB_SCHEME` | The scheme the **CLIENT** will connect to. | `https` | + +Be sure to not get `REVERB_HOST` or `REVERB_PORT` confused with `REVERB_SERVER_HOST` or `REVERB_SERVER_PORT`. The `_SERVER_` variables are for the **SERVER** (the Reverb daemon itself) and the others are for the **CLIENT** (people connecting to your application). + +## Get Up and Running The Easy Way +We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Reverb on Spin Pro. + +[Learn more about Laravel Reverb + Spin Pro →](https://getspin.pro/docs/services/laravel-reverb) \ No newline at end of file diff --git a/docs/content/docs/3.guide/1.migrating-from-official-php-images.md b/docs/content/docs/4.guide/1.migrating-from-official-php-images.md similarity index 100% rename from docs/content/docs/3.guide/1.migrating-from-official-php-images.md rename to docs/content/docs/4.guide/1.migrating-from-official-php-images.md diff --git a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md b/docs/content/docs/4.guide/100.migrating-from-v2-to-v3.md similarity index 100% rename from docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md rename to docs/content/docs/4.guide/100.migrating-from-v2-to-v3.md diff --git a/docs/content/docs/3.guide/2.understanding-file-permissions.md b/docs/content/docs/4.guide/2.understanding-file-permissions.md similarity index 100% rename from docs/content/docs/3.guide/2.understanding-file-permissions.md rename to docs/content/docs/4.guide/2.understanding-file-permissions.md diff --git a/docs/content/docs/3.guide/2.using-s6-overlay.md b/docs/content/docs/4.guide/2.using-s6-overlay.md similarity index 100% rename from docs/content/docs/3.guide/2.using-s6-overlay.md rename to docs/content/docs/4.guide/2.using-s6-overlay.md diff --git a/docs/content/docs/3.guide/3.using-healthchecks-with-laravel.md b/docs/content/docs/4.guide/3.using-healthchecks-with-laravel.md similarity index 91% rename from docs/content/docs/3.guide/3.using-healthchecks-with-laravel.md rename to docs/content/docs/4.guide/3.using-healthchecks-with-laravel.md index 0d9eecdb4..6faeefbaf 100644 --- a/docs/content/docs/3.guide/3.using-healthchecks-with-laravel.md +++ b/docs/content/docs/4.guide/3.using-healthchecks-with-laravel.md @@ -13,6 +13,15 @@ Dialing in health checks are very important for ensuring your application is run Health checks are a way to check the status of your application. Whenever a container is started, a health check is performed. If the health check fails, the container will be restarted or marked as unhealthy. Health checks are very important for rolling updates and ensuring your application can start up in order if you have services that depend on each other. +## See it in action +Check this video out to get a full understand of how our health checks work: + +::video-embed +--- +src: https://www.youtube.com/watch?v=cuYIB5VrH1Q +--- +:: + ## Our Health Checks We offer a number of health check commands, specifically for Laravel. You can find these commands are prefixed with `healthcheck-` and are located in [`/usr/local/bin`](https://github.com/serversideup/docker-php/tree/main/src/common/usr/local/bin). The examples below show how to use these health checks in your `docker-compose.yml` file, but you can also use them in other environments. @@ -25,9 +34,10 @@ By default, our Dockerfiles ship with the following health check commands: | `fpm` | [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck) | | `fpm-apache` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | | `fpm-nginx` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | +| `frankenphp` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | ::note -Notice how `fpm-apache` and `fpm-nginx` have a `$HEALTHCHECK_PATH` variable? This is because you can specify a path for the health check to validate. +Notice how `fpm-apache`, `fpm-nginx`, and `frankenphp` have a `$HEALTHCHECK_PATH` variable? This is because you can specify a path for the health check to validate. :: ## Changing the Health Check Path diff --git a/docs/content/docs/3.guide/4.using-wordpress-with-docker.md b/docs/content/docs/4.guide/4.using-wordpress-with-docker.md similarity index 100% rename from docs/content/docs/3.guide/4.using-wordpress-with-docker.md rename to docs/content/docs/4.guide/4.using-wordpress-with-docker.md From 5c6928cdddef3c1bf7959ccd337ea738f2cb4957 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 14 Oct 2025 17:27:17 -0500 Subject: [PATCH 125/304] Moved folders --- .../docs/4.laravel/1.laravel-automations.md | 135 ------------------ .../4.laravel/2.laravel-task-scheduler.md | 119 --------------- .../content/docs/4.laravel/3.laravel-queue.md | 55 ------- .../docs/4.laravel/4.laravel-horizon.md | 58 -------- .../docs/4.laravel/4.laravel-reverb.md | 78 ---------- 5 files changed, 445 deletions(-) delete mode 100644 docs/content/docs/4.laravel/1.laravel-automations.md delete mode 100644 docs/content/docs/4.laravel/2.laravel-task-scheduler.md delete mode 100644 docs/content/docs/4.laravel/3.laravel-queue.md delete mode 100644 docs/content/docs/4.laravel/4.laravel-horizon.md delete mode 100644 docs/content/docs/4.laravel/4.laravel-reverb.md diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md deleted file mode 100644 index 4cacc12a6..000000000 --- a/docs/content/docs/4.laravel/1.laravel-automations.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -head.title: 'Laravel Automations Script - Docker PHP - Server Side Up' -description: 'Automate your deployments and minimize your efforts with Laravel.' -layout: docs ---- - -# Laravel Automations -`serversideup/php` has a "Laravel Automations" script that helps automate common tasks to maintain your Laravel application and improve it's performance. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. - -## What the script does - -::note -In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once the main part of the script is enabled, you can control the individual tasks by setting the corresponding environment variables to `true` or `false`. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. -:: - -| Environment Variable | Default | Description | -| -------------------- | -------------- | ----------- | -| `AUTORUN_ENABLED` | `false` | Enables the Laravel Automations script.
**ℹ️ Note:** This must be set to `true` for the script to run. | -| `AUTORUN_DEBUG` | `false` | Enables a special debug mode, specifically for the Laravel Automations script. | -| `AUTORUN_LARAVEL_CONFIG_CACHE` | `true` | `php artisan config:cache`: Caches the configuration files into a single file. | -| `AUTORUN_LARAVEL_EVENT_CACHE` | `true` | `php artisan event:cache`: Creates a manifest of all your application's events and listeners. | -| `AUTORUN_LARAVEL_MIGRATION` | `true` | `php artisan migrate`: Runs migrations. | -| `AUTORUN_LARAVEL_MIGRATION_DATABASE` | `null` | Run migrations on a specific database. In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). If `null`, it will use the default database connection. | -| `AUTORUN_LARAVEL_MIGRATION_FORCE` | `true` | Force migrations to run in production without confirmation. Set to `false` to disable the `--force` flag. | -| `AUTORUN_LARAVEL_MIGRATION_ISOLATION` | `false` | Run your migrations with the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag.
**ℹ️ Note:** Requires Laravel v9.38.0+. Only works with `default` migration mode. | -| `AUTORUN_LARAVEL_MIGRATION_MODE` | `default` | Migration mode: `default`, `fresh`, or `refresh`.
**⚠️ Warning:** `fresh` and `refresh` drop all tables. | -| `AUTORUN_LARAVEL_MIGRATION_SEED` | `false` | Automatically seed the database after migrations using the `--seed` flag. | -| `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK` | `false` | Skip the database connection check before running migrations. | -| `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` | `30` | Number of seconds to wait for database connection before timing out during migrations. | -| `AUTORUN_LARAVEL_OPTIMIZE` | `true` | `php artisan optimize`: Optimizes the application. | -| `AUTORUN_LARAVEL_ROUTE_CACHE` | `true` | `php artisan route:cache`: Caches the routes. | -| `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | -| `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | - -## Database Connection Checks -Before running migrations, the script performs database connection checks to ensure the database is ready. It will: -- Clear the Laravel configuration cache before attempting migrations -- Try to connect to the database for up to `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` seconds (default: 30) -- Show detailed connection attempts when `AUTORUN_DEBUG` is enabled - -## php artisan storage:link -Creates a symbolic link from `public/storage` to `storage/app/public`. - -[Read more about storage links →](https://laravel.com/docs/12.x/filesystem#the-public-disk) - -## php artisan migrate -Before running migrations, we ensure the database is online and ready to accept connections. By default, we will wait 30 seconds before timing out. - -### Migration Modes -You can control how migrations run using `AUTORUN_LARAVEL_MIGRATION_MODE`: -- `default` (default): Runs `php artisan migrate` - standard forward migrations -- `fresh`: Runs `php artisan migrate:fresh` - drops all tables and re-runs migrations -- `refresh`: Runs `php artisan migrate:refresh` - rolls back and re-runs migrations - -::note -**⚠️ Warning:** Using `fresh` or `refresh` modes will **drop all tables** in your database. Only use these in development or testing environments. -:: - -### Force Flag -By default, migrations run with the `--force` flag to bypass production warnings. You can disable this by setting `AUTORUN_LARAVEL_MIGRATION_FORCE` to `false`. - -### Seeding -You can automatically seed your database after migrations by setting `AUTORUN_LARAVEL_MIGRATION_SEED` to `true`. This adds the `--seed` flag to your migration command. - -### Specific Database Migrations -If you need to specify the exact database connection to use for migrations, you can set `AUTORUN_LARAVEL_MIGRATION_DATABASE` to the name of the database connection you want to use. - -```bash -AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql -``` - -In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). - -```bash -AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql,pgsql -``` - -### Isolated Migrations -You can enable the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. - -**Special Notes for Isolated Migrations:** -- Requires Laravel v9.38.0+ -- Only works with `default` migration mode (not compatible with `fresh` or `refresh`) -- Your application must be using the memcached, redis, dynamodb, database, file, or array cache driver as your application's default cache driver. In addition, all servers must be communicating with the same central cache server. - -[Read more about migrations →](https://laravel.com/docs/12.x/migrations#running-migrations) - -## php artisan optimize -Laravel comes with an artisan command called `optimize`, which will optimize the application by caching the configuration, routes, views, and events all in one command. - -You can disable any cache features by setting the corresponding environment variable to `false` (for example, `AUTORUN_LARAVEL_CONFIG_CACHE` would disable configuration caching). - -If your application is running Laravel v11.38.0 or higher, we will utilize the `optimize --except` parameter to exclude any cache features you have disabled. Otherwise, we will run the individual optimizations separately. - -It's possible to disable the `optimize` command by setting `AUTORUN_LARAVEL_OPTIMIZE` to `false`, but the major advantage of using the `optimize` command is other dependencies may hook into this action and run other commands. - -[Read more about optimizing Laravel →](https://laravel.com/docs/12.x/deployment#optimization) - -## php artisan config:cache -This command caches all configuration files into a single file, which can then be quickly loaded by Laravel. Once the configuration is cache, the `.env` file will no longer be loaded. - -[Read more about configuration caching →](https://laravel.com/docs/12.x/configuration#configuration-caching) - -## php artisan route:cache -This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. - -[Read more about route caching →](https://laravel.com/docs/12.x/routing#route-caching) - -## php artisan view:cache -This command caches all of the views in your application, which can greatly decrease the time it takes to render your views. - -[Read more about view caching →](https://laravel.com/docs/12.x/views#optimizing-views) - -## php artisan event:cache -This command creates a manifest of all your application's events and listeners, which can greatly speed up the process of registering them with Laravel. - -[Read more about event caching →](https://laravel.com/docs/12.x/events#event-discovery-in-production) - -## Debugging the AUTORUN script -It's very important to understand the nature of how containerized environments work when debugging the AUTORUN script. In some cases, some users may become frustrated when they push an update but their changes are never deployed. - -In most cases, this is due to a bug in their application code that causes a migration or some other process to fail. - -::note -If a failure occurs in the Laravel Automations script, it will exit with a non-zero exit code -- preventing the container from starting. -:: - -If you are experiencing issues, you can enable the `AUTORUN_DEBUG` environment variable to get more detailed ouput of what could be going wrong. - -If you need even more information, you can set `LOG_OUTPUT_LEVEL` to `debug` to get **A TON** of output of what's exactly happening. - -### Preventing issues with the AUTORUN script -- Ensure you are running the latest version of `serversideup/php` -- Ensure you have dependencies installed before calling this script -- Use automated testing to catch issues before deploying \ No newline at end of file diff --git a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md deleted file mode 100644 index a3e9a8b1d..000000000 --- a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -head.title: 'Laravel Task Scheduler with Docker - Docker PHP - Server Side Up' -description: 'Learn how to configure a Laravel Task Scheduler with Docker.' -layout: docs ---- - -# Laravel Task Scheduler with Docker -Running a Laravel task scheduler with Docker can be a little different from the traditional methods. - -## Important concepts -1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) -1. Be sure to set the health check -1. We will **not** use `cron` to run the scheduler -1. By default `schedule:work` checks every minute, so we will use that to run the system process -1. The actual time trigger itself is set within Laravel - -## More detail -We need to run the [schedule:work](https://laravel.com/docs/12.x/scheduling#running-the-scheduler-locally) command from Laravel. Although the docs say "Running the scheduler locally", this is what we want in production. It will run the scheduler in the foreground and execute it every minute. You can configure your Laravel app for the exact time that a command should run through a [scheduled task](https://laravel.com/docs/12.x/scheduling#scheduling-artisan-commands). - - -## Examples -Here is a simplified example of how you can achieve this with Docker Compose: - -::note -Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. -:: - -::code-panel ---- -label: Example & Simplified Docker Compose File ---- -```yaml -services: - php: - image: my/laravel-app - - task: - image: my/laravel-app - command: ["php", "/var/www/html/artisan", "schedule:work"] - stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx - healthcheck: - # This is our native healthcheck script for the scheduler - test: ["CMD", "healthcheck-schedule"] - start_period: 10s -``` -:: - - -This is an example how we would set the actual execution time within Laravel itself: -::code-panel ---- -label: Example in Laravel (version <= 10) using `Kernel.php` ---- -```php -command('process:invoices')->daily()->at('02:00')->timezone('America/Chicago'); - $schedule->command('process:latefees')->daily()->at('04:00')->timezone('America/Chicago'); - } - - /** - * Register the commands for the application. - * - * @return void - */ - protected function commands() - { - $this->load(__DIR__.'/Commands'); - - require base_path('routes/console.php'); - } -} -``` -:: - -::code-panel ---- -label: Example in Laravel (version >= 11) using `routes/console.php` ---- -```php -delete(); -})->daily()->at('04:00')->timezone('America/Chicago'); -``` -:: - -## Get Up and Running The Easy Way -We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with the Task Scheduler on Spin Pro. - -[Learn more about Laravel Task Scheduler + Spin Pro →](https://getspin.pro/docs/services/laravel-scheduler) diff --git a/docs/content/docs/4.laravel/3.laravel-queue.md b/docs/content/docs/4.laravel/3.laravel-queue.md deleted file mode 100644 index 1c3800485..000000000 --- a/docs/content/docs/4.laravel/3.laravel-queue.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -head.title: 'Laravel Queue with Docker - Docker PHP - Server Side Up' -description: 'Learn how to configure a Laravel Queue with Docker.' -layout: docs ---- - -# Laravel Queue with Docker -All you need to do is pass the Laravel Queue command to the container and Laravel will start the queue worker. - - -::code-panel ---- -label: Usual Laravel Queue Command ---- -```sh -php artisan queue:work --tries=3 -``` -:: - -## Important concepts -1. It's usually best to run the queue as a separate container (but using the same image) -1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) -1. Be sure to set the health check -1. Notice we're using the same `my/laravel-app` image for both the PHP and Queue services. This is a common practice to keep the image consistent. -1. If you need to run the queue in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process) to manage and monitor multiple processes in one container. - -## Run it with Docker -::note -Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. -:: - -::code-panel ---- -label: Example & Simplified Docker Compose File ---- -```yaml -services: - php: - image: my/laravel-app - - queue: - image: my/laravel-app - command: ["php", "/var/www/html/artisan", "queue:work", "--tries=3"] - stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx - healthcheck: - # This is our native healthcheck script for the queue - test: ["CMD", "healthcheck-queue"] - start_period: 10s -``` -:: - -## Get Up and Running The Easy Way -We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Queues on Spin Pro. - -[Learn more about Laravel Queues + Spin Pro →](https://getspin.pro/docs/services/laravel-queues) \ No newline at end of file diff --git a/docs/content/docs/4.laravel/4.laravel-horizon.md b/docs/content/docs/4.laravel/4.laravel-horizon.md deleted file mode 100644 index fe6ae57c0..000000000 --- a/docs/content/docs/4.laravel/4.laravel-horizon.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -head.title: 'Laravel Horizon with Docker - Docker PHP - Server Side Up' -description: 'Learn how to configure Laravel Horizon with Docker.' -layout: docs ---- - -# Laravel Horizon with Docker -We simply pass the command to the Docker container and Laravel will start the Horizon process. - -::code-panel ---- -label: Usual Laravel Horizon Command ---- -```sh -php artisan horizon -``` -:: - -## Important concepts -1. In most cases, you probably want to run this as a separate container from your web container -1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) -1. Be sure to set the health check -1. Ensure that you have your `.env` configured correctly to authenticate with Redis -1. Ensure Redis is running before you attempt to connect Horizon to Redis -1. If you need to run horizon in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process) to manage and monitor multiple processes in one container. - -## Run it with Docker -::note -Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. -:: -::code-panel ---- -label: Example Docker Compose File ---- -```yaml -services: - php: - image: my/laravel-app - - redis: - image: redis:6 - command: "redis-server --appendonly yes --requirepass redispassword" - - horizon: - image: my/laravel-app - command: ["php", "/var/www/html/artisan", "horizon"] - stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx - healthcheck: - # This is our native healthcheck script for Horizon - test: ["CMD", "healthcheck-horizon"] - start_period: 10s -``` -:: - -## Get Up and Running The Easy Way -We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Horizon on Spin Pro. - -[Learn more about Laravel Horizon + Spin Pro →](https://getspin.pro/docs/services/laravel-horizon) \ No newline at end of file diff --git a/docs/content/docs/4.laravel/4.laravel-reverb.md b/docs/content/docs/4.laravel/4.laravel-reverb.md deleted file mode 100644 index 811636db6..000000000 --- a/docs/content/docs/4.laravel/4.laravel-reverb.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -head.title: 'Laravel Reverb with Docker - Docker PHP - Server Side Up' -description: 'Learn how to configure Laravel Reverb with Docker.' -layout: docs ---- - -# Laravel Reverb with Docker -We simply pass the command to the Docker container and Laravel will start the Reverb process. - -::code-panel ---- -label: Usual Laravel Reverb Command ---- -```sh -php artisan reverb:start -``` -:: - -## Important concepts -1. You will need to follow the [Laravel Reverb setup instructions](https://laravel.com/docs/12.x/reverb) to install the Laravel Reverb package into your Laravel application. -1. In most cases, you probably want to run this as a separate container from your web container -1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437) for more details why) -1. Be sure to set the health check -1. You may need a proxy like Traefik to correctly route traffic to the right container -1. If you need to run Reverb in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process) to manage and monitor multiple processes in one container. - -## Run it with Docker -::note -Notice Laravel Reverb is running on port `8000`, where as Laravel is running on port `8080`. You may need to set additional environment variables and configure a reverse proxy like Traefik to correctly route traffic to the right container. -:: -::code-panel ---- -label: Example Docker Compose File ---- -```yaml -services: - php: - image: my/laravel-app - labels: - - "traefik.enable=true" - - "traefik.http.routers.laravel.tls=true" - - "traefik.http.routers.laravel.entrypoints=websecure" - - "traefik.http.routers.laravel.rule=Host(`https://app.example.com`)" - - "traefik.http.services.laravel.loadbalancer.server.port=8080" - - "traefik.http.services.laravel.loadbalancer.server.scheme=http" - - reverb: - image: my/laravel-app - command: ["php", "/var/www/html/artisan", "--port=8000", "reverb:start"] - stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx - healthcheck: - # This is our native healthcheck script for Reverb - test: ["CMD", "healthcheck-reverb"] - start_period: 10s - labels: - - "traefik.enable=true" - - "traefik.http.routers.reverb.tls=true" - - "traefik.http.routers.reverb.entrypoints=websecure" - - "traefik.http.routers.reverb.rule=Host(`https://reverb.example.com`)" - - "traefik.http.services.reverb.loadbalancer.server.port=8000" - - "traefik.http.services.reverb.loadbalancer.server.scheme=http" -``` -:: - -## Laravel ENV updates -Reverb may require a few ENV variables to be set in your Laravel application. -| **Laravel ENV Variable** | **Description** | **Value if matching example above** | -| ------------------------- | --------- | --------- | -| `REVERB_HOST` | The hostname the **CLIENT** will connect to. | `reverb.example.com` | -| `REVERB_PORT` | The port the **CLIENT** will connect to. | `443` | -| `REVERB_SCHEME` | The scheme the **CLIENT** will connect to. | `https` | - -Be sure to not get `REVERB_HOST` or `REVERB_PORT` confused with `REVERB_SERVER_HOST` or `REVERB_SERVER_PORT`. The `_SERVER_` variables are for the **SERVER** (the Reverb daemon itself) and the others are for the **CLIENT** (people connecting to your application). - -## Get Up and Running The Easy Way -We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Reverb on Spin Pro. - -[Learn more about Laravel Reverb + Spin Pro →](https://getspin.pro/docs/services/laravel-reverb) \ No newline at end of file From 7d818f55c80538c28a6b3b4098886044ee2e7d15 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 14 Oct 2025 17:27:25 -0500 Subject: [PATCH 126/304] Remove version declaration from Docker Compose example in PHP settings documentation --- .../5.customizing-the-image/1.changing-common-php-settings.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/content/docs/5.customizing-the-image/1.changing-common-php-settings.md b/docs/content/docs/5.customizing-the-image/1.changing-common-php-settings.md index 1a22cb211..ed3fea0d0 100644 --- a/docs/content/docs/5.customizing-the-image/1.changing-common-php-settings.md +++ b/docs/content/docs/5.customizing-the-image/1.changing-common-php-settings.md @@ -19,7 +19,6 @@ Here are a few examples on how you can change common PHP settings. label: "Docker Compose: Changing allowed upload size" --- ```yaml -version: '3' services: php: image: serversideup/php:8.2.12-unit-bookworm From bbbefc314731a103de2f59487ae1cd2c588496c2 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 14 Oct 2025 17:54:12 -0500 Subject: [PATCH 127/304] Added "acme" certificate mode for FrankenPHP --- .../4.configuring-ssl.md | 1 + .../etc/entrypoint.d/10-generate-ssl.sh | 3 +++ .../etc/frankenphp/ssl-mode/acme.caddyfile | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 src/variations/frankenphp/etc/frankenphp/ssl-mode/acme.caddyfile diff --git a/docs/content/docs/5.customizing-the-image/4.configuring-ssl.md b/docs/content/docs/5.customizing-the-image/4.configuring-ssl.md index b015b7f9d..229f6f168 100644 --- a/docs/content/docs/5.customizing-the-image/4.configuring-ssl.md +++ b/docs/content/docs/5.customizing-the-image/4.configuring-ssl.md @@ -10,6 +10,7 @@ SSL is disabled by default but can be turned on by setting `SSL_MODE`: - `off` (default): HTTP only. - `mixed`: HTTP and HTTPS. - `full`: HTTPS only. HTTP requests will be redirected to HTTPS. +- `acme`: Automatic HTTPS using Let's Encrypt. (Only available in `frankenphp`) ## Self-signed Certificate Example If you set `SSL_MODE` to `mixed` or `full`, a self-signed certificate will be generated by default. diff --git a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh index abbd96d61..2ead8416e 100644 --- a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh +++ b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh @@ -12,6 +12,9 @@ SSL_MODE=${SSL_MODE:-"off"} if [ "$SSL_MODE" = "off" ]; then echo "ℹ️ NOTICE ($script_name): SSL mode is off, so we'll not generate a self-signed SSL certificate and key." return 0 +elif [ "$SSL_MODE" = "acme" ]; then + echo "ℹ️ NOTICE ($script_name): SSL mode is acme, so we'll not generate a self-signed SSL certificate and key." + return 0 fi if [ -z "$SSL_CERTIFICATE_FILE" ] || [ -z "$SSL_PRIVATE_KEY_FILE" ]; then diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/acme.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/acme.caddyfile new file mode 100644 index 000000000..5c8576f42 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/acme.caddyfile @@ -0,0 +1,18 @@ +{$CADDY_HTTP_SERVER_ADDRESS:http://} { + # Redirect localhost healthcheck requests to HTTPS with the correct port + @healthcheck { + remote_ip 127.0.0.1/8 ::1 + path /healthcheck # Caddy healthcheck endpoint + path {$HEALTHCHECK_PATH:/healthcheck} # Custom healthcheck endpoint + } + log_skip @healthcheck + redir @healthcheck https://localhost:{$CADDY_HTTPS_PORT:8443}{uri} 308 + + # Redirect all other traffic to HTTPS (without explicit port) + redir https://{host}{uri} 308 +} + +{$CADDY_HTTPS_SERVER_ADDRESS:https://} { + import app-common + import security-https +} From b8fe41a544b5030dbded2a06aa5b176d7aa14528 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 15 Oct 2025 16:07:44 -0500 Subject: [PATCH 128/304] Set proper XDG_* paths --- src/variations/frankenphp/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index dcb860d12..d7f341ccc 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -19,8 +19,8 @@ RUN set -eux; \ # Create directories mkdir -p \ /var/www/html/public \ - /var/www/config/caddy \ - /var/www/data/caddy \ + /config/caddy \ + /data/caddy \ /etc/caddy \ /etc/frankenphp/ssl-mode \ /etc/frankenphp/log-level \ From e029eafdf71f96a646d66bbc374d1378c118cfea Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 15 Oct 2025 18:00:06 -0500 Subject: [PATCH 129/304] Added FrankenPHP version during build (Fixes #577) --- src/variations/frankenphp/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index d7f341ccc..1655e51ba 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -98,7 +98,7 @@ RUN if cat /etc/os-release | grep -q 'debian'; then \ CGO_ENABLED=1 \ XCADDY_SETCAP=1 \ XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ - CGO_CFLAGS="$(php-config --includes) $ADDITIONAL_BUILD_FLAGS" \ + CGO_CFLAGS="-DFRANKENPHP_VERSION=${FRANKENPHP_VERSION} $(php-config --includes) $ADDITIONAL_BUILD_FLAGS" \ CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ xcaddy build \ --output /usr/local/bin/frankenphp \ From 3c1196af7daa6d50bb152dd18f31ea4d3b2d9891 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 15 Oct 2025 18:02:13 -0500 Subject: [PATCH 130/304] Refactored Caddyfile structure for better SSL experience --- src/variations/frankenphp/Dockerfile | 5 +---- .../etc/entrypoint.d/10-generate-ssl.sh | 6 +++--- .../frankenphp/etc/frankenphp/Caddyfile | 13 +++++++++++-- .../auto-https/disable_certs.caddyfile | 1 + .../auto-https/disable_redirects.caddyfile | 1 + .../auto-https/ignore_loaded_certs.caddyfile | 1 + .../etc/frankenphp/auto-https/off.caddyfile | 1 + .../etc/frankenphp/auto-https/on.caddyfile | 1 + .../etc/frankenphp/ssl-mode/acme.caddyfile | 18 ------------------ .../etc/frankenphp/ssl-mode/full.caddyfile | 2 +- .../etc/frankenphp/ssl-mode/mixed.caddyfile | 2 +- .../etc/frankenphp/ssl-mode/off.caddyfile | 2 +- 12 files changed, 23 insertions(+), 30 deletions(-) create mode 100644 src/variations/frankenphp/etc/frankenphp/auto-https/disable_certs.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/auto-https/disable_redirects.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/auto-https/ignore_loaded_certs.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/auto-https/off.caddyfile create mode 100644 src/variations/frankenphp/etc/frankenphp/auto-https/on.caddyfile delete mode 100644 src/variations/frankenphp/etc/frankenphp/ssl-mode/acme.caddyfile diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 1655e51ba..b08b6e1c2 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -24,13 +24,12 @@ RUN set -eux; \ /etc/caddy \ /etc/frankenphp/ssl-mode \ /etc/frankenphp/log-level \ + /etc/frankenphp/auto-https \ /etc/frankenphp/caddyfile.d; \ # Create default index.php echo ' /var/www/html/public/index.php; \ # Create symbolic links ln -sf /var/www/html /app; \ - ln -sf /var/www/config/ /config; \ - ln -sf /var/www/data/ /data; \ # Ensure /var/www/ has the correct permissions chown -R www-data:www-data /var/www/; \ chmod -R 755 /var/www/; \ @@ -109,8 +108,6 @@ RUN if cat /etc/os-release | grep -q 'debian'; then \ --with github.com/dunglas/mercure/caddy \ --with github.com/dunglas/vulcain/caddy -# WORKDIR /go/src/app/caddy - #################### # FrankenPHP Final #################### diff --git a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh index 2ead8416e..b7f408b72 100644 --- a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh +++ b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh @@ -10,10 +10,10 @@ SSL_PRIVATE_KEY_FILE=${SSL_PRIVATE_KEY_FILE:-"/etc/ssl/private/self-signed-web.k SSL_MODE=${SSL_MODE:-"off"} if [ "$SSL_MODE" = "off" ]; then - echo "ℹ️ NOTICE ($script_name): SSL mode is off, so we'll not generate a self-signed SSL certificate and key." + echo "ℹ️ NOTICE ($script_name): SSL mode is off, so we won't generate a self-signed SSL key pair." return 0 -elif [ "$SSL_MODE" = "acme" ]; then - echo "ℹ️ NOTICE ($script_name): SSL mode is acme, so we'll not generate a self-signed SSL certificate and key." +elif [ "$CADDY_AUTO_HTTPS" != "off" ]; then + echo "ℹ️ NOTICE ($script_name): Caddy Auto HTTPS is enabled, so we won't generate a default, self-signed, SSL key pair." return 0 fi diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index e177e29ac..cdd989e2a 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -1,7 +1,8 @@ { # Global Caddy configuration admin {$CADDY_ADMIN:off} - auto_https {$CADDY_AUTO_HTTPS:off} + + import auto-https/{$CADDY_AUTO_HTTPS:off}.caddyfile http_port {$CADDY_HTTP_PORT:8080} https_port {$CADDY_HTTPS_PORT:8443} @@ -54,7 +55,15 @@ fd00::/8 \ {$CADDY_GLOBAL_OPTIONS} } -# Common app logic; reused across all SSL modes +(auto-https-off) { + tls {$SSL_CERTIFICATE_FILE} {$SSL_CERTIFICATE_KEY_FILE} +} + +(auto-https-on) { + # tls directive is not needed when auto_https is enabled +} + +# Common app logic; reused across all modes (app-common) { root * {$CADDY_APP_PUBLIC_PATH:/var/www/html/public} diff --git a/src/variations/frankenphp/etc/frankenphp/auto-https/disable_certs.caddyfile b/src/variations/frankenphp/etc/frankenphp/auto-https/disable_certs.caddyfile new file mode 100644 index 000000000..2728fd426 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/auto-https/disable_certs.caddyfile @@ -0,0 +1 @@ +auto_https disable_certs diff --git a/src/variations/frankenphp/etc/frankenphp/auto-https/disable_redirects.caddyfile b/src/variations/frankenphp/etc/frankenphp/auto-https/disable_redirects.caddyfile new file mode 100644 index 000000000..ff73f3595 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/auto-https/disable_redirects.caddyfile @@ -0,0 +1 @@ +auto_https disable_redirects diff --git a/src/variations/frankenphp/etc/frankenphp/auto-https/ignore_loaded_certs.caddyfile b/src/variations/frankenphp/etc/frankenphp/auto-https/ignore_loaded_certs.caddyfile new file mode 100644 index 000000000..91f207895 --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/auto-https/ignore_loaded_certs.caddyfile @@ -0,0 +1 @@ +auto_https ignore_loaded_certs diff --git a/src/variations/frankenphp/etc/frankenphp/auto-https/off.caddyfile b/src/variations/frankenphp/etc/frankenphp/auto-https/off.caddyfile new file mode 100644 index 000000000..e954f76cf --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/auto-https/off.caddyfile @@ -0,0 +1 @@ +auto_https off diff --git a/src/variations/frankenphp/etc/frankenphp/auto-https/on.caddyfile b/src/variations/frankenphp/etc/frankenphp/auto-https/on.caddyfile new file mode 100644 index 000000000..aaa7c4e2b --- /dev/null +++ b/src/variations/frankenphp/etc/frankenphp/auto-https/on.caddyfile @@ -0,0 +1 @@ +# Auto HTTPS is enabled by default when no auto_https directive is specified diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/acme.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/acme.caddyfile deleted file mode 100644 index 5c8576f42..000000000 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/acme.caddyfile +++ /dev/null @@ -1,18 +0,0 @@ -{$CADDY_HTTP_SERVER_ADDRESS:http://} { - # Redirect localhost healthcheck requests to HTTPS with the correct port - @healthcheck { - remote_ip 127.0.0.1/8 ::1 - path /healthcheck # Caddy healthcheck endpoint - path {$HEALTHCHECK_PATH:/healthcheck} # Custom healthcheck endpoint - } - log_skip @healthcheck - redir @healthcheck https://localhost:{$CADDY_HTTPS_PORT:8443}{uri} 308 - - # Redirect all other traffic to HTTPS (without explicit port) - redir https://{host}{uri} 308 -} - -{$CADDY_HTTPS_SERVER_ADDRESS:https://} { - import app-common - import security-https -} diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile index 3ef2652d5..c80eca611 100644 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile @@ -13,7 +13,7 @@ } {$CADDY_HTTPS_SERVER_ADDRESS:https://} { - tls {$SSL_CERTIFICATE_FILE} {$SSL_CERTIFICATE_KEY_FILE} + import auto-https-{$CADDY_AUTO_HTTPS:off} import app-common import security-https } diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile index 80e1756d8..5d4a8c395 100644 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile @@ -3,7 +3,7 @@ } {$CADDY_HTTPS_SERVER_ADDRESS:https://} { - tls {$SSL_CERTIFICATE_FILE} {$SSL_CERTIFICATE_KEY_FILE} + import auto-https-{$CADDY_AUTO_HTTPS:off} import app-common import security-https } diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile index 08ef293c6..8146ebe93 100644 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile @@ -1,3 +1,3 @@ {$CADDY_HTTP_SERVER_ADDRESS:http://} { import app-common -} \ No newline at end of file +} From 5a3699cb2d29e34b1c6bfa74e5524d7f2e9c00cf Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 16 Oct 2025 04:52:58 -0500 Subject: [PATCH 131/304] Updated installation documentation to enhance clarity on PHP variations, including detailed descriptions for CLI, FPM, FPM-Apache, FPM-NGINX, and FrankenPHP. Marked NGINX Unit as deprecated and provided migration guidance. Improved overall structure for better user understanding. --- .../docs/1.getting-started/2.installation.md | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/docs/content/docs/1.getting-started/2.installation.md b/docs/content/docs/1.getting-started/2.installation.md index d27c7ec82..8094871b4 100644 --- a/docs/content/docs/1.getting-started/2.installation.md +++ b/docs/content/docs/1.getting-started/2.installation.md @@ -64,39 +64,53 @@ Our most popular tags include: | unit | Debian Based[![serversideup/php:8.4-unit](https://img.shields.io/docker/image-size/serversideup/php/8.4-unit?label=serversideup%2Fphp%3A8.4-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.4-unit&page=1&ordering=-name)[![serversideup/php:8.3-unit](https://img.shields.io/docker/image-size/serversideup/php/8.3-unit?label=serversideup%2Fphp%3A8.3-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-unit&page=1&ordering=-name)[![serversideup/php:8.2-unit](https://img.shields.io/docker/image-size/serversideup/php/8.2-unit?label=serversideup%2Fphp%3A8.2-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-unit&page=1&ordering=-name)[![serversideup/php:8.1-unit](https://img.shields.io/docker/image-size/serversideup/php/8.1-unit?label=serversideup%2Fphp%3A8.1-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-unit&page=1&ordering=-name)[![serversideup/php:8.0-unit](https://img.shields.io/docker/image-size/serversideup/php/8.0-unit?label=serversideup%2Fphp%3A8.0-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-unit&page=1&ordering=-name)[![serversideup/php:7.4-unit](https://img.shields.io/docker/image-size/serversideup/php/7.4-unit?label=serversideup%2Fphp%3A7.4-unit)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-unit&page=1&ordering=-name) | ## Selecting the right variation -There are 4 main variations. +We offer a number of different variations to suit your needs. Each variation is optimized for specific use cases. ### CLI -If you need to run a quick command with something like `composer` or use PHP to run a CLI program only, this might be a great option for you. +The CLI variation is a minimal image designed for running PHP from the command line only. It does not include a web server. + +Use this variation for running commands like Composer, running scheduled tasks, or executing PHP scripts that don't require a web server. + +[Learn more about CLI →](/docs/image-variations/cli) ### FPM -The `fpm` variation is great for people who need to run a PHP "backend" if they already have a proxy serving static content. If you're using FPM only, that means you're probably at a larger scale. +The FPM (Fast Process Manager) variation runs PHP-FPM without a web server. This variation is ideal for microservices architectures where you need a dedicated PHP backend. + +Use this variation when you already have a separate proxy or load balancer handling static content and routing PHP requests to your FPM container. This is commonly used in larger-scale deployments. + +[Learn more about FPM →](/docs/image-variations/fpm) ### FPM-Apache -The `fpm-apache` variation is meant for users who want to run something like WordPress with Docker. Apache is configured to be a "reverse proxy", which will serve any static content with Apache and serve any PHP requests with PHP-FPM. Since there are two processes required to run this variation, we use [S6 Overlay](/docs/guide/using-s6-overlay) to ensure the container health is accurate. +The FPM-Apache variation combines PHP-FPM with Apache as a reverse proxy. Apache serves static content directly and forwards PHP requests to PHP-FPM for processing. -[Learn more about using Docker with WordPress →](/docs/guide/using-wordpress-with-docker) +Use this variation for applications like WordPress that benefit from Apache's .htaccess support and mod_rewrite functionality. This is a production-ready setup using S6 Overlay to manage both processes reliably. + +[Learn more about FPM-Apache →](/docs/image-variations/fpm-apache) | [Using Docker with WordPress →](/docs/guide/using-wordpress-with-docker) ### FPM-NGINX -Over the last 15+ years `fpm-nginx` has been "the way" to run PHP web applications, including Laravel. Since around 2010, it's been adopted and trusted by some of the best PHP system administrators out there. As of 2025, we're starting to see other options like FrankenPHP push the industry forward, but `fpm-nginx` is very stable and still widely used today. +The FPM-NGINX variation combines PHP-FPM with NGINX as a reverse proxy. This is the traditional setup that has been widely adopted for modern PHP applications, including many Laravel deployments. -The only caveat with running FPM + NGINX together with Docker is it requires two processes (which can lead to strange container behaviors). Thankfully, we took this all in mind with our design by using 's been trusted and deployed millions of times and there's a large adoption This allows you to serve static content quickly with NGINX but also pass PHP requests to PHP-FPM. Similar to PHP-Apache, there are two processes required to run this variation. We use S6 Overlay to ensure the container health is accurate. +Use this variation for production Laravel applications and other PHP frameworks that require a stable, battle-tested web server configuration. We use S6 Overlay to ensure both processes run reliably in a single container. -[Learn more about S6 Overlay →](/docs/guide/using-s6-overlay) +[Learn more about FPM-NGINX →](/docs/image-variations/fpm-nginx) ### Unit (deprecated) ::note In October 2025, NGINX stopped supporting NGINX Unit and archived the project. NGINX Unit will eventually be removed from our project. [See the official announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit) :: -The `unit` variation is for NGINX Unit, which is a NGINX's modern approach to delivering containerized web applications. -[Learn more about Unit →](https://unit.nginx.org/) +The Unit variation uses NGINX Unit as a modern application server that runs everything in a single process. + +While this variation is being deprecated, it can still be used for existing applications. Consider migrating to FrankenPHP for a modern single-process alternative. + +[Learn more about Unit →](/docs/image-variations/unit) ### FrankenPHP +The FrankenPHP variation is a modern application server built on top of the Caddy web server. It runs everything in a single process, eliminating the complexity of managing PHP-FPM and a separate web server. -The `frankenphp` variation is a modern application server for PHP built on top of the Caddy web server. Instead of relying on the complexities of two processes running NGINX + PHP-FPM, FrankenPHP replaces both to run everything under one process. FrankenPHP supports worker mode for Laravel and Symfony applications, automatic HTTPS, and modern protocols like HTTP/2 and HTTP/3. +Use this variation for Laravel or Symfony applications that can benefit from worker mode, automatic HTTPS, and modern protocols like HTTP/2 and HTTP/3. This is the recommended variation for new projects seeking cutting-edge performance. -[Learn more about FrankenPHP →](https://frankenphp.dev/) +[Learn more about FrankenPHP →](/docs/image-variations/frankenphp) ## Selecting the version of PHP Selecting the best version of PHP is highly dependent on your use case. In a perfect world, running the latest version of PHP will give you the latest and greatest, but it all depends on the libraries that your application uses. From 6c487ae4a9013922c418d53291a196c7e07cebe8 Mon Sep 17 00:00:00 2001 From: Dan Pastori Date: Fri, 17 Oct 2025 09:53:03 -0500 Subject: [PATCH 132/304] Initial commit with new theme, updated nuxt, updated content, nuxt UI --- .gitignore | 3 +- docs/.env.example | 11 +- docs/.gitignore | 27 +- docs/.npmrc | 1 - docs/.nvmrc | 1 - docs/README.md | 47 +- docs/app/app.config.ts | 123 + docs/app/app.vue | 59 + docs/app/assets/css/main.css | 26 + docs/app/components/AppFooter.vue | 23 + docs/app/components/AppHeader.vue | 66 + docs/app/components/AppLogo.vue | 40 + .../content => app/components}/Badges.vue | 2 +- docs/app/components/HeroVideo.vue | 15 + docs/app/components/OgImage/OgImageDocs.vue | 76 + docs/app/components/PageHeaderLinks.vue | 84 + docs/app/components/TemplateMenu.vue | 49 + docs/app/components/content/StarsBg.vue | 183 + docs/app/error.vue | 42 + docs/app/layouts/docs.vue | 22 + docs/app/pages/[...slug].vue | 115 + docs/app/pages/index.vue | 29 + docs/assets/css/animations.css | 69 - docs/assets/css/docsearch.css | 49 - docs/assets/css/hamburger.css | 82 - docs/assets/css/tailwind.css | 10 - docs/components/Docs/Anchor.vue | 23 - docs/components/Docs/Eyebrow.vue | 18 - docs/components/Docs/Footer.vue | 21 - docs/components/Docs/Header.vue | 63 - docs/components/Docs/Logo.vue | 11 - docs/components/Docs/ModeToggle.vue | 18 - docs/components/Docs/Navigation.vue | 56 - docs/components/Docs/NavigationGroup.vue | 58 - docs/components/Docs/PageLink.vue | 32 - docs/components/Docs/Search.vue | 17 - docs/components/Docs/SmallPrint.vue | 26 - docs/components/Docs/Tag.vue | 55 - docs/components/Docs/TopLevelNavItem.vue | 12 - docs/components/DocumentDrivenNotFound.vue | 27 - docs/components/Global/MobileMenu.vue | 133 - docs/components/Global/OgImage/DocsImage.vue | 35 - docs/components/Global/ServerSideUp.vue | 232 - docs/components/Icons/Anchor.vue | 9 - docs/components/Icons/Arrow.vue | 20 - docs/components/Icons/ChatBubbleIcon.vue | 16 - docs/components/Icons/Check.vue | 12 - docs/components/Icons/CheckIcon.vue | 18 - docs/components/Icons/ClipboardIcon.vue | 13 - docs/components/Icons/EnvelopeIcon.vue | 14 - docs/components/Icons/Moon.vue | 5 - docs/components/Icons/Resource.vue | 9 - docs/components/Icons/Search.vue | 8 - docs/components/Icons/Social/Discord.vue | 5 - docs/components/Icons/Social/GitHub.vue | 8 - docs/components/Icons/Social/Twitter.vue | 5 - docs/components/Icons/Sun.vue | 8 - docs/components/Icons/UserIcon.vue | 19 - docs/components/Icons/UsersIcon.vue | 25 - docs/components/content/About.vue | 26 - docs/components/content/AppButton.vue | 45 - docs/components/content/AppHeading2.vue | 42 - docs/components/content/AppHeading3.vue | 16 - docs/components/content/AppHeading4.vue | 16 - docs/components/content/AppLink.vue | 46 - .../components/content/Code/ClipboardIcon.vue | 12 - docs/components/content/Code/CopyButton.vue | 52 - docs/components/content/Code/PanelHeader.vue | 22 - docs/components/content/CodeGroup.vue | 105 - docs/components/content/CodePanel.vue | 31 - docs/components/content/Column.vue | 16 - docs/components/content/DiscordIcon.vue | 5 - docs/components/content/DocsIcon.vue | 5 - docs/components/content/Features.vue | 49 - docs/components/content/GitHubIcon.vue | 5 - docs/components/content/GridPattern.vue | 39 - docs/components/content/Guide.vue | 19 - docs/components/content/Guides.vue | 35 - docs/components/content/HeartIcon.vue | 5 - docs/components/content/HeroPattern.vue | 30 - docs/components/content/InfoIcon.vue | 12 - docs/components/content/LandingSignup.vue | 41 - docs/components/content/LeadP.vue | 7 - docs/components/content/Libraries.vue | 65 - .../components/content/MarketingDevToProd.vue | 26 - .../content/MarketingFollowAlong.vue | 76 - docs/components/content/MarketingGrid.vue | 55 - docs/components/content/MarketingHeader.vue | 148 - docs/components/content/MarketingHero.vue | 17 - .../components/content/MarketingOptimized.vue | 17 - .../content/MarketingTestimonials.vue | 39 - docs/components/content/NotProse.vue | 5 - docs/components/content/Note.vue | 8 - docs/components/content/Orchestrators.vue | 75 - docs/components/content/Properties.vue | 9 - docs/components/content/Property.vue | 20 - docs/components/content/Resources.vue | 67 - docs/components/content/Resources/Pattern.vue | 37 - .../components/content/Resources/Resource.vue | 38 - .../content/Resources/ResourceIcon.vue | 12 - docs/components/content/Row.vue | 5 - docs/components/content/Search.vue | 215 - docs/components/content/VideoEmbed.vue | 21 - docs/composables/states.ts | 1 - docs/content.config.ts | 25 + .../content/1.getting-started/.navigation.yml | 2 + docs/content/1.getting-started/1.index.md | 137 + .../2.these-images-vs-others.md} | 9 +- .../3.installation.md} | 126 +- docs/content/1.getting-started/3.usage.md | 113 + docs/content/2.essentials/.navigation.yml | 1 + .../content/2.essentials/1.markdown-syntax.md | 230 + docs/content/2.essentials/2.code-blocks.md | 456 + .../2.essentials/3.prose-components.md | 481 + docs/content/2.essentials/4.images-embeds.md | 57 + .../3.default-configurations.md | 115 - .../docs/1.getting-started/3.upgrade-guide.md | 59 - .../1.getting-started/4.choosing-a-host.md | 27 - .../docs/1.getting-started/5.changelog.md | 11 - .../content/docs/1.getting-started/6.about.md | 55 - .../docs/1.getting-started/7.contributing.md | 161 - docs/content/docs/1.index.md | 26 - .../docs/3.laravel/1.laravel-automations.md | 135 - .../3.laravel/2.laravel-task-scheduler.md | 119 - .../content/docs/3.laravel/3.laravel-queue.md | 55 - .../docs/3.laravel/4.laravel-horizon.md | 58 - .../docs/3.laravel/4.laravel-reverb.md | 78 - .../1.migrating-from-official-php-images.md | 28 - .../4.guide/100.migrating-from-v2-to-v3.md | 184 - .../2.understanding-file-permissions.md | 116 - .../docs/4.guide/2.using-s6-overlay.md | 96 - .../3.using-healthchecks-with-laravel.md | 121 - .../4.guide/4.using-wordpress-with-docker.md | 63 - .../1.changing-common-php-settings.md | 92 - .../2.installing-additional-php-extensions.md | 197 - .../3.adding-your-own-start-up-scripts.md | 168 - .../4.configuring-ssl.md | 100 - .../1.environment-variable-specification.md | 118 - .../docs/7.reference/2.command-reference.md | 115 - docs/content/index.md | 196 +- docs/eslint.config.mjs | 6 + docs/layouts/docs.vue | 77 - docs/layouts/marketing.vue | 67 - docs/middleware/directory.ts | 15 - docs/nuxt.config.ts | 118 +- docs/package.json | 47 +- docs/public/favicon.ico | Bin 0 -> 4286 bytes docs/public/images/docs/container-init.svg | 79 - docs/public/images/docs/docker-layers.png | Bin 76743 -> 0 bytes .../images/docs/permissions-privileged.png | Bin 84041 -> 0 bytes docs/public/images/docs/reverse-proxy.svg | 126 - .../images/docs/s6-overlay-container.svg | 72 - .../images/docs/supervisor-container.svg | 73 - docs/public/images/docs/watch-repo.png | Bin 22787 -> 0 bytes .../images/favicon/android-chrome-192x192.png | Bin 3458 -> 0 bytes .../images/favicon/android-chrome-512x512.png | Bin 11483 -> 0 bytes .../images/favicon/apple-touch-icon.png | Bin 2223 -> 0 bytes docs/public/images/favicon/browserconfig.xml | 9 - docs/public/images/favicon/favicon-16x16.png | Bin 883 -> 0 bytes docs/public/images/favicon/favicon-32x32.png | Bin 1107 -> 0 bytes docs/public/images/favicon/favicon.ico | Bin 15086 -> 0 bytes docs/public/images/favicon/mstile-150x150.png | Bin 2184 -> 0 bytes .../images/favicon/safari-pinned-tab.svg | 24 - docs/public/images/favicon/site.webmanifest | 19 - .../public/images/icons/cloudflare-square.svg | 6 - docs/public/images/icons/community-icon.svg | 3 - docs/public/images/icons/docs-icon.svg | 3 - docs/public/images/icons/heart-square.svg | 4 - docs/public/images/icons/heart.svg | 3 - docs/public/images/icons/heartbeat-square.svg | 4 - docs/public/images/icons/lightning-square.svg | 4 - docs/public/images/icons/logging-square.svg | 4 - docs/public/images/icons/nginx-square.svg | 5 - docs/public/images/icons/php-square.svg | 7 - docs/public/images/icons/rocket-square.svg | 4 - docs/public/images/icons/search-icon.svg | 3 - docs/public/images/icons/shield-square.svg | 4 - docs/public/images/icons/stars-square.svg | 4 - docs/public/images/logos/amplitude.svg | 21 - docs/public/images/logos/discord-slate.svg | 3 - docs/public/images/logos/discord-white.svg | 3 - docs/public/images/logos/docker.svg | 19 - docs/public/images/logos/github-slate.svg | 3 - docs/public/images/logos/github-white.svg | 3 - docs/public/images/logos/go.svg | 14 - docs/public/images/logos/kubernetes.svg | 11 - docs/public/images/logos/node.svg | 4 - docs/public/images/logos/nomad.svg | 10 - docs/public/images/logos/og-logo.png | Bin 5098 -> 0 bytes docs/public/images/logos/og-ssu-logo.png | Bin 3732 -> 0 bytes docs/public/images/logos/php.svg | 10 - docs/public/images/logos/python.svg | 13 - docs/public/images/logos/ruby.svg | 4 - .../images/logos/server-side-up-footer.svg | 6 - .../logos/server-side-up-logo-horizontal.svg | 6 - docs/public/images/logos/twitter-slate.svg | 3 - docs/public/images/logos/twitter-white.svg | 3 - docs/public/images/logos/x-logo.svg | 3 - .../images/{logos => }/php-docker-logo.svg | 0 docs/public/images/placeholder-hero-video.png | Bin 141372 -> 0 bytes .../images/placeholder-optimized-video.png | Bin 76505 -> 0 bytes docs/public/images/social-image.jpg | Bin 229668 -> 0 bytes .../images/testimonials/chris-fidao.png | Bin 39044 -> 0 bytes .../images/testimonials/johan-janssens.png | Bin 36857 -> 0 bytes docs/public/images/testimonials/ziga-zajc.png | Bin 26483 -> 0 bytes docs/renovate.json | 13 + docs/server/api/search.json.get.ts | 5 - docs/server/routes/raw/[...slug].md.get.ts | 27 + docs/server/routes/sitemap.xml.ts | 20 - docs/tailwind.config.js | 60 - docs/typography.js | 360 - docs/yarn.lock | 11690 ++++++++-------- 212 files changed, 9002 insertions(+), 12154 deletions(-) delete mode 100644 docs/.nvmrc create mode 100644 docs/app/app.config.ts create mode 100644 docs/app/app.vue create mode 100644 docs/app/assets/css/main.css create mode 100644 docs/app/components/AppFooter.vue create mode 100644 docs/app/components/AppHeader.vue create mode 100644 docs/app/components/AppLogo.vue rename docs/{components/content => app/components}/Badges.vue (92%) create mode 100644 docs/app/components/HeroVideo.vue create mode 100644 docs/app/components/OgImage/OgImageDocs.vue create mode 100644 docs/app/components/PageHeaderLinks.vue create mode 100644 docs/app/components/TemplateMenu.vue create mode 100644 docs/app/components/content/StarsBg.vue create mode 100644 docs/app/error.vue create mode 100644 docs/app/layouts/docs.vue create mode 100644 docs/app/pages/[...slug].vue create mode 100644 docs/app/pages/index.vue delete mode 100644 docs/assets/css/animations.css delete mode 100644 docs/assets/css/docsearch.css delete mode 100644 docs/assets/css/hamburger.css delete mode 100644 docs/assets/css/tailwind.css delete mode 100644 docs/components/Docs/Anchor.vue delete mode 100644 docs/components/Docs/Eyebrow.vue delete mode 100644 docs/components/Docs/Footer.vue delete mode 100644 docs/components/Docs/Header.vue delete mode 100644 docs/components/Docs/Logo.vue delete mode 100644 docs/components/Docs/ModeToggle.vue delete mode 100644 docs/components/Docs/Navigation.vue delete mode 100644 docs/components/Docs/NavigationGroup.vue delete mode 100644 docs/components/Docs/PageLink.vue delete mode 100644 docs/components/Docs/Search.vue delete mode 100644 docs/components/Docs/SmallPrint.vue delete mode 100644 docs/components/Docs/Tag.vue delete mode 100644 docs/components/Docs/TopLevelNavItem.vue delete mode 100644 docs/components/DocumentDrivenNotFound.vue delete mode 100644 docs/components/Global/MobileMenu.vue delete mode 100644 docs/components/Global/OgImage/DocsImage.vue delete mode 100644 docs/components/Global/ServerSideUp.vue delete mode 100644 docs/components/Icons/Anchor.vue delete mode 100644 docs/components/Icons/Arrow.vue delete mode 100644 docs/components/Icons/ChatBubbleIcon.vue delete mode 100644 docs/components/Icons/Check.vue delete mode 100644 docs/components/Icons/CheckIcon.vue delete mode 100644 docs/components/Icons/ClipboardIcon.vue delete mode 100644 docs/components/Icons/EnvelopeIcon.vue delete mode 100644 docs/components/Icons/Moon.vue delete mode 100644 docs/components/Icons/Resource.vue delete mode 100644 docs/components/Icons/Search.vue delete mode 100644 docs/components/Icons/Social/Discord.vue delete mode 100644 docs/components/Icons/Social/GitHub.vue delete mode 100644 docs/components/Icons/Social/Twitter.vue delete mode 100644 docs/components/Icons/Sun.vue delete mode 100644 docs/components/Icons/UserIcon.vue delete mode 100644 docs/components/Icons/UsersIcon.vue delete mode 100644 docs/components/content/About.vue delete mode 100644 docs/components/content/AppButton.vue delete mode 100644 docs/components/content/AppHeading2.vue delete mode 100644 docs/components/content/AppHeading3.vue delete mode 100644 docs/components/content/AppHeading4.vue delete mode 100644 docs/components/content/AppLink.vue delete mode 100644 docs/components/content/Code/ClipboardIcon.vue delete mode 100644 docs/components/content/Code/CopyButton.vue delete mode 100644 docs/components/content/Code/PanelHeader.vue delete mode 100644 docs/components/content/CodeGroup.vue delete mode 100644 docs/components/content/CodePanel.vue delete mode 100644 docs/components/content/Column.vue delete mode 100644 docs/components/content/DiscordIcon.vue delete mode 100644 docs/components/content/DocsIcon.vue delete mode 100644 docs/components/content/Features.vue delete mode 100644 docs/components/content/GitHubIcon.vue delete mode 100644 docs/components/content/GridPattern.vue delete mode 100644 docs/components/content/Guide.vue delete mode 100644 docs/components/content/Guides.vue delete mode 100644 docs/components/content/HeartIcon.vue delete mode 100644 docs/components/content/HeroPattern.vue delete mode 100644 docs/components/content/InfoIcon.vue delete mode 100644 docs/components/content/LandingSignup.vue delete mode 100644 docs/components/content/LeadP.vue delete mode 100644 docs/components/content/Libraries.vue delete mode 100644 docs/components/content/MarketingDevToProd.vue delete mode 100644 docs/components/content/MarketingFollowAlong.vue delete mode 100644 docs/components/content/MarketingGrid.vue delete mode 100644 docs/components/content/MarketingHeader.vue delete mode 100644 docs/components/content/MarketingHero.vue delete mode 100644 docs/components/content/MarketingOptimized.vue delete mode 100644 docs/components/content/MarketingTestimonials.vue delete mode 100644 docs/components/content/NotProse.vue delete mode 100644 docs/components/content/Note.vue delete mode 100644 docs/components/content/Orchestrators.vue delete mode 100644 docs/components/content/Properties.vue delete mode 100644 docs/components/content/Property.vue delete mode 100644 docs/components/content/Resources.vue delete mode 100644 docs/components/content/Resources/Pattern.vue delete mode 100644 docs/components/content/Resources/Resource.vue delete mode 100644 docs/components/content/Resources/ResourceIcon.vue delete mode 100644 docs/components/content/Row.vue delete mode 100644 docs/components/content/Search.vue delete mode 100644 docs/components/content/VideoEmbed.vue delete mode 100644 docs/composables/states.ts create mode 100644 docs/content.config.ts create mode 100644 docs/content/1.getting-started/.navigation.yml create mode 100644 docs/content/1.getting-started/1.index.md rename docs/content/{docs/1.getting-started/1.these-images-vs-others.md => 1.getting-started/2.these-images-vs-others.md} (95%) rename docs/content/{docs/1.getting-started/2.installation.md => 1.getting-started/3.installation.md} (79%) create mode 100644 docs/content/1.getting-started/3.usage.md create mode 100644 docs/content/2.essentials/.navigation.yml create mode 100644 docs/content/2.essentials/1.markdown-syntax.md create mode 100644 docs/content/2.essentials/2.code-blocks.md create mode 100644 docs/content/2.essentials/3.prose-components.md create mode 100644 docs/content/2.essentials/4.images-embeds.md delete mode 100644 docs/content/docs/1.getting-started/3.default-configurations.md delete mode 100644 docs/content/docs/1.getting-started/3.upgrade-guide.md delete mode 100644 docs/content/docs/1.getting-started/4.choosing-a-host.md delete mode 100644 docs/content/docs/1.getting-started/5.changelog.md delete mode 100644 docs/content/docs/1.getting-started/6.about.md delete mode 100644 docs/content/docs/1.getting-started/7.contributing.md delete mode 100644 docs/content/docs/1.index.md delete mode 100644 docs/content/docs/3.laravel/1.laravel-automations.md delete mode 100644 docs/content/docs/3.laravel/2.laravel-task-scheduler.md delete mode 100644 docs/content/docs/3.laravel/3.laravel-queue.md delete mode 100644 docs/content/docs/3.laravel/4.laravel-horizon.md delete mode 100644 docs/content/docs/3.laravel/4.laravel-reverb.md delete mode 100644 docs/content/docs/4.guide/1.migrating-from-official-php-images.md delete mode 100644 docs/content/docs/4.guide/100.migrating-from-v2-to-v3.md delete mode 100644 docs/content/docs/4.guide/2.understanding-file-permissions.md delete mode 100644 docs/content/docs/4.guide/2.using-s6-overlay.md delete mode 100644 docs/content/docs/4.guide/3.using-healthchecks-with-laravel.md delete mode 100644 docs/content/docs/4.guide/4.using-wordpress-with-docker.md delete mode 100644 docs/content/docs/5.customizing-the-image/1.changing-common-php-settings.md delete mode 100644 docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md delete mode 100644 docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md delete mode 100644 docs/content/docs/5.customizing-the-image/4.configuring-ssl.md delete mode 100644 docs/content/docs/7.reference/1.environment-variable-specification.md delete mode 100644 docs/content/docs/7.reference/2.command-reference.md create mode 100644 docs/eslint.config.mjs delete mode 100644 docs/layouts/docs.vue delete mode 100644 docs/layouts/marketing.vue delete mode 100644 docs/middleware/directory.ts create mode 100644 docs/public/favicon.ico delete mode 100644 docs/public/images/docs/container-init.svg delete mode 100644 docs/public/images/docs/docker-layers.png delete mode 100644 docs/public/images/docs/permissions-privileged.png delete mode 100644 docs/public/images/docs/reverse-proxy.svg delete mode 100644 docs/public/images/docs/s6-overlay-container.svg delete mode 100644 docs/public/images/docs/supervisor-container.svg delete mode 100644 docs/public/images/docs/watch-repo.png delete mode 100644 docs/public/images/favicon/android-chrome-192x192.png delete mode 100644 docs/public/images/favicon/android-chrome-512x512.png delete mode 100644 docs/public/images/favicon/apple-touch-icon.png delete mode 100644 docs/public/images/favicon/browserconfig.xml delete mode 100644 docs/public/images/favicon/favicon-16x16.png delete mode 100644 docs/public/images/favicon/favicon-32x32.png delete mode 100644 docs/public/images/favicon/favicon.ico delete mode 100644 docs/public/images/favicon/mstile-150x150.png delete mode 100644 docs/public/images/favicon/safari-pinned-tab.svg delete mode 100644 docs/public/images/favicon/site.webmanifest delete mode 100644 docs/public/images/icons/cloudflare-square.svg delete mode 100644 docs/public/images/icons/community-icon.svg delete mode 100644 docs/public/images/icons/docs-icon.svg delete mode 100644 docs/public/images/icons/heart-square.svg delete mode 100644 docs/public/images/icons/heart.svg delete mode 100644 docs/public/images/icons/heartbeat-square.svg delete mode 100644 docs/public/images/icons/lightning-square.svg delete mode 100644 docs/public/images/icons/logging-square.svg delete mode 100644 docs/public/images/icons/nginx-square.svg delete mode 100644 docs/public/images/icons/php-square.svg delete mode 100644 docs/public/images/icons/rocket-square.svg delete mode 100644 docs/public/images/icons/search-icon.svg delete mode 100644 docs/public/images/icons/shield-square.svg delete mode 100644 docs/public/images/icons/stars-square.svg delete mode 100644 docs/public/images/logos/amplitude.svg delete mode 100644 docs/public/images/logos/discord-slate.svg delete mode 100644 docs/public/images/logos/discord-white.svg delete mode 100644 docs/public/images/logos/docker.svg delete mode 100644 docs/public/images/logos/github-slate.svg delete mode 100644 docs/public/images/logos/github-white.svg delete mode 100644 docs/public/images/logos/go.svg delete mode 100644 docs/public/images/logos/kubernetes.svg delete mode 100644 docs/public/images/logos/node.svg delete mode 100644 docs/public/images/logos/nomad.svg delete mode 100644 docs/public/images/logos/og-logo.png delete mode 100644 docs/public/images/logos/og-ssu-logo.png delete mode 100644 docs/public/images/logos/php.svg delete mode 100644 docs/public/images/logos/python.svg delete mode 100644 docs/public/images/logos/ruby.svg delete mode 100644 docs/public/images/logos/server-side-up-footer.svg delete mode 100644 docs/public/images/logos/server-side-up-logo-horizontal.svg delete mode 100644 docs/public/images/logos/twitter-slate.svg delete mode 100644 docs/public/images/logos/twitter-white.svg delete mode 100644 docs/public/images/logos/x-logo.svg rename docs/public/images/{logos => }/php-docker-logo.svg (100%) delete mode 100644 docs/public/images/placeholder-hero-video.png delete mode 100644 docs/public/images/placeholder-optimized-video.png delete mode 100644 docs/public/images/social-image.jpg delete mode 100644 docs/public/images/testimonials/chris-fidao.png delete mode 100644 docs/public/images/testimonials/johan-janssens.png delete mode 100644 docs/public/images/testimonials/ziga-zajc.png create mode 100644 docs/renovate.json delete mode 100644 docs/server/api/search.json.get.ts create mode 100644 docs/server/routes/raw/[...slug].md.get.ts delete mode 100644 docs/server/routes/sitemap.xml.ts delete mode 100644 docs/tailwind.config.js delete mode 100644 docs/typography.js diff --git a/.gitignore b/.gitignore index cbc1be6a1..c3f34b862 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ package.json yarn.lock node_modules php-versions.yml -*.tmp \ No newline at end of file +*.tmp +/docs/_OLD_ \ No newline at end of file diff --git a/docs/.env.example b/docs/.env.example index c47b2b615..e90e45a54 100644 --- a/docs/.env.example +++ b/docs/.env.example @@ -1,3 +1,10 @@ +# Used for Nuxt SEO +NUXT_SITE_URL=https://example.com +NUXT_SITE_NAME=My Awesome Website +NUXT_SITE_ENV="development" + +# Used for +PLAUSIBLE_ENABLED=false +BASE_PATH=http://localhost:3000/open-source/docker-php NUXT_APP_BASE_URL=/open-source/docker-php -TOP_LEVEL_DOMAIN=http://localhost:3000 -BASE_PATH=http://localhost:3000/open-source/docker-php \ No newline at end of file +TOP_LEVEL_DOMAIN=http://localhost:3000 \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore index 438cb0860..6bbb425a0 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,8 +1,27 @@ -node_modules -*.log* +# Nuxt dev/build outputs +.output +.data .nuxt .nitro .cache -.output -.env dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example + +# VSC +.history diff --git a/docs/.npmrc b/docs/.npmrc index cf0404245..bf2e7648b 100644 --- a/docs/.npmrc +++ b/docs/.npmrc @@ -1,2 +1 @@ shamefully-hoist=true -strict-peer-dependencies=false diff --git a/docs/.nvmrc b/docs/.nvmrc deleted file mode 100644 index 2edeafb09..000000000 --- a/docs/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -20 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 38d0b403c..0787bb89c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,35 +1,44 @@ -# Documentation & Static Site, generated with Nuxt Content -This is a documentation site built on top of Nuxt Content (v3). +# Nuxt Docs Template -# Docs location -All docs are located in the [./content](./content/docs) folder if you're just looking for the docs in plain text. +[![Nuxt UI](https://img.shields.io/badge/Made%20with-Nuxt%20UI-00DC82?logo=nuxt&labelColor=020420)](https://ui.nuxt.com) -## Setup +Use this template to build your own documentation with [Nuxt UI](https://ui.nuxt.com) quickly. -Ensure you're in the right directory. +- [Live demo](https://docs-template.nuxt.dev/) +- [Documentation](https://ui.nuxt.com/docs/getting-started/installation) -```bash -cd docs/ -``` +
+ + + + Nuxt Docs Template + + -Copy over the environment variable example file. +## Quick Start -```bash -cp .env.example .env +```bash [Terminal] +npm create nuxt@latest -- -t github:nuxt-ui-templates/docs ``` +## Deploy your own + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-name=docs&repository-url=https%3A%2F%2Fgithub.com%2Fnuxt-ui-templates%2Fdocs&demo-image=https%3A%2F%2Fui.nuxt.com%2Fassets%2Ftemplates%2Fnuxt%2Fdocs-dark.png&demo-url=https%3A%2F%2Fdocs-template.nuxt.dev%2F&demo-title=Nuxt%20Docs%20Template&demo-description=A%20documentation%20template%20powered%20by%20Nuxt%20Content.) + +## Setup + Make sure to install the dependencies: ```bash -yarn install +pnpm install ``` ## Development Server -Start the development server on http://localhost:3000 +Start the development server on `http://localhost:3000`: ```bash -yarn dev +pnpm dev ``` ## Production @@ -37,13 +46,17 @@ yarn dev Build the application for production: ```bash -yarn build +pnpm build ``` Locally preview production build: ```bash -yarn preview +pnpm preview ``` Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. + +## Renovate integration + +Install [Renovate GitHub app](https://github.com/apps/renovate/installations/select_target) on your repository and you are good to go. diff --git a/docs/app/app.config.ts b/docs/app/app.config.ts new file mode 100644 index 000000000..77a67a33d --- /dev/null +++ b/docs/app/app.config.ts @@ -0,0 +1,123 @@ +export default defineAppConfig({ + ui: { + colors: { + primary: 'blue', + neutral: 'neutral' + }, + banner: { + slots: { + icon: 'text-white size-5 shrink-0 pointer-events-none', + title: 'text-white font-bold text-sm truncate', + } + }, + mode: 'dark', + header: { + slots: { + right: 'flex items-center justify-end lg:flex-1 gap-3', + }, + }, + footer: { + slots: { + root: 'border-t border-default', + left: 'text-sm text-muted' + } + } + }, + seo: { + siteName: 'Nuxt Docs Template' + }, + header: { + title: 'PHP Docker Images (serversideup/docker-php)', + to: '/', + logo: { + alt: 'PHP Docker Images (serversideup/docker-php)', + light: '/images/php-docker-logo.svg', + dark: '/images/php-docker-logo.svg' + }, + search: true, + links: [{ + 'icon': 'i-lucide-book-open', + 'to': '/getting-started', + 'aria-label': 'Documentation', + 'label': 'Docs', + 'variant': 'ghost', + 'size': 'xl', + 'class': 'font-bold' + },{ + 'icon': 'i-simple-icons-discord', + 'to': 'https://serversideup.net/discord', + 'target': '_blank', + 'aria-label': 'Server Side Up on Discord', + 'label': 'Discord', + 'variant': 'ghost', + 'size': 'xl', + 'class': 'font-bold' + },{ + 'icon': 'i-simple-icons-github', + 'to': 'https://github.com/serversideup/docker-php', + 'target': '_blank', + 'aria-label': 'GitHub', + 'label': 'GitHub', + 'variant': 'ghost', + 'size': 'xl', + 'class': 'font-bold' + },{ + 'trailingIcon': 'i-lucide-heart', + 'label': 'Sponsor', + 'to': 'https://github.com/sponsors/serversideup', + 'target': '_blank', + 'aria-label': 'Sponsor', + 'size': 'xl', + 'variant': 'outline', + 'class': 'font-bold', + + },{ + 'trailingIcon': 'i-lucide-arrow-right', + 'label': 'Get Started', + 'to': '/docs', + 'aria-label': 'Get Started', + 'size': 'xl', + 'variant': 'solid', + 'class': 'font-bold', + 'color': 'primary', + }] + }, + footer: { + credits: `Built with Nuxt UI • © ${new Date().getFullYear()}`, + colorMode: false, + links: [{ + 'icon': 'i-simple-icons-discord', + 'to': 'https://go.nuxt.com/discord', + 'target': '_blank', + 'aria-label': 'Nuxt on Discord' + }, { + 'icon': 'i-simple-icons-x', + 'to': 'https://go.nuxt.com/x', + 'target': '_blank', + 'aria-label': 'Nuxt on X' + }, { + 'icon': 'i-simple-icons-github', + 'to': 'https://github.com/nuxt/ui', + 'target': '_blank', + 'aria-label': 'Nuxt UI on GitHub' + }] + }, + toc: { + title: 'Table of Contents', + bottom: { + title: 'Community', + edit: 'https://github.com/nuxt-ui-templates/docs/edit/main/content', + links: [{ + icon: 'i-lucide-star', + label: 'Star on GitHub', + to: 'https://github.com/nuxt/ui', + target: '_blank' + }, { + icon: 'i-lucide-book-open', + label: 'Nuxt UI docs', + to: 'https://ui.nuxt.com/docs/getting-started/installation/nuxt', + target: '_blank' + }] + } + } +}) diff --git a/docs/app/app.vue b/docs/app/app.vue new file mode 100644 index 000000000..7298d1a67 --- /dev/null +++ b/docs/app/app.vue @@ -0,0 +1,59 @@ + + + diff --git a/docs/app/assets/css/main.css b/docs/app/assets/css/main.css new file mode 100644 index 000000000..76b6d4ec6 --- /dev/null +++ b/docs/app/assets/css/main.css @@ -0,0 +1,26 @@ +@import "tailwindcss"; +@import "@nuxt/ui"; + +@source "../../../content/**/*"; + +@theme static { + --ui-header-height: --spacing(24); + --container-8xl: 90rem; + --font-sans: 'Public Sans', sans-serif; + + --color-green-50: #EFFDF5; + --color-green-100: #D9FBE8; + --color-green-200: #B3F5D1; + --color-green-300: #75EDAE; + --color-green-400: #00DC82; + --color-green-500: #00C16A; + --color-green-600: #00A155; + --color-green-700: #007F45; + --color-green-800: #016538; + --color-green-900: #0A5331; + --color-green-950: #052E16; +} + +:root { + --ui-container: var(--container-8xl); +} diff --git a/docs/app/components/AppFooter.vue b/docs/app/components/AppFooter.vue new file mode 100644 index 000000000..b8859719a --- /dev/null +++ b/docs/app/components/AppFooter.vue @@ -0,0 +1,23 @@ + + + diff --git a/docs/app/components/AppHeader.vue b/docs/app/components/AppHeader.vue new file mode 100644 index 000000000..e99288a52 --- /dev/null +++ b/docs/app/components/AppHeader.vue @@ -0,0 +1,66 @@ + + + \ No newline at end of file diff --git a/docs/app/components/AppLogo.vue b/docs/app/components/AppLogo.vue new file mode 100644 index 000000000..521061f3e --- /dev/null +++ b/docs/app/components/AppLogo.vue @@ -0,0 +1,40 @@ + diff --git a/docs/components/content/Badges.vue b/docs/app/components/Badges.vue similarity index 92% rename from docs/components/content/Badges.vue rename to docs/app/components/Badges.vue index c8fb01105..498535522 100644 --- a/docs/components/content/Badges.vue +++ b/docs/app/components/Badges.vue @@ -1,5 +1,5 @@ \ No newline at end of file From ae84eb4ac24327c694f15b548c06a3355a10592d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 13:43:06 -0500 Subject: [PATCH 183/304] Add various SVG and PNG logos to documentation for improved visual consistency and branding --- docs/public/images/docs/watch-repo.png | Bin 0 -> 22787 bytes docs/public/images/logos/amplitude.svg | 21 ++++++++++++++++++ docs/public/images/logos/discord-slate.svg | 3 +++ docs/public/images/logos/discord-white.svg | 3 +++ docs/public/images/logos/docker.svg | 19 ++++++++++++++++ docs/public/images/logos/github-slate.svg | 3 +++ docs/public/images/logos/github-white.svg | 3 +++ docs/public/images/logos/go.svg | 14 ++++++++++++ docs/public/images/logos/kubernetes.svg | 11 +++++++++ docs/public/images/logos/node.svg | 4 ++++ docs/public/images/logos/nomad.svg | 10 +++++++++ docs/public/images/logos/og-logo.png | Bin 0 -> 5098 bytes docs/public/images/logos/og-ssu-logo.png | Bin 0 -> 3732 bytes docs/public/images/logos/php-docker-logo.svg | 18 +++++++++++++++ docs/public/images/logos/php.svg | 10 +++++++++ docs/public/images/logos/python.svg | 13 +++++++++++ docs/public/images/logos/ruby.svg | 4 ++++ .../images/logos/server-side-up-footer.svg | 6 +++++ .../logos/server-side-up-logo-horizontal.svg | 6 +++++ docs/public/images/logos/twitter-slate.svg | 3 +++ docs/public/images/logos/twitter-white.svg | 3 +++ docs/public/images/logos/x-logo.svg | 3 +++ 22 files changed, 157 insertions(+) create mode 100644 docs/public/images/docs/watch-repo.png create mode 100644 docs/public/images/logos/amplitude.svg create mode 100644 docs/public/images/logos/discord-slate.svg create mode 100644 docs/public/images/logos/discord-white.svg create mode 100644 docs/public/images/logos/docker.svg create mode 100644 docs/public/images/logos/github-slate.svg create mode 100644 docs/public/images/logos/github-white.svg create mode 100644 docs/public/images/logos/go.svg create mode 100644 docs/public/images/logos/kubernetes.svg create mode 100644 docs/public/images/logos/node.svg create mode 100644 docs/public/images/logos/nomad.svg create mode 100644 docs/public/images/logos/og-logo.png create mode 100644 docs/public/images/logos/og-ssu-logo.png create mode 100644 docs/public/images/logos/php-docker-logo.svg create mode 100644 docs/public/images/logos/php.svg create mode 100644 docs/public/images/logos/python.svg create mode 100644 docs/public/images/logos/ruby.svg create mode 100644 docs/public/images/logos/server-side-up-footer.svg create mode 100644 docs/public/images/logos/server-side-up-logo-horizontal.svg create mode 100644 docs/public/images/logos/twitter-slate.svg create mode 100644 docs/public/images/logos/twitter-white.svg create mode 100644 docs/public/images/logos/x-logo.svg diff --git a/docs/public/images/docs/watch-repo.png b/docs/public/images/docs/watch-repo.png new file mode 100644 index 0000000000000000000000000000000000000000..e10ecef1bd67546fc3b898fd9a2083365291d8af GIT binary patch literal 22787 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 gQ!?;B16oPT; + + + AmplitudeJS + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/public/images/logos/discord-slate.svg b/docs/public/images/logos/discord-slate.svg new file mode 100644 index 000000000..1867bbf5b --- /dev/null +++ b/docs/public/images/logos/discord-slate.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/logos/discord-white.svg b/docs/public/images/logos/discord-white.svg new file mode 100644 index 000000000..fc83b6d55 --- /dev/null +++ b/docs/public/images/logos/discord-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/logos/docker.svg b/docs/public/images/logos/docker.svg new file mode 100644 index 000000000..fe2d9c43d --- /dev/null +++ b/docs/public/images/logos/docker.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/logos/github-slate.svg b/docs/public/images/logos/github-slate.svg new file mode 100644 index 000000000..804c8a920 --- /dev/null +++ b/docs/public/images/logos/github-slate.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/logos/github-white.svg b/docs/public/images/logos/github-white.svg new file mode 100644 index 000000000..aa42ea81e --- /dev/null +++ b/docs/public/images/logos/github-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/logos/go.svg b/docs/public/images/logos/go.svg new file mode 100644 index 000000000..7f7b19de5 --- /dev/null +++ b/docs/public/images/logos/go.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/docs/public/images/logos/kubernetes.svg b/docs/public/images/logos/kubernetes.svg new file mode 100644 index 000000000..70f477d00 --- /dev/null +++ b/docs/public/images/logos/kubernetes.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/public/images/logos/node.svg b/docs/public/images/logos/node.svg new file mode 100644 index 000000000..1d09de22b --- /dev/null +++ b/docs/public/images/logos/node.svg @@ -0,0 +1,4 @@ + + + diff --git a/docs/public/images/logos/nomad.svg b/docs/public/images/logos/nomad.svg new file mode 100644 index 000000000..b4df566c9 --- /dev/null +++ b/docs/public/images/logos/nomad.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/public/images/logos/og-logo.png b/docs/public/images/logos/og-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..07560c116066f96fa4fe58a2c84ec25d44e78829 GIT binary patch literal 5098 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 + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/logos/php.svg b/docs/public/images/logos/php.svg new file mode 100644 index 000000000..0a9ac462a --- /dev/null +++ b/docs/public/images/logos/php.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/docs/public/images/logos/python.svg b/docs/public/images/logos/python.svg new file mode 100644 index 000000000..9bceb587a --- /dev/null +++ b/docs/public/images/logos/python.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/docs/public/images/logos/ruby.svg b/docs/public/images/logos/ruby.svg new file mode 100644 index 000000000..b22a5bf10 --- /dev/null +++ b/docs/public/images/logos/ruby.svg @@ -0,0 +1,4 @@ + + + diff --git a/docs/public/images/logos/server-side-up-footer.svg b/docs/public/images/logos/server-side-up-footer.svg new file mode 100644 index 000000000..13bc7aa54 --- /dev/null +++ b/docs/public/images/logos/server-side-up-footer.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/public/images/logos/server-side-up-logo-horizontal.svg b/docs/public/images/logos/server-side-up-logo-horizontal.svg new file mode 100644 index 000000000..93198355b --- /dev/null +++ b/docs/public/images/logos/server-side-up-logo-horizontal.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/public/images/logos/twitter-slate.svg b/docs/public/images/logos/twitter-slate.svg new file mode 100644 index 000000000..05a4c0db0 --- /dev/null +++ b/docs/public/images/logos/twitter-slate.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/logos/twitter-white.svg b/docs/public/images/logos/twitter-white.svg new file mode 100644 index 000000000..0c89586ae --- /dev/null +++ b/docs/public/images/logos/twitter-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/logos/x-logo.svg b/docs/public/images/logos/x-logo.svg new file mode 100644 index 000000000..437e2bfdd --- /dev/null +++ b/docs/public/images/logos/x-logo.svg @@ -0,0 +1,3 @@ + + + From a9c97a2db1dfebe7c83dae805460a43a1bc3c700 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 13:44:28 -0500 Subject: [PATCH 184/304] Enhance documentation for PHP Docker images by adding notes on unprivileged user defaults and environment variable configuration. Include warnings about using supervisor and promote S6 Overlay for process management, improving clarity and user guidance. --- .../2.these-images-vs-others.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/1.getting-started/2.these-images-vs-others.md b/docs/content/docs/1.getting-started/2.these-images-vs-others.md index 6ae6af9c8..6793a4ba0 100644 --- a/docs/content/docs/1.getting-started/2.these-images-vs-others.md +++ b/docs/content/docs/1.getting-started/2.these-images-vs-others.md @@ -23,14 +23,23 @@ description: 'Learn the important differences between serversideup/php and other | Native health checks | ❌ | ✅ | ## Unprivileged by Default +::note +Running containers as `root` in the open internet is very dangerous. This is why we run our images as `www-data` by default. +:: + We believe in the principle of least privilege. Our images run as an unprivileged user by default. This means that if your application is compromised, the attacker will have a harder time escalating their privileges to the root user. Running unprivileged images also improves compatibility of running your containers in a Kubernetes environment, where running as root is not allowed. ## Variable-first Configuration +::tip +Setting environment variables saves a ton of headache when customizing your container. View our [environment variable specification](/docs/reference/environment-variable-specification) for all the available variables. +:: + Our design philosophy is built all around simplicity. The process of customizing the behavior of PHP is as simple as setting an environment variable. We took every common configuration option and set it up so you can change these values in a simple method, defaulting every single option to production-ready values. - +::u-button{to="/docs/reference/environment-variable-specification" label="Learn more about environment variables" aria-label="Learn more about environment variables" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} +:: ## Optimized for Laravel & WordPress We did the hard work of customizing the official PHP image and giving you everything to get started with running PHP's most popular frameworks in seconds. We also hardened the images to help protect your PHP application from common attacks. @@ -39,6 +48,10 @@ We did the hard work of customizing the official PHP image and giving you everyt We believe images should be ready for production and able to live in the open and wild Internet. We took our years of experience and tweaked these images to be performant and secure as possible. Having these values published online allows others to review, contribute, and improve the images -- giving you the best security and performance possible. ## What is "S6 Overlay?" -[S6 Overlay](https://github.com/just-containers/s6-overlay) is a process supervisor designed for containerization from the ground up. It's a modern alternative to [Supervisor (aka Supervisord)](https://supervisord.org/). We only use this in our `serversideup/php:*-fpm-apache` and `serversideup/php:*-fpm-nginx` images, as they require two processes to run a service. +::warning +Using **supervisor** to monitor multiple processes in a single container may lead to unintended consequences. This is why we use [S6 Overlay](/docs/guides/using-s6-overlay) to monitor multiple processes in a single container. +:: + +[S6 Overlay](https://github.com/just-containers/s6-overlay){target="_blank"} is a process supervisor designed for containerization from the ground up. It's a modern alternative to [Supervisor (aka Supervisord)](https://supervisord.org/){target="_blank"}. We only use this in our `serversideup/php:*-fpm-apache` and `serversideup/php:*-fpm-nginx` images, as they require two processes to run a service. - \ No newline at end of file +::u-button{to="/docs/guides/using-s6-overlay" label="Learn more about S6 Overlay" aria-label="Learn more about S6 Overlay" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From 029b69f8c5b59d66d29b9998ec034914cb5d88ef Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 13:44:46 -0500 Subject: [PATCH 185/304] Update installation documentation to replace references from 'Container Concepts' to 'Container Basics' for improved accuracy and user guidance. --- docs/content/docs/1.getting-started/3.installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/1.getting-started/3.installation.md b/docs/content/docs/1.getting-started/3.installation.md index 2d117f270..d91228c3c 100644 --- a/docs/content/docs/1.getting-started/3.installation.md +++ b/docs/content/docs/1.getting-started/3.installation.md @@ -12,7 +12,7 @@ title: Installation ## Understanding containers and Docker ::tip -New to containers? Check out our [Container Concepts guide](/docs/deployment-and-production/container-concepts) for a beginner-friendly introduction to Docker and containerization. +New to containers? Check out our [Container Basics guide](/docs/deployment-and-production/container-basics) for a beginner-friendly introduction to Docker and containerization. :: If you're new to Docker or containers, you might be wondering why you should containerize your application in the first place. The short answer: **containers let you run your application anywhere** — from your laptop to any cloud provider — with zero changes. @@ -34,7 +34,7 @@ There are some important terms to understand when working with containers: If you'd like to learn more about containers, you can learn more following the guide below. -::u-button{to="/docs/deployment-and-production/container-concepts" label="Learn More About Containers" aria-label="Learn More About Containers" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} +::u-button{to="/docs/deployment-and-production/container-basics" label="Learn More About Containers" aria-label="Learn More About Containers" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} :: ## Quick Start From b9990df1fc4d6981daaf9f0e36eeedaa561c826a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 13:49:11 -0500 Subject: [PATCH 186/304] Refactor button syntax in getting started documentation for consistency and clarity, ensuring a cleaner presentation of links to advantages and installation instructions. --- docs/content/docs/1.getting-started/1.index.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/1.getting-started/1.index.md b/docs/content/docs/1.getting-started/1.index.md index c2d3bb6b8..4954cf9d7 100644 --- a/docs/content/docs/1.getting-started/1.index.md +++ b/docs/content/docs/1.getting-started/1.index.md @@ -128,12 +128,10 @@ src: https://docker-php-public-assets.serversideup.net/docker-demo.mp4 This is just an overview. See the full list of benefits and features below. -::u-button{to="/docs/getting-started/these-images-vs-others" label="Read more about the advantages" aria-label="Read more about the advantages" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} -:: +:u-button{to="/docs/getting-started/these-images-vs-others" label="Read more about the advantages" aria-label="Read more about the advantages" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} ## Why should I use these PHP Docker Images? These images are production-ready and optimized for performance and security. We work hard to give you a "batteries included" experience so you can ship PHP with Docker as fast as possible. Click below to see a simple working example. -::u-button{to="/docs/getting-started/installation" label="Installation" aria-label="Installation" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} -:: \ No newline at end of file +:u-button{to="/docs/getting-started/installation" label="Installation" aria-label="Installation" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From c12161bb4b040c2a7766a6191a580965b8e48b80 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 13:49:27 -0500 Subject: [PATCH 187/304] Update button syntax in getting started documentation for consistency, ensuring a cleaner presentation of links to environment variable specifications and S6 Overlay usage. --- .../docs/1.getting-started/2.these-images-vs-others.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/1.getting-started/2.these-images-vs-others.md b/docs/content/docs/1.getting-started/2.these-images-vs-others.md index 6793a4ba0..0fd424597 100644 --- a/docs/content/docs/1.getting-started/2.these-images-vs-others.md +++ b/docs/content/docs/1.getting-started/2.these-images-vs-others.md @@ -38,8 +38,7 @@ Setting environment variables saves a ton of headache when customizing your cont Our design philosophy is built all around simplicity. The process of customizing the behavior of PHP is as simple as setting an environment variable. We took every common configuration option and set it up so you can change these values in a simple method, defaulting every single option to production-ready values. -::u-button{to="/docs/reference/environment-variable-specification" label="Learn more about environment variables" aria-label="Learn more about environment variables" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} -:: +:u-button{to="/docs/reference/environment-variable-specification" label="Learn more about environment variables" aria-label="Learn more about environment variables" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} ## Optimized for Laravel & WordPress We did the hard work of customizing the official PHP image and giving you everything to get started with running PHP's most popular frameworks in seconds. We also hardened the images to help protect your PHP application from common attacks. @@ -54,4 +53,4 @@ Using **supervisor** to monitor multiple processes in a single container may lea [S6 Overlay](https://github.com/just-containers/s6-overlay){target="_blank"} is a process supervisor designed for containerization from the ground up. It's a modern alternative to [Supervisor (aka Supervisord)](https://supervisord.org/){target="_blank"}. We only use this in our `serversideup/php:*-fpm-apache` and `serversideup/php:*-fpm-nginx` images, as they require two processes to run a service. -::u-button{to="/docs/guides/using-s6-overlay" label="Learn more about S6 Overlay" aria-label="Learn more about S6 Overlay" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file +:u-button{to="/docs/guides/using-s6-overlay" label="Learn more about S6 Overlay" aria-label="Learn more about S6 Overlay" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From 8bcffda59dd92c14402dbd942b1c848d07a40faa Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 13:49:54 -0500 Subject: [PATCH 188/304] Refactor button syntax in installation documentation for consistency, ensuring a cleaner presentation of links to container basics and enhancing user experience. --- docs/content/docs/1.getting-started/3.installation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/docs/1.getting-started/3.installation.md b/docs/content/docs/1.getting-started/3.installation.md index d91228c3c..0d77596d5 100644 --- a/docs/content/docs/1.getting-started/3.installation.md +++ b/docs/content/docs/1.getting-started/3.installation.md @@ -34,8 +34,7 @@ There are some important terms to understand when working with containers: If you'd like to learn more about containers, you can learn more following the guide below. -::u-button{to="/docs/deployment-and-production/container-basics" label="Learn More About Containers" aria-label="Learn More About Containers" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} -:: +:u-button{to="/docs/deployment-and-production/container-basics" label="Learn More About Containers" aria-label="Learn More About Containers" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} ## Quick Start In order to run containers, we need a container engine installed such as Docker. You can follow [Docker's installation guide](https://docs.docker.com/get-started/get-docker/) to get started. Confirm Docker is working by running these commands in your terminal: From 6ee620c40f5328f910e1c2f9ff4f729f80595246 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 13:52:38 -0500 Subject: [PATCH 189/304] Add 'Choosing an Image' guide to documentation, detailing image selection criteria, popular tags, and variations for PHP Docker images, enhancing user understanding and decision-making. --- .../1.getting-started/4.choosing-an-image.md | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 docs/content/docs/1.getting-started/4.choosing-an-image.md diff --git a/docs/content/docs/1.getting-started/4.choosing-an-image.md b/docs/content/docs/1.getting-started/4.choosing-an-image.md new file mode 100644 index 000000000..c6a4f9ad6 --- /dev/null +++ b/docs/content/docs/1.getting-started/4.choosing-an-image.md @@ -0,0 +1,123 @@ +--- +title: Choosing an Image +description: 'Learn how to choose the right image for your use case.' +--- +::lead-p +Choosing an image might be a little intimidating because there are so many options, but this ultimately gives you a ton of flexibility. This guide will help simplify your decision making process on choosing the right image. +:: + +## Registry: Where images are located +Before diving into the different options available, let's understand where these images come from. The source code is [hosted on GitHub](https://github.com/serversideup/docker-php){target="_blank"} with an automated process that builds and uploads images to container registries: + +1. [Docker Hub](https://hub.docker.com/r/serversideup/php) +2. [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php) + +To use these images, you need to specify a certain "tag" for the image you want. For example, the `8.4` version of the `fpm-nginx` variation is tagged as: + +```bash +serversideup/php:8.4-fpm-nginx +``` + +This image gives you the latest stable patch version of PHP 8.4 running the `fpm-nginx` variation. Since no registry is specified, it defaults to Docker Hub. + +When selecting an image, you'll need to decide on: + +1. PHP version +2. PHP variation +3. Base operating system +4. serversideup/php version (for production environments) + +## Our most popular images +All images are intelligently tagged with the PHP version and variation, allowing you to easily select the right image for your use case. + +If you don't specify a variation, it defaults to `cli` and the latest supported Debian release for that variation. + +Our most popular tags include: +| ⚙️ Variation | 🚀 Version | +| ------------ | ---------- | +| cli | **Debian Based** [![serversideup/php:8.4-cli](https://img.shields.io/docker/image-size/serversideup/php/8.4-cli?label=serversideup%2Fphp%3A8.4-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-cli&page=1&ordering=-name) [![serversideup/php:8.3-cli](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli?label=serversideup%2Fphp%3A8.3-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli&page=1&ordering=-name) [![serversideup/php:8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli?label=serversideup%2Fphp%3A8.2-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli&page=1&ordering=-name) [![serversideup/php:8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli?label=serversideup%2Fphp%3A8.1-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli&page=1&ordering=-name) [![serversideup/php:8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli?label=serversideup%2Fphp%3A8.0-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli&page=1&ordering=-name) [![serversideup/php:7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli?label=serversideup%2Fphp%3A7.4-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-cli-alpine?label=serversideup%2Fphp%3A8.4-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.3-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli-alpine?label=serversideup%2Fphp%3A8.3-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.2-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli-alpine?label=serversideup%2Fphp%3A8.2-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.1-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli-alpine?label=serversideup%2Fphp%3A8.1-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.0-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli-alpine?label=serversideup%2Fphp%3A8.0-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli-alpine&page=1&ordering=-name) [![serversideup/php:7.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli-alpine?label=serversideup%2Fphp%3A7.4-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli-alpine&page=1&ordering=-name) | +| fpm | **Debian Based** [![serversideup/php:8.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm?label=serversideup%2Fphp%3A8.4-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm&page=1&ordering=-name) [![serversideup/php:8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm?label=serversideup%2Fphp%3A8.3-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm&page=1&ordering=-name) [![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm&page=1&ordering=-name) [![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm&page=1&ordering=-name) [![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm&page=1&ordering=-name) [![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-alpine?label=serversideup%2Fphp%3A8.4-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.3-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-alpine?label=serversideup%2Fphp%3A8.3-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.2-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-alpine?label=serversideup%2Fphp%3A8.2-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.1-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-alpine?label=serversideup%2Fphp%3A8.1-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.0-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-alpine?label=serversideup%2Fphp%3A8.0-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-alpine&page=1&ordering=-name) [![serversideup/php:7.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-alpine?label=serversideup%2Fphp%3A7.4-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-alpine&page=1&ordering=-name) | +| fpm-apache | **Debian Based** [![serversideup/php:8.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-apache?label=serversideup%2Fphp%3A8.4-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-apache?label=serversideup%2Fphp%3A8.3-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache&page=1&ordering=-name) [![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache&page=1&ordering=-name) | +| fpm-nginx | **Debian Based** [![serversideup/php:8.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx?label=serversideup%2Fphp%3A8.4-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx?label=serversideup%2Fphp%3A8.3-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx&page=1&ordering=-name) [![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.4-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.3-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.3-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.2-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.2-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.1-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.1-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.0-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.0-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:7.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A7.4-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx-alpine&page=1&ordering=-name) | +| frankenphp | **Debian Based** [![serversideup/php:8.4-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp?label=serversideup%2Fphp%3A8.4-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp?label=serversideup%2Fphp%3A8.3-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp-alpine?label=serversideup%2Fphp%3A8.4-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp-alpine&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp-alpine?label=serversideup%2Fphp%3A8.3-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp-alpine&page=1&ordering=-name) | +| unit (deprecated) | **Debian Based** [![serversideup/php:8.4-unit](https://img.shields.io/docker/image-size/serversideup/php/8.4-unit?label=serversideup%2Fphp%3A8.4-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-unit&page=1&ordering=-name) [![serversideup/php:8.3-unit](https://img.shields.io/docker/image-size/serversideup/php/8.3-unit?label=serversideup%2Fphp%3A8.3-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-unit&page=1&ordering=-name) [![serversideup/php:8.2-unit](https://img.shields.io/docker/image-size/serversideup/php/8.2-unit?label=serversideup%2Fphp%3A8.2-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-unit&page=1&ordering=-name) [![serversideup/php:8.1-unit](https://img.shields.io/docker/image-size/serversideup/php/8.1-unit?label=serversideup%2Fphp%3A8.1-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-unit&page=1&ordering=-name) [![serversideup/php:8.0-unit](https://img.shields.io/docker/image-size/serversideup/php/8.0-unit?label=serversideup%2Fphp%3A8.0-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-unit&page=1&ordering=-name) [![serversideup/php:7.4-unit](https://img.shields.io/docker/image-size/serversideup/php/7.4-unit?label=serversideup%2Fphp%3A7.4-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-unit&page=1&ordering=-name) | + +## The full tag schema +Our tagging process is quite flexible, and it can become quite complex, but don't worry -- we've spent a ton of time applying sensible defaults to give you the best experience. + +The full tag schema is as follows: +```jinja +{{registry-url}}/serversideup/php:{{release-prefix}}-{{php-version}}-{{variation-name}}-{{operating-system}}-v{{github-release-version}} +``` + +Here's what each part means: + +| Option | Default | Other Options | +|--------|---------|---------------| +| `{{registry-url}}`
Which registry to pull images from. | `''`
(Docker Hub) | `ghcr.io` (GitHub Packages) | +| `{{release-prefix}}`
The prefix of the release. | `''` (stable) | `beta` (beta releases) | +| `{{php-version}}`
The version of PHP to use. | Latest stable PHP minor version (ie. `8.5`) | `8.4`
`8.3`
`8.2`
`8.1`
`8.0`
`7.4`
(you can also specify the full version number, ie. `8.4.1`) | +| `{{variation-name}}`
The name of the variation to use. | `cli` | `fpm`
`fpm-apache`
`fpm-nginx`
`frankenphp`
`unit` (deprecated) | +| `{{operating-system}}`
The operating system to use. | `debian` | `alpine`
`bullseye`
`bookworm`
`trixie` | +| `{{github-release-version}}`
The version of the GitHub release to use. | (latest stable release) | See our [GitHub Releases](https://github.com/serversideup/docker-php/releases){target="_blank"} for specific versions. | + +## PHP version +There are many factors to consider when choosing the right PHP version. Best practices include: + +1. Choose the latest PHP version (as your dependencies allow) +2. Choose a PHP version that is receiving [active and security support by PHP](https://www.php.net/supported-versions.php){target="_blank"} + +If you need help understanding what's new in each PHP version, check out [the official PHP changelog](https://www.php.net/ChangeLog-8.php){target="_blank"}. This will give you a detailed overview of the changes and improvements in each version. + +## PHP variation +Choose from several variations to suit your needs. Each variation is optimized for specific use cases. + +| Variation | Description | +|-----------|-------------| +| `cli` | Minimal image for running PHP from the command line only. No web server included. | +| `fpm` | Runs PHP-FPM without a web server. Ideal for microservices architectures where you need a dedicated PHP backend. | +| `fpm-apache` | Combines PHP-FPM with Apache as a reverse proxy. Ideal for running WordPress with Docker. | +| `fpm-nginx` | Combines PHP-FPM with NGINX as a reverse proxy. This is the traditional setup widely adopted for modern PHP applications, including Laravel. | +| `frankenphp` | Modern application server built on Caddy that runs everything in a single process. Ideal for Laravel/Symfony applications with worker mode support, automatic HTTPS, and HTTP/2 and HTTP/3 protocols. | +| `unit`
(deprecated) | Uses NGINX Unit as a modern application server that runs everything in a single process. Consider migrating to FrankenPHP for a modern alternative. | + +### CLI +The CLI variation is a minimal image designed for running PHP from the command line only. It does not include a web server. Use this variation when you need a disposable and repeatable method for easily running PHP scripts from your terminal. + +:u-button{to="/docs/image-variations/cli" label="Learn more about the CLI variation" aria-label="Learn more about the CLI variation" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### FPM +The FPM (Fast Process Manager) variation runs PHP-FPM without a web server. This variation is ideal for microservices architectures where you need a dedicated PHP backend. + +Use this variation when you already have a separate proxy or load balancer handling static content and routing PHP requests to your FPM container. This is commonly used in larger-scale deployments. + +:u-button{to="/docs/image-variations/fpm" label="Learn more about the FPM variation" aria-label="Learn more about the FPM variation" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### FPM-Apache +The FPM-Apache variation combines PHP-FPM with Apache as a reverse proxy. Apache serves static content directly and forwards PHP requests to PHP-FPM for processing. + +This is an excellent choice for WordPress sites and applications that specifically require Apache features or `.htaccess` support. + +:u-button{to="/docs/image-variations/fpm-apache" label="Learn more about the FPM-Apache variation" aria-label="Learn more about the FPM-Apache variation" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + + +### FPM-NGINX +The FPM-NGINX variation combines PHP-FPM with NGINX as a reverse proxy. This is the traditional setup that has been widely adopted for modern PHP applications, including many Laravel deployments. + +NGINX serves static files efficiently and proxies PHP requests to PHP-FPM, providing excellent performance and flexibility. + +:u-button{to="/docs/image-variations/fpm-nginx" label="Learn more about the FPM-NGINX variation" aria-label="Learn more about the FPM-NGINX variation" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### FrankenPHP +The FrankenPHP variation is a modern application server built on top of the Caddy web server. It runs everything in a single process, eliminating the complexity of managing PHP-FPM and a separate web server. + +Use this variation for Laravel or Symfony applications that can benefit from worker mode, automatic HTTPS, and modern protocols like HTTP/2 and HTTP/3. This is the recommended variation for new projects seeking cutting-edge performance. + +:u-button{to="/docs/image-variations/frankenphp" label="Learn more about the FrankenPHP variation" aria-label="Learn more about the FrankenPHP variation" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### Unit (deprecated) +::note +In October 2025, NGINX stopped supporting NGINX Unit and archived the project. NGINX Unit will eventually be removed from our project. [See the official announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit) +:: + +The Unit variation uses NGINX Unit as an application server that runs everything in a single process. Since NGINX Unit is no longer maintained, consider migrating to FrankenPHP for a single-process alternative. \ No newline at end of file From 4d208e1181fa4857ae4e4e55e70d6d3d139ea62d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:07:15 -0500 Subject: [PATCH 190/304] Expand 'Choosing an Image' documentation to include detailed descriptions of operating systems, their benefits, and specific version support, enhancing user guidance for selecting appropriate PHP Docker images. --- .../1.getting-started/4.choosing-an-image.md | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/1.getting-started/4.choosing-an-image.md b/docs/content/docs/1.getting-started/4.choosing-an-image.md index c6a4f9ad6..e442deb83 100644 --- a/docs/content/docs/1.getting-started/4.choosing-an-image.md +++ b/docs/content/docs/1.getting-started/4.choosing-an-image.md @@ -120,4 +120,35 @@ Use this variation for Laravel or Symfony applications that can benefit from wor In October 2025, NGINX stopped supporting NGINX Unit and archived the project. NGINX Unit will eventually be removed from our project. [See the official announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit) :: -The Unit variation uses NGINX Unit as an application server that runs everything in a single process. Since NGINX Unit is no longer maintained, consider migrating to FrankenPHP for a single-process alternative. \ No newline at end of file +The Unit variation uses NGINX Unit as an application server that runs everything in a single process. Since NGINX Unit is no longer maintained, consider migrating to FrankenPHP for a single-process alternative. + +## Operating System +Choosing an operating system comes down to a few preferences, but ultimately you need to make sure your dependencies are available for the operating system you choose. + +| Operating System | Description | +|-----------|-------------| +| `debian` (default) | Debian is a popular Linux distribution that is known for its stability and reliability. It is the default operating system for our images. | +| `alpine` | Alpine is a lightweight Linux distribution that is known for its small size and low resource usage. It is the default operating system for our images. | + +### Debian +Debian is a popular Linux distribution that is known for its stability and compatibility. It is the default operating system for our images. + +### Alpine +Alpine is a lightweight Linux distribution that is known for its small size and low resource usage. If you're an advanced user, Alpine may be an excellent choice for you. + +### Specific versions +::note +Not all operating systems are available for all image variations and PHP versions. Double check [Docker Hub](https://hub.docker.com/r/serversideup/php/tags){target="_blank"} and [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php){target="_blank"} for the most accurate list of available tags. +:: +We also support specific versions of operating systems. So this means that you can choose versions like: + +- `bookworm` +- `trixie` +- `alpine3.16` +- `alpine3.17` +- `alpine3.18` +- `alpine3.19` +- `alpine3.20` +- `alpine3.21` +- `alpine3.22` + From 2ef19ef0ff28345ec7af9bbf8c268611aa42d1bb Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:11:31 -0500 Subject: [PATCH 191/304] Add comprehensive 'Default Configurations' documentation for PHP Docker images, detailing production readiness, default settings, environment variables, PHP extensions, OS packages, health checks, and entrypoint scripts, enhancing user understanding and deployment efficiency. --- .../5.default-configurations.md | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 docs/content/docs/1.getting-started/5.default-configurations.md diff --git a/docs/content/docs/1.getting-started/5.default-configurations.md b/docs/content/docs/1.getting-started/5.default-configurations.md new file mode 100644 index 000000000..f75466930 --- /dev/null +++ b/docs/content/docs/1.getting-started/5.default-configurations.md @@ -0,0 +1,128 @@ +--- +head.title: 'Installation - Docker PHP - Server Side Up' +description: 'Learn how to get started with serversideup/php Docker Images.' +layout: docs +--- + +::lead-p +`serversideup/php` is configured for real-world deployments right out of the box. This saves you many hours so you can launch faster than ever. +:: + +## Production-ready and optimized for Laravel & WordPress +All values are defaulted to improve security and performance. We also spent the time to carefully review official documentation and include packages that are required specifically for Laravel and WordPress. + +## Unprivileged by Default +All images default to running as the OS-native `www-data` user. + +::note +The `www-data` UID/GID is different between Debian (`33:33`) and Alpine (`82:82`). We left these values alone to make these images as native as possible. If you switch between Debian and Alpine, you may need to adjust file permissions in your Docker image and volume mounts. +:: + +Since these images are not privileged, that means they are not running on ports less than 1024: + +| **Variation** | **Default Ports** | +|---------------|-------------------| +| cli | (none) | +| fpm | 9000 | +| fpm-nginx | HTTP: 8080, HTTPS: 8443 | +| fpm-apache | HTTP: 8080, HTTPS: 8443 | +| unit | HTTP: 8080, HTTPS: 8443 | +| frankenphp | HTTP: 8080, HTTPS: 8443 | + +### How do I run these services on ports 80 and/or 443? +Almost everyone will want to run these services on ports 80 and 443. If you have an advanced setup, you can use a reverse proxy like Caddy or Traefik to handle the SSL termination and forward the traffic to the container on the non-privileged port. + +Or you can simply use Docker's port mapping feature to map the container port to the host port. For example, to run the `fpm-nginx` variation on port 80 and 443, you can run the following command: + +```bash [Terminal] +docker run -p 80:8080 -p 443:8443 serversideup/php:8.4-fpm-nginx +``` + +## Default Environment Variables +Environment variables give you a ton of flexibility to customize your container without the complexity of mounting custom configuration files. By default, these images are set to production-ready values. You can read more about the available environment variables below. + +:u-button{to="/docs/reference/environment-variable-specification" label="Learn more about environment variables" aria-label="Learn more about environment variables" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Default PHP INI Settings + +| **PHP INI file** | **Description** | +|-----------------|-----------------| +| **Image Default**

`/usr/local/etc/php/conf.d/serversideup-docker-php.ini` | This is our production-ready PHP ini file that accepts environment variables. You can [review it in greater detail on GitHub](https://github.com/serversideup/docker-php/blob/main/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini){target="_blank"}. | +| **Adding your own PHP INI file**

`/usr/local/etc/php/conf.d/*.ini` | If you want to use our defaults, you can simple create a `.ini` file in the `/usr/local/etc/php/conf.d/` directory and it will be loaded automatically. This will be loaded *after* our default ini file, so whatever you set in your own ini file will override the default values. | + +:u-button{to="/docs/customizing-the-image/changing-common-php-settings" label="Learn more about changing common PHP settings" aria-label="Learn more about changing common PHP settings" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Default PHP Extensions +The following extensions are installed by default: + +| **Extension** | **Description** | **Why we included it** | +|---------------|-----------------|------------------------| +| [opcache](https://www.php.net/manual/en/book.opcache.php) | The Zend OPcache provides faster PHP execution through opcode caching and optimization. | This is a must-have for PHP performance.

⚠️ OPcache is disabled by default but can easily be enabled with [`PHP_OPCACHE_ENABLE=1`](/docs/reference/environment-variable-specification).| +| [mysqli](https://www.php.net/manual/en/book.mysqli.php) | The "MySQL Improved" extension is an older extension for connecting to MySQL 4.1 and above. | **Enabled for fpm-apache only**. This is a legacy MySQL connector required for WordPress.| +| [pcntl](https://www.php.net/manual/en/intro.pcntl.php) | Process Control support in PHP implements the Unix style of process creation, program execution, signal handling and process termination. | This is required for [Laravel queues and Laravel Horizon](https://laravel.com/docs/10.x/queues#timeout)| +| [pdo_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php) | The MySQL PDO extension allows you to connect to MySQL databases. | MySQL and MariaDB databases are very popular. | +| [pdo_pgsql](https://www.php.net/manual/en/ref.pdo-pgsql.php) | The PostgreSQL PDO extension allows you to connect to PostgreSQL databases. | PostgreSQL databases are very popular. | +| [redis](https://www.php.net/manual/en/book.redis.php) | The Redis extension allows you to connect to Redis databases. | Redis is very popular for caching and it's also required for [Laravel Horizon](https://laravel.com/docs/10.x/horizon). Our tests concluded adding this package only added 2MB to the image size. | +| [zip](https://www.php.net/manual/en/book.zip.php) | The Zip extension allows you to create and extract zip files. | We included this for the popularity of apps working with ZIP files. This package is also required if you're working with attachments on [Laravel Dusk](https://laravel.com/docs/10.x/dusk#attaching-files). | + +The official PHP images are already providing the following extensions: +- ctype +- curl +- dom +- fileinfo +- filter +- hash +- mbstring +- openssl +- pcre +- session +- tokenizer +- xml + +If you need to install additional extensions, you can use the guide below to learn more. + +:u-button{to="/docs/customizing-the-image/installing-additional-php-extensions" label="Learn more about installing additional PHP extensions" aria-label="Learn more about installing additional PHP extensions" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Default Operating System Packages +The following packages are installed by default: + +| **Package** | **Description** | **Image variations** | **Why we included it** | +|-------------|-----------------|----------------------|------------------------| +| `libfcgi-bin`
(Debian)
`fcgi`
(Alpine) | FastCGI is a protocol for interfacing interactive programs with a web server. | *-fpm
*-fpm-nginx
*-fpm-apache | This is required for the webserver to interface with PHP-FPM and the [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck) project. | +| `gettext-base` (Debian)
`gettext` (Alpine) | GNU gettext is a framework for translating user interfaces. | *-fpm-nginx
*-fpm-apache | This is required for the `envsubst` command. We use this command to process templates on container initialization. | +| `libstdc++6`
(Debian)
`libstdc++` (Alpine) | The GNU Standard C++ Library is a C++ standard library. | *-frankenphp | This is [required for the watcher to run](https://github.com/php/frankenphp/blob/e917ab79742c9e4703023861fdc7a86cdb59da1e/Dockerfile#L135-L138) with FrankenPHP. | +| `procps` | The procps package contains programs for monitoring your system and its processes. | * (only Debian images) | This is required for `pgrep` so we can use that for our native health checks. | +| `shadow` | Shadow is required for the `usermod` command. | *-alpine | This is required to change the UID and GID of the `www-data` user in `docker-php-serversideup-set-id`. | + +:u-button{to="/docs/getting-started/choosing-an-image#operating-system" label="Learn which operating systems are available" aria-label="Learn which operating systems are available" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + + +## Health Checks +By default, all health checks for web servers (Apache, Unit, NGINX, etc.) are located at `/healthcheck`. You should receive an `OK` response if the container is healthy. + +For our `fpm` variation, we use the [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck) script to verify the health of PHP-FPM. This script is located at `/usr/local/bin/php-fpm-healthcheck`. + +The `cli` variation does not have a health check because it doesn't really make sense to have one. Would love to discuss more if you feel different. + +:u-button{to="/docs/guides/using-healthchecks-with-laravel" label="Learn more about health checks" aria-label="Learn more about health checks" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + + +## Default Entrypoint Scripts +We created an entrypoint script that scans the `entrypoint.d` directory for other shell scripts to execute before the main container process starts. All scripts are executed in alphabetical order so you can have full control over what script execution order. + +We also provide a few default scripts to help you get started. + +| **Script Name** | **Description** | **Image variations** | +|------------|-----------------|----------------------| +| `0-container-info.sh` | Shows basic execution information, such as Docker User, UID, GID, etc. | all | +| `1-log-output-level.sh` | Sets PHP log output level to match `LOG_OUTPUT_LEVEL` | all | +| `10-init-unit.sh` | Processes Unit templates, configures SSL (if enabled), and prepares NGINX Unit for launch | unit | +| `10-init-webserver-config.sh` | Processes web server configuration templates, configures SSL (if enabled), and prepares web server for launch | *-nginx
*-apache | +| `50-laravel-automations.sh` | If `AUTORUN_ENABLED` is set to true, and a Laravel installation is detected, the following commands will automatically execute on container start:
- `php artisan config:cache`
- `php artisan route:cache`
- `php artisan view:cache`
- `php artisan event:cache`
- `php artisan migrate --force` | all | + +If you want to add your own entrypoint scripts, use the guide below to learn more. + +:u-button{to="/docs/customizing-the-image/adding-your-own-start-up-scripts" label="Learn more about adding your own start up scripts" aria-label="Learn more about adding your own start up scripts" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Disabling Default Entrypoint Scripts +We get it. Sometimes you just want our stuff to get out of the way. If you want full control to customize your image, all default entrypoint scripts can be disabled by setting `DISABLE_DEFAULT_CONFIG` to `true`. From 5fec04582c653f2f4caf336100810af544ab0a0e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:12:14 -0500 Subject: [PATCH 192/304] Add upgrade guide documentation for Docker PHP images, detailing version selection, release process, and manual update instructions for Debian and Alpine, enhancing user understanding of image management and stability in production environments. --- .../docs/1.getting-started/6.upgrade-guide.md | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/content/docs/1.getting-started/6.upgrade-guide.md diff --git a/docs/content/docs/1.getting-started/6.upgrade-guide.md b/docs/content/docs/1.getting-started/6.upgrade-guide.md new file mode 100644 index 000000000..0f3f65fba --- /dev/null +++ b/docs/content/docs/1.getting-started/6.upgrade-guide.md @@ -0,0 +1,55 @@ +--- +head.title: 'Upgrade Guide - Docker PHP - Server Side Up' +description: 'Learn how the serversideup/php Docker images are built and how to upgrade.' +layout: docs +title: Upgrading +--- + +If you do not select a specific patch version, then you will receive automatic PHP updates. + +For example, you can select your version based on the different version numbers: +- Major Version (example: `8` will give you the latest 8.x version) +- Minor Version (example: `8.4` will give you the latest 8.4.x version) +- Patch Version (example: `8.4.1` will always stay at the 8.4.1 version) + +If you use `latest`, you will always get the latest stable version of the CLI variation of PHP. For the best stability in production environments, you may want to pin to a specific patch version (example: `8.4.1`). + +If you need help choosing an image, see our guide below. + +:u-button{to="/docs/getting-started/choosing-an-image" label="Learn more about choosing an image" aria-label="Learn more about choosing an image" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Release process +All source code is merged into the `main` branch, which automatically build our "beta" images. + +After testing the images internally and from other community members, we will [publish a release on GitHub](https://github.com/serversideup/docker-php/releases) with a detailed changelog. + +## Updating the Docker images on your own +If you You and simply run `apt-get update && apt-get upgrade` within your own image, but still want the image to receive other operating system updates, you will need to run the following commands on your build. + +Any updates that you apply have a risk of breaking other things inside the container. Be sure to have a good testing process in place before applying updates to your production environment. + +#### Example Dockerfile with manual updates for Debian +```dockerfile [Dockerfile] +FROM serversideup/php:8.4.1-fpm-nginx + +RUN apt-get update \ + && apt-get upgrade -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* +``` + +If you're running an Alpine-based image, you can use the following commands: + +#### Example Dockerfile with manual updates for Alpine +```dockerfile [Dockerfile] +FROM serversideup/php:8.4.1-fpm-nginx-alpine + +RUN apk update \ + && apk upgrade \ + && rm -rf /var/cache/apk/* +``` + +## Subscribe to repository updates +Regardless if you are choosing to use automatic updates or manual updates, it is highly advised to subscribe to our releases. You can do this through the "Watch" button on our [GitHub](https://github.com/serversideup/docker-php). + +![Watch Repository](images/docs/watch-repo.png){.max-w-md.w-full.h-auto.mx-auto} From f9b01c51a9d4ae87592e9d67257182e81bf59802 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:12:46 -0500 Subject: [PATCH 193/304] Add changelog documentation for Docker PHP images, providing an overview of changes and a link to release notes on GitHub, enhancing user awareness of updates and version selection. --- docs/content/docs/1.getting-started/7.changelog.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/content/docs/1.getting-started/7.changelog.md diff --git a/docs/content/docs/1.getting-started/7.changelog.md b/docs/content/docs/1.getting-started/7.changelog.md new file mode 100644 index 000000000..ddd9b0a3f --- /dev/null +++ b/docs/content/docs/1.getting-started/7.changelog.md @@ -0,0 +1,13 @@ +--- +head.title: 'Changelog - Docker PHP - Server Side Up' +description: 'See the latest releases and changes for the PHP Docker Image project.' +layout: docs +--- +::lead-p +All our changes are documented and published on our GitHub. [See our release notes on GitHub →](https://github.com/serversideup/docker-php/releases){target="_blank"} +:: + +### Choosing a version +You may want to review [our guide on selecting the right image](/docs/getting-started/choosing-an-image) to determine which version and image tag is best for you. + +:u-button{to="https://github.com/serversideup/docker-php/releases" target="_blank" label="See our release notes on GitHub" aria-label="See our release notes on GitHub" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From 364ee7b0b5274f44bf312dff37d799f198126553 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:12:51 -0500 Subject: [PATCH 194/304] Add 'About Us' documentation for Docker PHP images, introducing the project creators, their mission, and resources for community engagement, enhancing user connection and understanding of the project's background. --- .../content/docs/1.getting-started/8.about.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/content/docs/1.getting-started/8.about.md diff --git a/docs/content/docs/1.getting-started/8.about.md b/docs/content/docs/1.getting-started/8.about.md new file mode 100644 index 000000000..d11aee036 --- /dev/null +++ b/docs/content/docs/1.getting-started/8.about.md @@ -0,0 +1,53 @@ +--- +head.title: 'About - Docker PHP - Server Side Up' +description: 'Learn more about how this project got started and who inspired it.' +layout: docs +--- + +::lead-p +We're taking the extra effort to open source as much as we can. Not only could this potentially help someone learn a little bit of Docker, but it makes it a *heck of a lot* easier for us to work with other people on our other open source projects. +:: + +## About Us +We're [Dan](https://x.com/danpastori){target="_blank"} and [Jay](https://x.com/jaydrogers){target="_blank"} - a two person team with a passion for open source products. We created [Server Side Up](https://serversideup.net){target="_blank"} to help share what we learn. + +### Find us at: + +* **📖 [Blog](https://serversideup.net){target="_blank"}** - Get the latest guides and free courses on all things web/mobile development. +* **🙋 [Community](https://community.serversideup.net){target="_blank"}** - Get friendly help from our community members. +* **🤵‍♂️ [Get Professional Help](https://serversideup.net/professional-support){target="_blank"}** - Get video + screen-sharing support from the core contributors. +* **💻 [GitHub](https://github.com/serversideup){target="_blank"}** - Check out our other open source projects. +* **📫 [Newsletter](https://serversideup.net/subscribe){target="_blank"}** - Skip the algorithms and get quality content right to your inbox. +* **🐥 [Twitter](https://x.com/serversideup){target="_blank"}** - You can also follow [Dan](https://x.com/danpastori){target="_blank"} and [Jay](https://x.com/jaydrogers){target="_blank"}. +* **❤️ [Sponsor Us](https://github.com/sponsors/serversideup){target="_blank"}** - Please consider sponsoring us so we can create more helpful resources. + +## Our products +If you appreciate this project, be sure to check out our other projects. + +### 📚 Books +- **[The Ultimate Guide to Building APIs & SPAs](https://serversideup.net/ultimate-guide-to-building-apis-and-spas-with-laravel-and-nuxt3/){target="_blank"}**: Build web & mobile apps from the same codebase. +- **[Building Multi-Platform Browser Extensions](https://serversideup.net/building-multi-platform-browser-extensions/){target="_blank"}**: Ship extensions to all browsers from the same codebase. + +### 🛠️ Software-as-a-Service +- **[Bugflow](https://bugflow.io/){target="_blank"}**: Get visual bug reports directly in GitHub, GitLab, and more. +- **[SelfHost Pro](https://selfhostpro.com/){target="_blank"}**: Connect Stripe or Lemonsqueezy to a private docker registry for self-hosted apps. + +### 🌍 Open Source +- **[AmplitudeJS](https://serversideup.net/open-source/amplitudejs){target="_blank"}**: Open-source HTML5 & JavaScript Web Audio Library. +- **[Spin](https://serversideup.net/open-source/spin/){target="_blank"}**: Laravel Sail alternative for running Docker from development → production. +- **[Financial Freedom](https://github.com/serversideup/financial-freedom){target="_blank"}**: Open source alternative to Mint, YNAB, & Monarch Money. + +## Special thanks +This project wouldn't be possible without the help of some amazing people. We're grateful for their support and contributions. We wanted to give them a special shoutout here (in no particular order). + +### [Chris Fidao](https://github.com/fideloper){target="_blank"} +Majority of our knowledge came from Chris' course, [Shipping Docker](https://serversforhackers.com/shipping-docker). If you have yet to discover his content, you will be very satisfied with every course he has to offer. He's a great human being and excellent educator. Chris has continuously provided quality feedback that has deeply inspired the direction of this project. + +### [Joel Clermont](https://github.com/joelclermont/){target="_blank"} +Joel is one of the friendliest people that you'll ever meet. When he's not podcasting on his own show, [No Compromises](https://show.nocompromises.io/), he's busy helping other people in the PHP community. Joel's deep experience with PHP is rare to find, and we're grateful that we were able to pick his brain to bring you the best PHP Docker experience possible. + +### [Patricio](https://github.com/ijpatricio){target="_blank"} +If there's a new framework or tool in the web development world, Patricio probably has content for it on his [YouTube Channel](https://www.youtube.com/@PatricioOnCode). Patricio is a great resource for all things web development. Frontend, backend, DevOps -- it doesn't matter. Patricio graciously shared his wide spectrum of experiences with us and is a huge supporter of this project. + +### [linuxserver.io](https://www.linuxserver.io/){target="_blank"} +These guys are absolute aces when it comes to Docker development. They are a great resource for tons of open source Docker images. Check out their work, especially if you're a homelabber. \ No newline at end of file From 46a830e137ea831bf7f910a9e7438f41745441ca Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:13:15 -0500 Subject: [PATCH 195/304] Add contributing documentation for Docker PHP images, outlining guidelines for improving documentation, project dependencies, local development setup, and GitHub Actions usage, enhancing community engagement and support for contributors. --- .../docs/1.getting-started/9.contributing.md | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 docs/content/docs/1.getting-started/9.contributing.md diff --git a/docs/content/docs/1.getting-started/9.contributing.md b/docs/content/docs/1.getting-started/9.contributing.md new file mode 100644 index 000000000..d07cffc1b --- /dev/null +++ b/docs/content/docs/1.getting-started/9.contributing.md @@ -0,0 +1,132 @@ +--- +head.title: 'Contributing - Docker PHP - Server Side Up' +description: 'Learn how you can make a difference and contribute to the Docker PHP project.' +layout: docs +--- + +::lead-p +Thanks for your interest in contributing to this project! Please use read this entire guide before submitting a pull request. +:: + +## Improve the docs +Improving the docs is very easy. If you find a simple mistake, look at the bottom of the Table Of Contents section and click "Edit this page". + +If you'd like to contribute bigger documentation changes, take a look at the `/docs` directory. Our entire site is available in that directory and you can see the process to easily install this on your machine by reading `/docs/README.md`. + +:u-button{to="https://github.com/serversideup/docker-php/tree/main/docs" target="_blank" label="Learn how to contribute to the docs" aria-label="Learn how to contribute to the docs" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + + +## Project dependencies +You must have these installed on your system. +* Docker (container engine): https://www.docker.com/products/docker-desktop + +## How things work +1. All files are stored in the `/src` folder +1. GitHub Actions will automatically build and deploy the images + +## Running things locally +To run a build, simply run `./scripts/dev.sh` (with Docker Desktop running). This will show you a help menu with all the available options. + +#### Example: Build a Unit image running PHP 8.2.12 on Debian Bookworm +```bash [Terminal] +bash scripts/dev.sh --variation unit --version 8.2.12 --os bookworm +``` + +This will build `serversideup/php:8.2.12-unit-bookworm` locally on your machine for testing and inspection. + +### Published Beta Images +We also have beta images that are published to our Docker Hub and GitHub Packages repositories. + +#### Debian Variations +| ⚙️ Variation | 🚀 Version | +| ------------ | ---------- | +| cli | **Debian Based** [![serversideup/php:beta-8.4-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-cli?label=serversideup%2Fphp%3Abeta-8.4-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-cli&page=1&ordering=-name) [![serversideup/php:beta-8.3-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-cli?label=serversideup%2Fphp%3Abeta-8.3-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-cli&page=1&ordering=-name) [![serversideup/php:beta-8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-cli?label=serversideup%2Fphp%3Abeta-8.2-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-cli&page=1&ordering=-name) [![serversideup/php:beta-8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-cli?label=serversideup%2Fphp%3Abeta-8.1-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-cli&page=1&ordering=-name) [![serversideup/php:beta-8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-cli?label=serversideup%2Fphp%3Abeta-8.0-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-cli&page=1&ordering=-name) [![serversideup/php:beta-7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-cli?label=serversideup%2Fphp%3Abeta-7.4-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-cli&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:beta-8.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-cli-alpine?label=serversideup%2Fphp%3Abeta-8.4-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-cli-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.3-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-cli-alpine?label=serversideup%2Fphp%3Abeta-8.3-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-cli-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.2-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-cli-alpine?label=serversideup%2Fphp%3Abeta-8.2-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-cli-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.1-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-cli-alpine?label=serversideup%2Fphp%3Abeta-8.1-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-cli-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.0-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-cli-alpine?label=serversideup%2Fphp%3Abeta-8.0-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-cli-alpine&page=1&ordering=-name) [![serversideup/php:beta-7.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-cli-alpine?label=serversideup%2Fphp%3Abeta-7.4-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-cli-alpine&page=1&ordering=-name) | +| fpm | **Debian Based** [![serversideup/php:beta-8.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm?label=serversideup%2Fphp%3Abeta-8.4-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm&page=1&ordering=-name) [![serversideup/php:beta-8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm?label=serversideup%2Fphp%3Abeta-8.3-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm&page=1&ordering=-name) [![serversideup/php:beta-8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm?label=serversideup%2Fphp%3Abeta-8.2-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm&page=1&ordering=-name) [![serversideup/php:beta-8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm?label=serversideup%2Fphp%3Abeta-8.1-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm&page=1&ordering=-name) [![serversideup/php:beta-8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm?label=serversideup%2Fphp%3Abeta-8.0-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm&page=1&ordering=-name) [![serversideup/php:beta-7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm?label=serversideup%2Fphp%3Abeta-7.4-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:beta-8.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.4-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.3-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.3-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.2-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.2-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.1-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.1-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.0-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm-alpine?label=serversideup%2Fphp%3Abeta-8.0-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm-alpine&page=1&ordering=-name) [![serversideup/php:beta-7.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm-alpine?label=serversideup%2Fphp%3Abeta-7.4-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm-alpine&page=1&ordering=-name) | +| fpm-apache | **Debian Based** [![serversideup/php:beta-8.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm-apache?label=serversideup%2Fphp%3Abeta-8.4-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm-apache&page=1&ordering=-name) [![serversideup/php:beta-8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm-apache?label=serversideup%2Fphp%3Abeta-8.3-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm-apache&page=1&ordering=-name) [![serversideup/php:beta-8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm-apache?label=serversideup%2Fphp%3Abeta-8.2-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm-apache&page=1&ordering=-name) [![serversideup/php:beta-8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm-apache?label=serversideup%2Fphp%3Abeta-8.1-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm-apache&page=1&ordering=-name) [![serversideup/php:beta-8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm-apache?label=serversideup%2Fphp%3Abeta-8.0-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm-apache&page=1&ordering=-name) [![serversideup/php:beta-7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm-apache?label=serversideup%2Fphp%3Abeta-7.4-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm-apache&page=1&ordering=-name) | +| fpm-nginx | **Debian Based** [![serversideup/php:beta-8.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.4-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm-nginx&page=1&ordering=-name) [![serversideup/php:beta-8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.3-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm-nginx&page=1&ordering=-name) [![serversideup/php:beta-8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.2-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm-nginx&page=1&ordering=-name) [![serversideup/php:beta-8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.1-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm-nginx&page=1&ordering=-name) [![serversideup/php:beta-8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm-nginx?label=serversideup%2Fphp%3Abeta-8.0-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm-nginx&page=1&ordering=-name) [![serversideup/php:beta-7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm-nginx?label=serversideup%2Fphp%3Abeta-7.4-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm-nginx&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:beta-8.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.4-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.3-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.3-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.2-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.2-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.1-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.1-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.0-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-8.0-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:beta-7.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-fpm-nginx-alpine?label=serversideup%2Fphp%3Abeta-7.4-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-fpm-nginx-alpine&page=1&ordering=-name) | +| frankenphp | **Debian Based** [![serversideup/php:beta-8.4-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-frankenphp?label=serversideup%2Fphp%3Abeta-8.4-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-frankenphp&page=1&ordering=-name) [![serversideup/php:beta-8.3-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-frankenphp?label=serversideup%2Fphp%3Abeta-8.3-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-frankenphp&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:beta-8.4-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-frankenphp-alpine?label=serversideup%2Fphp%3Abeta-8.4-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-frankenphp-alpine&page=1&ordering=-name) [![serversideup/php:beta-8.3-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-frankenphp-alpine?label=serversideup%2Fphp%3Abeta-8.3-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-frankenphp-alpine&page=1&ordering=-name) | +| unit (deprecated) | **Debian Based** [![serversideup/php:beta-8.4-unit](https://img.shields.io/docker/image-size/serversideup/php/beta-8.4-unit?label=serversideup%2Fphp%3Abeta-8.4-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.4-unit&page=1&ordering=-name) [![serversideup/php:beta-8.3-unit](https://img.shields.io/docker/image-size/serversideup/php/beta-8.3-unit?label=serversideup%2Fphp%3Abeta-8.3-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.3-unit&page=1&ordering=-name) [![serversideup/php:beta-8.2-unit](https://img.shields.io/docker/image-size/serversideup/php/beta-8.2-unit?label=serversideup%2Fphp%3Abeta-8.2-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.2-unit&page=1&ordering=-name) [![serversideup/php:beta-8.1-unit](https://img.shields.io/docker/image-size/serversideup/php/beta-8.1-unit?label=serversideup%2Fphp%3Abeta-8.1-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.1-unit&page=1&ordering=-name) [![serversideup/php:beta-8.0-unit](https://img.shields.io/docker/image-size/serversideup/php/beta-8.0-unit?label=serversideup%2Fphp%3Abeta-8.0-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-8.0-unit&page=1&ordering=-name) [![serversideup/php:beta-7.4-unit](https://img.shields.io/docker/image-size/serversideup/php/beta-7.4-unit?label=serversideup%2Fphp%3Abeta-7.4-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=beta-7.4-unit&page=1&ordering=-name) | + +#### Running a test web server: +Sometimes you need to just run a test web server locally to see if your changes work. Below is a good example on how to quickly do this. + +```bash [Terminal] +docker run --rm -v $(pwd):/var/www/html -p 80:8080 -p 443:8443 serversideup/php:8.4-fpm-nginx +``` + +## How PHP Versions are selected for distribution +We use the official PHP versions as our base image. To identify which versions should be built, we use a file called `scripts/conf/php-versions-base-config.yml` to explicitly select what versions should be built and any special rules/settings for each version (like base OS, default versions, etc). + +We then use a `scripts/get-php-versions.sh` script to download the [latest active releases from PHP](https://www.php.net/releases/active.php) and merge them into a final file called `scripts/conf/php-versions.yml`. + +The `php-versions.yml` file will include all final versions for tagging and building. + +We generate our tags with a file called `scripts/assemble-docker-tags.sh` which handles all the advanced logic of compiling our tags together. + +All the scripts above are designed to run locally and in GitHub Actions. Feel free to execute these scripts to see the help menus and how they work. + +## GitHub Actions +We use GitHub Actions exclusively to publish all of our releases. If the image exists from DockerHub or GitHub Packages, it will never be published from a local machine. + +See `.github/workflows/action_publish-beta-images.yml` for an example of how we publish our beta images. + +## NGINX Versions +We use the official NGINX repos to install the latest version of NGINX for each OS. The version to install is set by a build argument, which is loaded from the `scripts/conf/php-versions-base-config.yml` file. + +To view the current NGINX versions, run the following command: + +#### View NGINX versions +```bash [Terminal] +./scripts/get-nginx-versions.sh +``` + +This script will look at the official NGINX repos to find the latest version of NGINX for each OS. If you want to update the version, you can run the script with the `--write` flag. + +#### Update NGINX versions +```bash [Terminal] +./scripts/get-nginx-versions.sh --write +``` + +### NGINX repository key verification + +- **Debian (APT)**: We import the official NGINX GPG key from `https://nginx.org/keys/nginx_signing.key` and verify it against a pinned fingerprint via the `SIGNING_FINGERPRINT` build arg. +- **Alpine (APK)**: APK uses a raw RSA public key (`nginx_signing.rsa.pub`). We verify this key by pinning the SHA‑256 of the DER‑encoded public key via the `SIGNING_ALPINE_RSA_PUB_SHA256` build arg. You can provide multiple comma‑separated hashes to support key rotation. + +#### Compute the Alpine key hash when updating: + +```bash [Terminal] +curl -sS https://nginx.org/keys/nginx_signing.rsa.pub -o /tmp/nginx_signing.rsa.pub + +# macOS +openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | shasum -a 256 | awk '{print $1}' + +# Linux +openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -outform DER 2>/dev/null | sha256sum | awk '{print $1}' +``` + +#### Build with the new hash (optionally include the old hash during rotation) + + +```bash [Terminal] +docker build \ + --build-arg SIGNING_ALPINE_RSA_PUB_SHA256="," \ + -f src/variations/fpm-nginx/Dockerfile . +``` + +Reference: [Installing NGINX Open Source → Alpine packages](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#prebuilt_alpine). + +Why allow multiple hashes? This is optional, but useful during a short rotation window: + +- Ensure CI builds across branches/runners succeed while the upstream key change propagates. +- Avoid flakes from CDN/caching delays where some environments still see the old key. +- Let you pre-stage the new value before the official switch, then remove the old afterwards. + +If you control all builds centrally and can update quickly, pass a single hash. + +## Helping out +If you're really eager to help out, here are a few places to get started: +- Help answer questions on [our GitHub Discussions](https://github.com/serversideup/docker-php/discussions) and [our Discord](https://serversideup.net/discord) +- Chime in on [issues labeled "Help Wanted"](https://github.com/serversideup/docker-php/issues?q=is%3Aissue+is%3Aopen+label%3A%22%F0%9F%99%8F+Help+Wanted%22) +- [Open a feature request](https://github.com/serversideup/docker-php/discussions/66) and tell us how we can improve +- Run performance tests and share your results +- Say good things and tag us on X (formerly Twitter): [@serversideup](https://x.com/serversideup), [@danpastori](https://x.com/danpastori), [@jaydrogers](https://x.com/jaydrogers) From 9751cae8702e1ddb9e5ede4ebfd4bac186afea3a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:15:32 -0500 Subject: [PATCH 196/304] Add CLI documentation for the PHP Docker image, detailing usage, features, and quick start examples for running PHP commands without a web server, enhancing user understanding of the CLI variation. --- docs/content/docs/2.image-variations/cli.md | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/content/docs/2.image-variations/cli.md diff --git a/docs/content/docs/2.image-variations/cli.md b/docs/content/docs/2.image-variations/cli.md new file mode 100644 index 000000000..ce863bfe3 --- /dev/null +++ b/docs/content/docs/2.image-variations/cli.md @@ -0,0 +1,78 @@ +--- +title: CLI +description: 'Learn how to use the CLI variation of the serversideup/php image.' +--- + +::lead-p +The CLI variation is a minimal image designed for running PHP from the command line only. It does not include a web server. + +Use this variation for running commands like Composer, running one-off scripts, or executing PHP commands that don't require a web server. +:: + +## When to Use CLI +Use the CLI variation when you need to: + +- Run Composer for dependency management +- Execute one-off PHP scripts +- Need a very small image size + +#### Perfect for +- Running PHP locally without needing to install PHP on your host system. + +#### What's Inside + +| Item | Status | +|------|--------| +| PHP CLI binary | ✅ | +| Common PHP extensions | ✅ | +| `composer` executable | ✅ | +| `install-php-extensions` script | ✅ | +| Essential system utilities | ✅ | +| Native health checks | ❌ | +| Web server | ❌ (no web server included) | +| Process management | Single entrypoint, single process | +| Exposed Ports | None | +| Stop Signal | `SIGTERM` | + +## Quick Start +Here are a few quick examples to get you started. + +### Docker CLI +```bash [Terminal] +docker run -it -v $(pwd):/var/www/html serversideup/php:cli bash +``` + +The above command will mount your current directory as the `/var/www/html` directory in the container and open a bash shell inside the container where PHP is installed. To exit, just type `exit`. + +### Docker Compose +If you want something more repeatable, you can use Docker Compose to start a container with the CLI variation and mount your current directory as the `/var/www/html` directory in the container. + +```yml [compose.yml] +services: + php: + image: serversideup/php:cli + volumes: + - ./:/var/www/html +``` + +Once you have your `compose.yml` file set, you can use the `docker compose` cli to start a container with your configuration. + +```bash [Terminal] +docker compose run -it php bash +``` + +Or you can pass commands directly to the container without starting a shell. + +::note +Don't get confused. `php` is in this command twice because it's the name of the service and the command to run inside the container. If this is too confusing, you can set your service name to something else like `app` in your `compose.yml` file. +:: + +```bash [Terminal] +docker compose run php php my-script.php +``` + +### Further Customization +If you need to customize the container further, reference the docs below: + +- [Environment Variable Specifications](docs/reference/environment-variable-specification) - See which environment variables are available to customize common PHP settings. +- [Command Reference](docs/reference/command-reference) - See which commands are available to run inside the container. From 4fcdae33beafbf1bd5cfe1e1e1b835b690401fe6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:19:15 -0500 Subject: [PATCH 197/304] Add FPM documentation for the PHP Docker image, detailing usage, configuration, and quick start examples for running PHP-FPM in a microservices architecture, enhancing user understanding of the FPM variation and its integration with web servers. --- docs/content/docs/2.image-variations/fpm.md | 224 ++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 docs/content/docs/2.image-variations/fpm.md diff --git a/docs/content/docs/2.image-variations/fpm.md b/docs/content/docs/2.image-variations/fpm.md new file mode 100644 index 000000000..802027470 --- /dev/null +++ b/docs/content/docs/2.image-variations/fpm.md @@ -0,0 +1,224 @@ +--- +title: FPM +description: 'Learn how to use the FPM variation of the serversideup/php image.' +--- + +::lead-p +The FPM variation runs PHP-FPM (FastCGI Process Manager) without a web server. It's designed to work alongside a separate web server or load balancer that handles static content and proxies PHP requests to this container. + +Use this variation when you're building microservices architectures or have a separate proxy layer handling HTTP traffic. +:: + +## When to Use FPM +Use the FPM variation when you need to: + +- Separate your PHP processing from your web server layer +- Build microservices where PHP runs as a dedicated backend service +- Use a separate load balancer or API gateway to route traffic +- Have an existing NGINX, Traefik, or other reverse proxy infrastructure +- Scale your PHP processing independently from your web server + +#### Perfect for +- Microservices architectures where separation of concerns is important +- Kubernetes deployments with separate service containers +- Large-scale deployments with dedicated load balancers +- Advanced setups where you want full control over your proxy configuration + +#### What's Inside + +| Item | Status | +|------|--------| +| PHP-FPM process manager | ✅ | +| PHP CLI binary | ✅ | +| Common PHP extensions | ✅ | +| `composer` executable | ✅ | +| `install-php-extensions` script | ✅ | +| Essential system utilities | ✅ | +| S6 Overlay (process supervisor) | ✅ | +| Native health checks | ✅ (via [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){target="_blank"} script) | +| Web server | ❌ (requires external web server) | +| Process management | Single entrypoint, single process | +| Exposed Ports | `9000` (FastCGI) | +| Stop Signal | `SIGQUIT` | + +## How FPM Works +Unlike variations that include a web server, the FPM variation only runs PHP-FPM, which listens on port 9000 for FastCGI requests. + +You'll need a separate web server (like NGINX, Apache, or Caddy) to: +1. Accept HTTP requests from clients +2. Serve static files directly (CSS, JavaScript, images) +3. Forward PHP requests to the FPM container on port 9000 +4. Return the PHP-FPM response back to the client + +This architecture gives you maximum flexibility but requires more configuration than the all-in-one variations. + +::note +If you want a simpler setup with everything in one container, consider using the `fpm-nginx`, `fpm-apache`, or `frankenphp` variations instead. These include both the web server and PHP-FPM in a single container. +:: + +## Quick Start +Here are a few examples to help you get started with the FPM variation. + +### Docker Compose with Separate NGINX +This example shows a common setup with PHP-FPM in one container and NGINX in another. + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm + volumes: + - ./:/var/www/html + + nginx: + image: nginx:alpine + ports: + - "80:80" + volumes: + - ./:/var/www/html + - ./nginx.conf:/etc/nginx/conf.d/default.conf + depends_on: + - php +``` + +And your NGINX configuration (`nginx.conf`): + +```nginx [nginx.conf] +server { + listen 80; + server_name localhost; + root /var/www/html/public; + + index index.php index.html; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass php:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.(?!well-known).* { + deny all; + } +} +``` + +::tip +Notice how the `fastcgi_pass` directive points to `php:9000`. This is the service name from your Docker Compose file. Docker's networking allows services to communicate using their service names. +:: + +### Kubernetes Example +The FPM variation is particularly well-suited for Kubernetes deployments where you might have separate containers in the same pod. + +```yml [deployment.yaml] +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-app +spec: + replicas: 3 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + volumeMounts: + - name: app-code + mountPath: /var/www/html + - name: nginx-config + mountPath: /etc/nginx/conf.d + + - name: php-fpm + image: serversideup/php:8.4-fpm + volumeMounts: + - name: app-code + mountPath: /var/www/html + + volumes: + - name: app-code + emptyDir: {} + - name: nginx-config + configMap: + name: nginx-config +``` + +### Health Check +The FPM variation includes [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){target="_blank"}, a POSIX-compliant script that monitors PHP-FPM's `/status` endpoint to verify the service is healthy. + +```yaml [compose.yml]{7-10} +services: + php: + image: serversideup/php:8.4-fpm + volumes: + - ./:/var/www/html + healthcheck: + test: ["CMD", "php-fpm-healthcheck"] + interval: 10s + timeout: 3s + retries: 3 +``` + +::tip +The `php-fpm-healthcheck` script can also monitor specific metrics like accepted connections or queue length. For example, you could fail the health check if the listen queue exceeds 10 processes: `php-fpm-healthcheck --listen-queue=10` +:: + +## Environment Variables +The FPM variation supports extensive customization through environment variables. Here are some common ones: + +| Variable | Default | Description | +|----------|---------|-------------| +| `PHP_FPM_POOL_NAME` | `www` | Name of the PHP-FPM pool | +| `PHP_FPM_PM_CONTROL` | `dynamic` | Process manager control (`dynamic`, `static`, `ondemand`) | +| `PHP_FPM_PM_MAX_CHILDREN` | `20` | Maximum number of child processes | +| `PHP_FPM_PM_START_SERVERS` | `2` | Number of child processes created on startup | +| `PHP_FPM_PM_MIN_SPARE_SERVERS` | `1` | Minimum number of idle processes | +| `PHP_FPM_PM_MAX_SPARE_SERVERS` | `3` | Maximum number of idle processes | +| `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | +| `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | + +::tip +For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). +:: + +## Performance Tuning +The FPM variation gives you fine-grained control over PHP process management. Here are some tuning tips: + +### For High-Traffic Applications +```yaml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm + environment: + PHP_FPM_PM_CONTROL: "static" + PHP_FPM_PM_MAX_CHILDREN: "50" + PHP_MEMORY_LIMIT: "512M" +``` + +### For Low-Memory Environments +```yaml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm + environment: + PHP_FPM_PM_CONTROL: "ondemand" + PHP_FPM_PM_MAX_CHILDREN: "10" + PHP_FPM_PM_PROCESS_IDLE_TIMEOUT: "10s" +``` + +## Further Customization +If you need to customize the container further, reference the docs below: + +- [Environment Variable Specification](/docs/reference/environment-variable-specification) - See which environment variables are available to customize PHP and PHP-FPM settings. +- [Command Reference](/docs/reference/command-reference) - See which commands are available to run inside the container. From f5fef7cce0d4a9c98ec1ce1ab88971b7096850c8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:24:21 -0500 Subject: [PATCH 198/304] Add FPM-Apache documentation for the PHP Docker image, detailing its features, use cases, and quick start examples, enhancing user understanding of the FPM-Apache variation and its integration with Apache web server. --- .../docs/2.image-variations/fpm-apache.md | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 docs/content/docs/2.image-variations/fpm-apache.md diff --git a/docs/content/docs/2.image-variations/fpm-apache.md b/docs/content/docs/2.image-variations/fpm-apache.md new file mode 100644 index 000000000..b2cfa4519 --- /dev/null +++ b/docs/content/docs/2.image-variations/fpm-apache.md @@ -0,0 +1,238 @@ +--- +title: FPM-Apache +description: 'Learn how to use the FPM-Apache variation of the serversideup/php image.' +--- + +::lead-p +The FPM-Apache variation combines PHP-FPM with Apache as a reverse proxy in a single container. Apache serves static content directly and forwards PHP requests to PHP-FPM for processing. + +Use this variation when you need Apache-specific features, `.htaccess` support, or want an all-in-one solution for running PHP applications. +:: + +## When to Use FPM-Apache +Use the FPM-Apache variation when you need to: + +- Run WordPress sites that rely on `.htaccess` configurations +- Use Apache-specific modules like `mod_rewrite` or `mod_security` +- Deploy applications that require `.htaccess` support +- Want an all-in-one container with both web server and PHP processing +- Need Apache's mature ecosystem and widespread documentation + +#### Perfect for +- WordPress hosting with Docker +- Legacy PHP applications that depend on Apache +- Teams familiar with Apache configuration +- Applications requiring `.htaccess` support + +#### What's Inside + +| Item | Status | +|------|--------| +| Apache web server | ✅ | +| PHP-FPM process manager | ✅ | +| PHP CLI binary | ✅ | +| Common PHP extensions | ✅ | +| `composer` executable | ✅ | +| `install-php-extensions` script | ✅ | +| Essential system utilities | ✅ | +| S6 Overlay (process supervisor) | ✅ | +| Native health checks | ✅ (via HTTP endpoint) | +| `.htaccess` support | ✅ | +| SSL/TLS support | ✅ (self-signed certificates or bring your own) | +| Process management | S6 Overlay supervising both Apache and PHP-FPM | +| Exposed Ports | `8080` (HTTP), `8443` (HTTPS) | +| Stop Signal | `SIGQUIT` | + +## How FPM-Apache Works +This variation runs both Apache and PHP-FPM in a single container, managed by S6 Overlay (for the most accurate process supervision). Here's how requests flow: + +::steps{level="4"} + +#### Client sends HTTP request +The container listens on port 8080 (or 8443 for HTTPS) for incoming HTTP requests. + +#### Apache receives the request +Apache receives the request and determines if it's a static file or PHP script. + +#### Check for static files +Static files (CSS, JavaScript, images) are served directly by Apache. + +#### Forward PHP requests to PHP-FPM +PHP requests are forwarded to PHP-FPM via FastCGI protocol. + +#### Process PHP requests with PHP-FPM +PHP-FPM processes the PHP script and returns the result to Apache. + +#### Send the response back to the client +Apache sends the response back to the client. + +:: + +S6 Overlay ensures both Apache and PHP-FPM are running and automatically restarts them if either process fails. + +::note +If you don't specifically need Apache, consider using the [`fpm-nginx`](/docs/image-variations/fpm-nginx) or [`frankenphp`](/docs/image-variations/frankenphp) variations instead. They offer better performance for modern PHP applications. +:: + +## Quick Start +Here are a few examples to help you get started with the FPM-Apache variation. + +### Docker CLI +```bash [Terminal] +docker run -p 80:8080 -v $(pwd):/var/www/html/public serversideup/php:8.4-fpm-apache +``` + +Your application will be available at `http://localhost`. The default document root is `/var/www/html/public`. + +### Docker Compose +::warning +Notice how we're mapping the current directory to `/var/www/html/`, but the actual default document root is `/var/www/html/public`. We're assuming you're creating the `public` directory and putting your PHP code in there. It's not best practice to expose your `compose.yml` file. See the [Installation guide](/docs/getting-started/installation) for a full example. +:: + +This is the recommended approach for local development and production deployments. + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-apache + ports: + - "80:8080" + volumes: + - ./:/var/www/html + environment: + PHP_OPCACHE_ENABLE: "1" +``` + +::tip +The FPM-Apache variation uses ports 8080 and 8443 (instead of 80 and 443) to allow the container to run as a non-root user for better security. +:: + +### WordPress Example +The FPM-Apache variation is excellent for WordPress hosting: + +```yml [compose.yml] +services: + wordpress: + image: serversideup/php:8.4-fpm-apache + ports: + - "8080:8080" + volumes: + - ./wordpress:/var/www/html/public + environment: + PHP_MEMORY_LIMIT: "512M" + PHP_OPCACHE_ENABLE: "1" + depends_on: + - mariadb + + mariadb: + image: mariadb:11 + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: wordpress + MYSQL_USER: wordpress + MYSQL_PASSWORD: wordpress + volumes: + - db_data:/var/lib/mysql + +volumes: + db_data: +``` + +### Health Check +The FPM-Apache variation includes a built-in health check that verifies Apache is responding: + +::note +The health check endpoint is configurable via the `HEALTHCHECK_PATH` environment variable, which defaults to `/healthcheck`. +:: + +## SSL/TLS Support +The FPM-Apache variation includes built-in SSL support with self-signed certificates for development. + +### Enabling SSL +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-apache + ports: + - "8080:8080" + - "8443:8443" + volumes: + - ./:/var/www/html + environment: + SSL_MODE: "full" +``` + +Available SSL modes: +- `off` - SSL disabled (default) +- `mixed` - Both HTTP (8080) and HTTPS (8443) enabled +- `full` - HTTPS only on port 8443 + +### Custom SSL Certificates +For production, use your own SSL certificates: + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-apache + ports: + - "443:8443" + volumes: + - ./:/var/www/html + - ./certs/server.crt:/etc/ssl/private/self-signed-web.crt:ro + - ./certs/server.key:/etc/ssl/private/self-signed-web.key:ro + environment: + SSL_MODE: "full" +``` + +::warning +For production deployments, consider using a reverse proxy like Traefik or Caddy to handle SSL termination instead of managing certificates in the container. +:: + +## Environment Variables +The FPM-Apache variation supports extensive customization through environment variables. + +### Apache Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `APACHE_DOCUMENT_ROOT` | `/var/www/html/public` | Document root for Apache | +| `APACHE_START_SERVERS` | `2` | Number of Apache server processes to start | +| `APACHE_MIN_SPARE_THREADS` | `10` | Minimum idle threads | +| `APACHE_MAX_SPARE_THREADS` | `75` | Maximum idle threads | +| `APACHE_THREADS_PER_CHILD` | `25` | Number of threads per child process | +| `APACHE_MAX_REQUEST_WORKERS` | `150` | Maximum simultaneous connections | +| `APACHE_MAX_CONNECTIONS_PER_CHILD` | `0` | Requests before child process restarts (0 = unlimited) | +| `SSL_MODE` | `off` | SSL mode: `off`, `mixed`, or `full` | +| `SSL_CERTIFICATE_FILE` | `/etc/ssl/private/self-signed-web.crt` | Path to SSL certificate | +| `SSL_PRIVATE_KEY_FILE` | `/etc/ssl/private/self-signed-web.key` | Path to SSL private key | +| `HEALTHCHECK_PATH` | `/healthcheck` | Path for health check endpoint | + +::tip +For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). +:: + +### PHP-FPM Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `PHP_FPM_POOL_NAME` | `www` | Name of the PHP-FPM pool | +| `PHP_FPM_PM_CONTROL` | `dynamic` | Process manager control (`dynamic`, `static`, `ondemand`) | +| `PHP_FPM_PM_MAX_CHILDREN` | `20` | Maximum number of child processes | +| `PHP_FPM_PM_START_SERVERS` | `2` | Number of child processes created on startup | +| `PHP_FPM_PM_MIN_SPARE_SERVERS` | `1` | Minimum number of idle processes | +| `PHP_FPM_PM_MAX_SPARE_SERVERS` | `3` | Maximum number of idle processes | +| `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | +| `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | +| `PHP_UPLOAD_MAX_FILE_SIZE` | `100M` | Maximum upload file size | +| `PHP_POST_MAX_SIZE` | `100M` | Maximum POST request size | + +::tip +For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). +:: + +## Further Customization +If you need to customize the container further, reference the docs below: + +- [Environment Variable Specification](/docs/reference/environment-variable-specification) - See which environment variables are available to customize PHP and Apache settings. +- [Command Reference](/docs/reference/command-reference) - See which commands are available to run inside the container. + From 89f82f905df63e3239987aaf6ae237d8b16e7293 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:55:56 -0500 Subject: [PATCH 199/304] Add custom NGINX configuration icon to app configuration, enhancing visual representation for users managing NGINX setups in Docker. --- docs/app/app.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/app/app.config.ts b/docs/app/app.config.ts index 488992efc..60977991b 100644 --- a/docs/app/app.config.ts +++ b/docs/app/app.config.ts @@ -21,6 +21,7 @@ export default defineAppConfig({ codeIcon: { 'compose.yml': 'i-services-docker', 'compose.yaml': 'i-services-docker', + 'custom-nginx.conf': 'i-services-nginx', 'deployment.yaml': 'i-services-kubernetes', 'deployment.yml': 'i-services-kubernetes', 'docker-compose.yaml': 'i-services-docker', From a40f28991f29db84117aa7807a27b0ba4e34d60d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:56:01 -0500 Subject: [PATCH 200/304] Enhance installation documentation for PHP Docker images by adding detailed steps for starting, stopping, and upgrading the PHP container, including instructions for modifying the `compose.yml` file and refreshing the browser to view changes. --- .../docs/1.getting-started/3.installation.md | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/content/docs/1.getting-started/3.installation.md b/docs/content/docs/1.getting-started/3.installation.md index 0d77596d5..33da2321d 100644 --- a/docs/content/docs/1.getting-started/3.installation.md +++ b/docs/content/docs/1.getting-started/3.installation.md @@ -102,15 +102,17 @@ From your project root directory, run the following command to start your PHP ap Make sure you are running the following commands from your **project root directory** (`my-php-project`). If you do not have `compose.yml` in the same directory as you run this command, the command will not work. Also, make sure you don't have any other containers or services that are currently running on port 80. If you do, you will need to stop them before running the following command. :: +::steps{level="4"} + +#### Start the PHP container ```bash [Terminal] docker compose up ``` - You'll see the logs appear in your terminal. **Keep your terminal open** as we'll need it to control the container. ![Terminal output showing Docker Compose successfully starting a PHP container with nginx logs](images/docs/running-php-container.png) -### Viewing your PHP app +#### Viewing your PHP app To view your PHP app, open your browser and navigate to `http://localhost`. You should see the PHP info page showing PHP `8.3` with the `fpm-nginx` variation: ![PHP Info Page](images/docs/php-info.png) @@ -122,21 +124,33 @@ Look for these values in the PHP info page: - `upload_max_filesize` - This should show `250M` - `opcache.enable` - This should show `Off` -To make changes to these settings, we must bring the containers down first before making the changes. +:: -### Stopping your application -To stop the container, press :kbd{value="ctrl"} + :kbd{value="C"} on the original terminal window or you can run the following command from your project root directory: -```bash [Terminal] -docker compose down -``` +### Making changes +::caution +To have our changes take effect, we must restart the containers. +:: + +With our container running, let's make some upgrades to our PHP app: -### Making changes to your PHP app -Let's do something a bit more interesting: 1. Let's upgrade to PHP 8.4 1. Let's use FrankenPHP instead of FPM-NGINX 2. Turn on OPCache 3. Increase the upload limit to 500M +To do this, we need to need to: + +::steps{level="4"} + +#### Stop the container +**Press :kbd{value="ctrl"} + :kbd{value="C"} on the original terminal window** or you can run the following command *in a new terminal window from your project root directory*: +```bash [Terminal] +docker compose down +``` + +#### Making changes to your PHP app +Make the updates below to your `compose.yml` file: + ```yml [compose.yml]{3-4,10-13} services: php: @@ -153,11 +167,13 @@ services: PHP_OPCACHE_ENABLE: "1" ``` -Now let's bring the containers back up again: - +#### Bring the container up again ```bash [Terminal] docker compose up ``` +You'll see the logs appear in your terminal. **Keep your terminal open** as we'll need it to control the container. + +#### Refresh your browser Check `http://localhost` again and you should see the changes we made: @@ -167,6 +183,8 @@ Holy smokes! We've upgraded to PHP 8.4 and you're using FrankenPHP! You can also ![PHP Info Page with Changes to server options](images/docs/php-info-with-changes-options.png) +:: + ## You've got this 💪 You've successfully created your first PHP app with Docker. Better yet, you've seen the power of serversideup/php where it's easy to change your PHP version and variation by changing a single line in your configuration file. From 893671d2abfd58a2a2f603c86aa3e9b8da2c4a92 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 14:56:45 -0500 Subject: [PATCH 201/304] Refine installation documentation for PHP Docker images by removing unnecessary phrasing and clarifying the steps for upgrading the PHP application, enhancing readability and user experience. --- docs/content/docs/1.getting-started/3.installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/1.getting-started/3.installation.md b/docs/content/docs/1.getting-started/3.installation.md index 33da2321d..9b9c8282b 100644 --- a/docs/content/docs/1.getting-started/3.installation.md +++ b/docs/content/docs/1.getting-started/3.installation.md @@ -131,7 +131,7 @@ Look for these values in the PHP info page: To have our changes take effect, we must restart the containers. :: -With our container running, let's make some upgrades to our PHP app: +Let's make some upgrades to our PHP app: 1. Let's upgrade to PHP 8.4 1. Let's use FrankenPHP instead of FPM-NGINX From e43e6b3087a20d27153618d8289b50746b21dd6c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:08:39 -0500 Subject: [PATCH 202/304] Add navigation configuration files for Laravel, WordPress, Platforms, Advanced Guides, and Customization sections, improving documentation structure and accessibility for users. --- docs/content/docs/3.framework-guides/1.laravel/.navigation.yml | 3 +++ .../docs/3.framework-guides/2.wordpress/.navigation.yml | 3 +++ .../4.deployment-and-production/99.platforms/.navigation.yml | 3 +++ docs/content/docs/5.guides/.navigation.yml | 3 +++ docs/content/docs/6.customizing-the-image/.navigation.yml | 3 +++ 5 files changed, 15 insertions(+) create mode 100644 docs/content/docs/3.framework-guides/1.laravel/.navigation.yml create mode 100644 docs/content/docs/3.framework-guides/2.wordpress/.navigation.yml create mode 100644 docs/content/docs/4.deployment-and-production/99.platforms/.navigation.yml create mode 100644 docs/content/docs/5.guides/.navigation.yml create mode 100644 docs/content/docs/6.customizing-the-image/.navigation.yml diff --git a/docs/content/docs/3.framework-guides/1.laravel/.navigation.yml b/docs/content/docs/3.framework-guides/1.laravel/.navigation.yml new file mode 100644 index 000000000..2cbfff2b5 --- /dev/null +++ b/docs/content/docs/3.framework-guides/1.laravel/.navigation.yml @@ -0,0 +1,3 @@ +title: Laravel +icon: false +defaultOpen: false \ No newline at end of file diff --git a/docs/content/docs/3.framework-guides/2.wordpress/.navigation.yml b/docs/content/docs/3.framework-guides/2.wordpress/.navigation.yml new file mode 100644 index 000000000..4de484196 --- /dev/null +++ b/docs/content/docs/3.framework-guides/2.wordpress/.navigation.yml @@ -0,0 +1,3 @@ +title: WordPress +icon: false +defaultOpen: false \ No newline at end of file diff --git a/docs/content/docs/4.deployment-and-production/99.platforms/.navigation.yml b/docs/content/docs/4.deployment-and-production/99.platforms/.navigation.yml new file mode 100644 index 000000000..85283371c --- /dev/null +++ b/docs/content/docs/4.deployment-and-production/99.platforms/.navigation.yml @@ -0,0 +1,3 @@ +title: Platforms +icon: false +defaultOpen: false \ No newline at end of file diff --git a/docs/content/docs/5.guides/.navigation.yml b/docs/content/docs/5.guides/.navigation.yml new file mode 100644 index 000000000..a5d442aa1 --- /dev/null +++ b/docs/content/docs/5.guides/.navigation.yml @@ -0,0 +1,3 @@ +title: Advanced Guides +icon: false +defaultOpen: false \ No newline at end of file diff --git a/docs/content/docs/6.customizing-the-image/.navigation.yml b/docs/content/docs/6.customizing-the-image/.navigation.yml new file mode 100644 index 000000000..a2603f1c3 --- /dev/null +++ b/docs/content/docs/6.customizing-the-image/.navigation.yml @@ -0,0 +1,3 @@ +title: Customization +icon: false +defaultOpen: false \ No newline at end of file From 281b058bc168b10e4c5b1f53dc49ee0f6317c435 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:08:46 -0500 Subject: [PATCH 203/304] Add documentation for NGINX Unit variation, noting its archival status and recommending migration to FrankenPHP, enhancing user awareness of unsupported features. --- docs/content/docs/2.image-variations/unit.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docs/content/docs/2.image-variations/unit.md diff --git a/docs/content/docs/2.image-variations/unit.md b/docs/content/docs/2.image-variations/unit.md new file mode 100644 index 000000000..20c0917bd --- /dev/null +++ b/docs/content/docs/2.image-variations/unit.md @@ -0,0 +1,14 @@ +--- +title: Unit +description: 'Unit was offered as a variation, but the entire project was archived in October 2025. This variation is no longer supported.' +--- + +## Unit was archived by NGINX and is no longer maintained +::caution +In October 2025, NGINX stopped supporting NGINX Unit and archived the project. NGINX Unit will eventually be removed from our project. [See the official announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit){target="_blank"} +:: + +## Consider migrating to FrankenPHP +If you are using the Unit variation, consider migrating to FrankenPHP for a single-process alternative. + +:u-button{to="/docs/image-variations/frankenphp" label="Learn more about the FrankenPHP variation" aria-label="Learn more about the FrankenPHP variation" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From 2578bfb6829a1f06cd543735063adcd836809e53 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:08:58 -0500 Subject: [PATCH 204/304] Add container basics documentation, introducing key concepts of containerization, benefits, and getting started with Docker, enhancing user understanding of deploying PHP applications in containers. --- .../{1.container-concepts.md => 1.container-basics.md} | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename docs/content/docs/4.deployment-and-production/{1.container-concepts.md => 1.container-basics.md} (93%) diff --git a/docs/content/docs/4.deployment-and-production/1.container-concepts.md b/docs/content/docs/4.deployment-and-production/1.container-basics.md similarity index 93% rename from docs/content/docs/4.deployment-and-production/1.container-concepts.md rename to docs/content/docs/4.deployment-and-production/1.container-basics.md index 5ca16e2bc..a7c72d0cb 100644 --- a/docs/content/docs/4.deployment-and-production/1.container-concepts.md +++ b/docs/content/docs/4.deployment-and-production/1.container-basics.md @@ -1,5 +1,5 @@ --- -title: Container Concepts +title: Container Basics description: 'Learn the basics of containers with PHP and how to use it to deploy your PHP applications.' --- @@ -45,5 +45,4 @@ These images work with any Docker setup — Docker Compose, Docker Swarm, Kubern Spin is free and open source, and it's a great way to get started with Docker if you're looking for a free method to deploy to any VPS provider. -::u-button{to="https://serversideup.net/open-source/spin/" label="Learn more about Spin" aria-label="Learn more about Spin" target="_blank" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} -:: \ No newline at end of file +:u-button{to="https://serversideup.net/open-source/spin/" label="Learn more about Spin" aria-label="Learn more about Spin" target="_blank" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From ad01f07ee8fbbab2921d446ee6cc005b424a2f97 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:13:10 -0500 Subject: [PATCH 205/304] Add navigation entry for Orchestrators in deployment documentation, improving structure and accessibility for users exploring orchestration options in PHP Docker environments. --- .../98.orchestrators/.navigation.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/content/docs/4.deployment-and-production/98.orchestrators/.navigation.yml diff --git a/docs/content/docs/4.deployment-and-production/98.orchestrators/.navigation.yml b/docs/content/docs/4.deployment-and-production/98.orchestrators/.navigation.yml new file mode 100644 index 000000000..13126c183 --- /dev/null +++ b/docs/content/docs/4.deployment-and-production/98.orchestrators/.navigation.yml @@ -0,0 +1,3 @@ +title: Orchestrators +icon: false +defaultOpen: false \ No newline at end of file From a870c2e535afd97fa2c98e4905b0c684a0ffbcd2 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:33:52 -0500 Subject: [PATCH 206/304] Update FPM-Apache documentation to include SSL configuration and additional port mappings, enhancing security and accessibility for users deploying PHP applications with HTTPS support. --- docs/content/docs/2.image-variations/fpm-apache.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/2.image-variations/fpm-apache.md b/docs/content/docs/2.image-variations/fpm-apache.md index b2cfa4519..9ed604cf9 100644 --- a/docs/content/docs/2.image-variations/fpm-apache.md +++ b/docs/content/docs/2.image-variations/fpm-apache.md @@ -97,10 +97,12 @@ services: image: serversideup/php:8.4-fpm-apache ports: - "80:8080" + - "443:8443" volumes: - ./:/var/www/html environment: PHP_OPCACHE_ENABLE: "1" + SSL_MODE: "full" ``` ::tip @@ -115,10 +117,12 @@ services: wordpress: image: serversideup/php:8.4-fpm-apache ports: - - "8080:8080" + - "80:8080" + - "443:8443" volumes: - - ./wordpress:/var/www/html/public + - ./wordpress:/var/www/html environment: + SSL_MODE: "full" PHP_MEMORY_LIMIT: "512M" PHP_OPCACHE_ENABLE: "1" depends_on: From f15e0b8a52853eecb7a5f6557bfa13cd820af52b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:34:11 -0500 Subject: [PATCH 207/304] Add new SVG and PNG images for documentation, including container initialization, Docker layers, permissions, reverse proxy, S6 overlay, and supervisor container visuals, enhancing the visual representation and understanding of containerization concepts in PHP Docker environments. --- docs/public/images/docs/container-init.svg | 79 +++++++++++ docs/public/images/docs/docker-layers.png | Bin 0 -> 76743 bytes .../images/docs/permissions-privileged.png | Bin 0 -> 84041 bytes docs/public/images/docs/reverse-proxy.svg | 126 ++++++++++++++++++ .../images/docs/s6-overlay-container.svg | 72 ++++++++++ .../images/docs/supervisor-container.svg | 73 ++++++++++ 6 files changed, 350 insertions(+) create mode 100644 docs/public/images/docs/container-init.svg create mode 100644 docs/public/images/docs/docker-layers.png create mode 100644 docs/public/images/docs/permissions-privileged.png create mode 100644 docs/public/images/docs/reverse-proxy.svg create mode 100644 docs/public/images/docs/s6-overlay-container.svg create mode 100644 docs/public/images/docs/supervisor-container.svg diff --git a/docs/public/images/docs/container-init.svg b/docs/public/images/docs/container-init.svg new file mode 100644 index 000000000..ef5dd7e48 --- /dev/null +++ b/docs/public/images/docs/container-init.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/docs/docker-layers.png b/docs/public/images/docs/docker-layers.png new file mode 100644 index 0000000000000000000000000000000000000000..76526583fe991934700bb9e870bf9919d73eafe4 GIT binary patch literal 76743 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
+
+
+ + + + + + + + + + +
+ + +
+
+ + + \ No newline at end of file diff --git a/docs/app/pages/[...slug].vue b/docs/app/pages/[...slug].vue index bf4e7efa2..d4937684f 100644 --- a/docs/app/pages/[...slug].vue +++ b/docs/app/pages/[...slug].vue @@ -77,6 +77,10 @@ if (!page.value) { throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true }) } +if (page.value?.redirect) { + navigateTo(page.value?.redirect) +} + const { data: surround } = await useAsyncData(`${route.path}-surround`, () => { return queryCollectionItemSurroundings('docs', route.path, { fields: ['description'] diff --git a/docs/content.config.ts b/docs/content.config.ts index 84bf3ef1f..e81cfdf98 100644 --- a/docs/content.config.ts +++ b/docs/content.config.ts @@ -13,6 +13,7 @@ export default defineContentConfig({ exclude: ['index.md'] }, schema: z.object({ + redirect: z.string().optional(), links: z.array(z.object({ label: z.string(), icon: z.string(), diff --git a/docs/content/docs/index.md b/docs/content/docs/index.md new file mode 100644 index 000000000..0c3517392 --- /dev/null +++ b/docs/content/docs/index.md @@ -0,0 +1,3 @@ +--- +redirect: /docs/getting-started +--- \ No newline at end of file diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index d24c20172..55cf77941 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -7,6 +7,7 @@ export default defineNuxtConfig({ '@nuxt/content', '@nuxtjs/plausible', '@nuxtjs/sitemap', + '@vueuse/nuxt', 'nuxt-og-image', 'nuxt-llms', 'nuxt-schema-org' diff --git a/docs/package.json b/docs/package.json index c9f93d2b2..fbfe41629 100644 --- a/docs/package.json +++ b/docs/package.json @@ -20,6 +20,8 @@ "@nuxt/ui": "^4.0.1", "@nuxtjs/plausible": "^2.0.1", "@nuxtjs/sitemap": "^7.4.7", + "@vueuse/core": "^14.0.0", + "@vueuse/nuxt": "^14.0.0", "better-sqlite3": "^12.4.1", "nuxt": "^4.1.3", "nuxt-llms": "0.1.3", diff --git a/docs/public/images/logos/server-side-up-logo-horizontal.svg b/docs/public/images/logos/server-side-up-logo-horizontal.svg new file mode 100644 index 000000000..93198355b --- /dev/null +++ b/docs/public/images/logos/server-side-up-logo-horizontal.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/yarn.lock b/docs/yarn.lock index a735c398c..add35eede 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -335,17 +335,17 @@ mime "^3.0.0" "@emnapi/core@^1.4.3", "@emnapi/core@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0" - integrity sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg== + version "1.6.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.6.0.tgz#517f65d1c8270d5d5aa1aad660d5acb897430dca" + integrity sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg== dependencies: "@emnapi/wasi-threads" "1.1.0" tslib "^2.4.0" "@emnapi/runtime@^1.4.3", "@emnapi/runtime@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.5.0.tgz#9aebfcb9b17195dce3ab53c86787a6b7d058db73" - integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ== + version "1.6.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.6.0.tgz#8fe297e0090f6e89a57a1f31f1c440bdbc3c01d8" + integrity sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA== dependencies: tslib "^2.4.0" @@ -505,9 +505,9 @@ eslint-visitor-keys "^3.4.3" "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.11.0", "@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.8.0": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== "@eslint/compat@^1.2.5": version "1.4.0" @@ -516,19 +516,19 @@ dependencies: "@eslint/core" "^0.16.0" -"@eslint/config-array@^0.21.0": - version "0.21.0" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" - integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== +"@eslint/config-array@^0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" + integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== dependencies: - "@eslint/object-schema" "^2.1.6" + "@eslint/object-schema" "^2.1.7" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/config-helpers@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.0.tgz#e9f94ba3b5b875e32205cb83fece18e64486e9e6" - integrity sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog== +"@eslint/config-helpers@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.1.tgz#7d173a1a35fe256f0989a0fdd8d911ebbbf50037" + integrity sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw== dependencies: "@eslint/core" "^0.16.0" @@ -582,15 +582,15 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.37.0", "@eslint/js@^9.33.0": - version "9.37.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.37.0.tgz#0cfd5aa763fe5d1ee60bedf84cd14f54bcf9e21b" - integrity sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg== +"@eslint/js@9.38.0", "@eslint/js@^9.33.0": + version "9.38.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.38.0.tgz#f7aa9c7577577f53302c1d795643589d7709ebd1" + integrity sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A== -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== +"@eslint/object-schema@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== "@eslint/plugin-kit@^0.3.3": version "0.3.5" @@ -666,16 +666,16 @@ integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== "@iconify-json/lucide@^1.2.69": - version "1.2.69" - resolved "https://registry.yarnpkg.com/@iconify-json/lucide/-/lucide-1.2.69.tgz#c7994a5de2c2baa9166bc1c81e2d295db75df129" - integrity sha512-xOhNf74m+C+nSCObfEqYi34dXk1GMfMUcOB+gfqKY/bn0RcsPLinGfgouOvrUFEreDEFbCti7sdheTf5HESLTA== + version "1.2.70" + resolved "https://registry.yarnpkg.com/@iconify-json/lucide/-/lucide-1.2.70.tgz#4d89e1dc37f8aec808b9dcc37fd8ff3d4f1cb9ec" + integrity sha512-56s9NdBKgshywVY1e4gOcxzAbU1J649e/jLHBJU1tyNqRs7mFLVEGwj2mmzHJ5YAZB5Tsngi4f/ocTBPlG06ZA== dependencies: "@iconify/types" "*" "@iconify-json/simple-icons@^1.2.54": - version "1.2.54" - resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.54.tgz#b72bb1c36c03634bd31a8f33666b2237d40b36ad" - integrity sha512-OQQYl8yC5j3QklZOYnK31QYe5h47IhyCoxSLd53f0e0nA4dgi8VOZS30SgSAbsecQ+S0xlGJMjXIHTIqZ+ML3w== + version "1.2.55" + resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.2.55.tgz#eb55f409e76f62f4502504a3e182d04fd8f0c507" + integrity sha512-9vc04pmup/zcef8hDypWU8nMwMaFVkWuUzWkxyL++DVp5AA8baoJHK6RyKN1v+cvfR2agxkUb053XVggzFFkTA== dependencies: "@iconify/types" "*" @@ -686,10 +686,10 @@ dependencies: "@iconify/types" "*" -"@iconify/collections@^1.0.579": - version "1.0.606" - resolved "https://registry.yarnpkg.com/@iconify/collections/-/collections-1.0.606.tgz#f6f4c5d9a521d04337285cb324d1b122a07d789f" - integrity sha512-PQiFoOJpOpd3ulwmhKdtxCaeRVWp2d5jeHR2TUMuyyEizCqCmIWppWL/dGbDcbWPPLx7sovXzhLIrGihWNzBdQ== +"@iconify/collections@^1.0.608": + version "1.0.608" + resolved "https://registry.yarnpkg.com/@iconify/collections/-/collections-1.0.608.tgz#ed35264ae81b1834374e29bd5eb3181dde9b4896" + integrity sha512-uMbaErE6TzDb04peWVFYjc9cweBD+j1nFBHi5EEcA1u1mXJAyePF01VzH6dimurrhivvU+nRmuYfiC8GPDyG6g== dependencies: "@iconify/types" "*" @@ -698,7 +698,7 @@ resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57" integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== -"@iconify/utils@^3.0.0": +"@iconify/utils@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@iconify/utils/-/utils-3.0.2.tgz#9599607f20690cd3e7a5d2d459af0eb81a89dc2b" integrity sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ== @@ -850,7 +850,7 @@ "@emnapi/runtime" "^1.4.3" "@tybys/wasm-util" "^0.10.0" -"@napi-rs/wasm-runtime@^1.0.5", "@napi-rs/wasm-runtime@^1.0.6": +"@napi-rs/wasm-runtime@^1.0.6", "@napi-rs/wasm-runtime@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz#dcfea99a75f06209a235f3d941e3460a51e9b14c" integrity sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw== @@ -1133,24 +1133,24 @@ unstorage "^1.16.0" "@nuxt/icon@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@nuxt/icon/-/icon-2.0.0.tgz#b31a9a474ea41867bb56bd3d91ccc217fb58e7be" - integrity sha512-sy8+zkKMYp+H09S0cuTteL3zPTmktqzYPpPXV9ZkLNjrQsaPH08n7s/9wjr+C/K/w2R3u18E3+P1VIQi3xaq1A== + version "2.1.0" + resolved "https://registry.yarnpkg.com/@nuxt/icon/-/icon-2.1.0.tgz#067ad1fc7847189c4983f987a4637dbdab45816d" + integrity sha512-m+XQrgzeK5gQ1HkB7G7u1os6egoD07fiHKijG7NPxqT5yZUGOjKJ7X/Le10l3QWRKyCB+IiU0t+eUqSvh+SULg== dependencies: - "@iconify/collections" "^1.0.579" + "@iconify/collections" "^1.0.608" "@iconify/types" "^2.0.0" - "@iconify/utils" "^3.0.0" + "@iconify/utils" "^3.0.2" "@iconify/vue" "^5.0.0" - "@nuxt/devtools-kit" "^2.6.2" - "@nuxt/kit" "^4.0.3" + "@nuxt/devtools-kit" "^2.6.5" + "@nuxt/kit" "^4.1.3" consola "^3.4.2" - local-pkg "^1.1.1" - mlly "^1.7.4" + local-pkg "^1.1.2" + mlly "^1.8.0" ohash "^2.0.11" pathe "^2.0.3" picomatch "^4.0.3" - std-env "^3.9.0" - tinyglobby "^0.2.14" + std-env "^3.10.0" + tinyglobby "^0.2.15" "@nuxt/image@^1.11.0": version "1.11.0" @@ -1894,9 +1894,9 @@ integrity sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q== "@rolldown/pluginutils@^1.0.0-beta.34": - version "1.0.0-beta.43" - resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz#fa8249860113711ad3c8053bc79cb07c79b77f62" - integrity sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ== + version "1.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.44.tgz#ac8dcf86c50b177f7849a0a21df7e78b08eda2dc" + integrity sha512-g6eW7Zwnr2c5RADIoqziHoVs6b3W5QTQ4+qbpfjbkMJ9x+8Og211VW/oot2dj9dVwaK/UyC6Yo+02gV+wWQVNg== "@rollup/plugin-alias@^5.1.1": version "5.1.1" @@ -1969,115 +1969,115 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz#59e7478d310f7e6a7c72453978f562483828112f" - integrity sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA== - -"@rollup/rollup-android-arm64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz#a825192a0b1b2f27a5c950c439e7e37a33c5d056" - integrity sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w== - -"@rollup/rollup-darwin-arm64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz#4ee37078bccd725ae3c5f30ef92efc8e1bf886f3" - integrity sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg== - -"@rollup/rollup-darwin-x64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz#43cc08bd05bf9f388f125e7210a544e62d368d90" - integrity sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw== - -"@rollup/rollup-freebsd-arm64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz#bc8e640e28abe52450baf3fc80d9b26d9bb6587d" - integrity sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ== - -"@rollup/rollup-freebsd-x64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz#e981a22e057cc8c65bb523019d344d3a66b15bbc" - integrity sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw== - -"@rollup/rollup-linux-arm-gnueabihf@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz#4036b68904f392a20f3499d63b33e055b67eb274" - integrity sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ== - -"@rollup/rollup-linux-arm-musleabihf@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz#d3b1b9589606e0ff916801c855b1ace9e733427a" - integrity sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q== - -"@rollup/rollup-linux-arm64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz#cbf0943c477e3b96340136dd3448eaf144378cf2" - integrity sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg== - -"@rollup/rollup-linux-arm64-musl@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz#837f5a428020d5dce1c3b4cc049876075402cf78" - integrity sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g== - -"@rollup/rollup-linux-loong64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz#532c214ababb32ab4bc21b4054278b9a8979e516" - integrity sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ== - -"@rollup/rollup-linux-ppc64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz#93900163b61b49cee666d10ee38257a8b1dd161a" - integrity sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g== - -"@rollup/rollup-linux-riscv64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz#f0ffdcc7066ca04bc972370c74289f35c7a7dc42" - integrity sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg== - -"@rollup/rollup-linux-riscv64-musl@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz#361695c39dbe96773509745d77a870a32a9f8e48" - integrity sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA== - -"@rollup/rollup-linux-s390x-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz#09fc6cc2e266a2324e366486ae5d1bca48c43a6a" - integrity sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA== - -"@rollup/rollup-linux-x64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz#aa9d5b307c08f05d3454225bb0a2b4cc87eeb2e1" - integrity sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg== - -"@rollup/rollup-linux-x64-musl@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz#26949e5b4645502a61daba2f7a8416bd17cb5382" - integrity sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw== - -"@rollup/rollup-openharmony-arm64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz#ef493c072f9dac7e0edb6c72d63366846b6ffcd9" - integrity sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA== - -"@rollup/rollup-win32-arm64-msvc@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz#56e1aaa6a630d2202ee7ec0adddd05cf384ffd44" - integrity sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ== - -"@rollup/rollup-win32-ia32-msvc@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz#0a44bbf933a9651c7da2b8569fa448dec0de7480" - integrity sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw== - -"@rollup/rollup-win32-x64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz#730e12f0b60b234a7c02d5d3179ca3ec7972033d" - integrity sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ== - -"@rollup/rollup-win32-x64-msvc@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz#5b2dd648a960b8fa00d76f2cc4eea2f03daa80f4" - integrity sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w== +"@rollup/rollup-android-arm-eabi@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz#0f44a2f8668ed87b040b6fe659358ac9239da4db" + integrity sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ== + +"@rollup/rollup-android-arm64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz#25b9a01deef6518a948431564c987bcb205274f5" + integrity sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA== + +"@rollup/rollup-darwin-arm64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz#8a102869c88f3780c7d5e6776afd3f19084ecd7f" + integrity sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA== + +"@rollup/rollup-darwin-x64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz#8e526417cd6f54daf1d0c04cf361160216581956" + integrity sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA== + +"@rollup/rollup-freebsd-arm64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz#0e7027054493f3409b1f219a3eac5efd128ef899" + integrity sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA== + +"@rollup/rollup-freebsd-x64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz#72b204a920139e9ec3d331bd9cfd9a0c248ccb10" + integrity sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz#ab1b522ebe5b7e06c99504cc38f6cd8b808ba41c" + integrity sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ== + +"@rollup/rollup-linux-arm-musleabihf@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz#f8cc30b638f1ee7e3d18eac24af47ea29d9beb00" + integrity sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ== + +"@rollup/rollup-linux-arm64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz#7af37a9e85f25db59dc8214172907b7e146c12cc" + integrity sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg== + +"@rollup/rollup-linux-arm64-musl@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz#a623eb0d3617c03b7a73716eb85c6e37b776f7e0" + integrity sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q== + +"@rollup/rollup-linux-loong64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz#76ea038b549c5c6c5f0d062942627c4066642ee2" + integrity sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA== + +"@rollup/rollup-linux-ppc64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz#d9a4c3f0a3492bc78f6fdfe8131ac61c7359ccd5" + integrity sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw== + +"@rollup/rollup-linux-riscv64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz#87ab033eebd1a9a1dd7b60509f6333ec1f82d994" + integrity sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw== + +"@rollup/rollup-linux-riscv64-musl@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz#bda3eb67e1c993c1ba12bc9c2f694e7703958d9f" + integrity sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg== + +"@rollup/rollup-linux-s390x-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz#f7bc10fbe096ab44694233dc42a2291ed5453d4b" + integrity sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ== + +"@rollup/rollup-linux-x64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz#a151cb1234cc9b2cf5e8cfc02aa91436b8f9e278" + integrity sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q== + +"@rollup/rollup-linux-x64-musl@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz#7859e196501cc3b3062d45d2776cfb4d2f3a9350" + integrity sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg== + +"@rollup/rollup-openharmony-arm64@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz#85d0df7233734df31e547c1e647d2a5300b3bf30" + integrity sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw== + +"@rollup/rollup-win32-arm64-msvc@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz#e62357d00458db17277b88adbf690bb855cac937" + integrity sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w== + +"@rollup/rollup-win32-ia32-msvc@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz#fc7cd40f44834a703c1f1c3fe8bcc27ce476cd50" + integrity sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg== + +"@rollup/rollup-win32-x64-gnu@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz#1a22acfc93c64a64a48c42672e857ee51774d0d3" + integrity sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ== + +"@rollup/rollup-win32-x64-msvc@4.52.5": + version "4.52.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz#1657f56326bbe0ac80eedc9f9c18fc1ddd24e107" + integrity sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg== "@sec-ant/readable-stream@^0.4.1": version "0.4.1" @@ -2190,12 +2190,12 @@ integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== "@stylistic/eslint-plugin@^5.2.3": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-5.4.0.tgz#4cd51beb5602a8978a9a956c3568180efffcabe5" - integrity sha512-UG8hdElzuBDzIbjG1QDwnYH0MQ73YLXDFHgZzB4Zh/YJfnw8XNsloVtytqzx0I2Qky9THSdpTmi8Vjn/pf/Lew== + version "5.5.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-5.5.0.tgz#44c2e5454ff827f962b1908f0b5939130a6b18b3" + integrity sha512-IeZF+8H0ns6prg4VrkhgL+yrvDXWDH2cKchrbh80ejG9dQgZWp10epHMbgRuQvgchLII/lfh6Xn3lu6+6L86Hw== dependencies: "@eslint-community/eslint-utils" "^4.9.0" - "@typescript-eslint/types" "^8.44.0" + "@typescript-eslint/types" "^8.46.1" eslint-visitor-keys "^4.2.1" espree "^10.4.0" estraverse "^5.3.0" @@ -2208,126 +2208,123 @@ dependencies: tslib "^2.8.0" -"@tailwindcss/node@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.1.14.tgz#cf3864490c746db6b06b46aa235df9021a289bad" - integrity sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw== +"@tailwindcss/node@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.1.15.tgz#7008f30cd35d67352690ead8f59389455f8003c5" + integrity sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw== dependencies: "@jridgewell/remapping" "^2.3.4" enhanced-resolve "^5.18.3" jiti "^2.6.0" - lightningcss "1.30.1" + lightningcss "1.30.2" magic-string "^0.30.19" source-map-js "^1.2.1" - tailwindcss "4.1.14" - -"@tailwindcss/oxide-android-arm64@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.14.tgz#8903678d75715d913b8f7c5f6fa0517af83b5111" - integrity sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ== - -"@tailwindcss/oxide-darwin-arm64@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.14.tgz#72d56afadce829047a83d8512f29ee16cf6fbea5" - integrity sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA== - -"@tailwindcss/oxide-darwin-x64@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.14.tgz#ac1af82da01299143129fdf615f6fcc046b4094e" - integrity sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw== - -"@tailwindcss/oxide-freebsd-x64@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.14.tgz#a955cedf9b020147d222f92490e9d331db9b5c36" - integrity sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw== - -"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.14.tgz#5474bee4d377144107f3f0198a3c0225a46c02e6" - integrity sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw== - -"@tailwindcss/oxide-linux-arm64-gnu@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.14.tgz#b06ca140083b353735414e32f7a8786f55ce2dd6" - integrity sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w== - -"@tailwindcss/oxide-linux-arm64-musl@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.14.tgz#85f4cabea2a07609274d1f747bd098c5da2a7cd2" - integrity sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ== - -"@tailwindcss/oxide-linux-x64-gnu@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.14.tgz#0d7fbf91763a2f6886044a050298489107d120bd" - integrity sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg== - -"@tailwindcss/oxide-linux-x64-musl@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.14.tgz#93578713064ba4c16df517df01b3c546ecc9878d" - integrity sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q== - -"@tailwindcss/oxide-wasm32-wasi@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.14.tgz#9e55999129a952a3dcc2196cc9cc55248cc1b1fe" - integrity sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ== + tailwindcss "4.1.15" + +"@tailwindcss/oxide-android-arm64@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.15.tgz#ea5a49655203214bacf3af5b21373457c34d1462" + integrity sha512-TkUkUgAw8At4cBjCeVCRMc/guVLKOU1D+sBPrHt5uVcGhlbVKxrCaCW9OKUIBv1oWkjh4GbunD/u/Mf0ql6kEA== + +"@tailwindcss/oxide-darwin-arm64@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.15.tgz#6eed73058cef8cb3f0a3f984700d93049dd1549f" + integrity sha512-xt5XEJpn2piMSfvd1UFN6jrWXyaKCwikP4Pidcf+yfHTSzSpYhG3dcMktjNkQO3JiLCp+0bG0HoWGvz97K162w== + +"@tailwindcss/oxide-darwin-x64@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.15.tgz#1eba5369a071d3b06488be8c12dbb94029e69389" + integrity sha512-TnWaxP6Bx2CojZEXAV2M01Yl13nYPpp0EtGpUrY+LMciKfIXiLL2r/SiSRpagE5Fp2gX+rflp/Os1VJDAyqymg== + +"@tailwindcss/oxide-freebsd-x64@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.15.tgz#d7395ae1a6d0adcaf313ef6f94e771dd64e11b77" + integrity sha512-quISQDWqiB6Cqhjc3iWptXVZHNVENsWoI77L1qgGEHNIdLDLFnw3/AfY7DidAiiCIkGX/MjIdB3bbBZR/G2aJg== + +"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.15.tgz#7b296f228fb9bda92ec70d6607927a3eda8b9550" + integrity sha512-ObG76+vPlab65xzVUQbExmDU9FIeYLQ5k2LrQdR2Ud6hboR+ZobXpDoKEYXf/uOezOfIYmy2Ta3w0ejkTg9yxg== + +"@tailwindcss/oxide-linux-arm64-gnu@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.15.tgz#959afeee72c55a39e80f32a100e9a3ddbf73295c" + integrity sha512-4WbBacRmk43pkb8/xts3wnOZMDKsPFyEH/oisCm2q3aLZND25ufvJKcDUpAu0cS+CBOL05dYa8D4U5OWECuH/Q== + +"@tailwindcss/oxide-linux-arm64-musl@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.15.tgz#8a58e33595233628bb96e02fbb03b544680a36a2" + integrity sha512-AbvmEiteEj1nf42nE8skdHv73NoR+EwXVSgPY6l39X12Ex8pzOwwfi3Kc8GAmjsnsaDEbk+aj9NyL3UeyHcTLg== + +"@tailwindcss/oxide-linux-x64-gnu@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.15.tgz#546666cde500f80d3ff2e819de9276a9580685b1" + integrity sha512-+rzMVlvVgrXtFiS+ES78yWgKqpThgV19ISKD58Ck+YO5pO5KjyxLt7AWKsWMbY0R9yBDC82w6QVGz837AKQcHg== + +"@tailwindcss/oxide-linux-x64-musl@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.15.tgz#07e9ae8ca70bb9a4990ceee612c88781bd45a6fc" + integrity sha512-fPdEy7a8eQN9qOIK3Em9D3TO1z41JScJn8yxl/76mp4sAXFDfV4YXxsiptJcOwy6bGR+70ZSwFIZhTXzQeqwQg== + +"@tailwindcss/oxide-wasm32-wasi@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.15.tgz#82a6c5159bc562e4619cf938450a9c017837b194" + integrity sha512-sJ4yd6iXXdlgIMfIBXuVGp/NvmviEoMVWMOAGxtxhzLPp9LOj5k0pMEMZdjeMCl4C6Up+RM8T3Zgk+BMQ0bGcQ== dependencies: "@emnapi/core" "^1.5.0" "@emnapi/runtime" "^1.5.0" "@emnapi/wasi-threads" "^1.1.0" - "@napi-rs/wasm-runtime" "^1.0.5" + "@napi-rs/wasm-runtime" "^1.0.7" "@tybys/wasm-util" "^0.10.1" tslib "^2.4.0" -"@tailwindcss/oxide-win32-arm64-msvc@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.14.tgz#097c00bfc60cd84943a9cb5e853b25fa25525c77" - integrity sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA== +"@tailwindcss/oxide-win32-arm64-msvc@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.15.tgz#7e7f3f187c7a60209466b7064e78d5b70ad1945d" + integrity sha512-sJGE5faXnNQ1iXeqmRin7Ds/ru2fgCiaQZQQz3ZGIDtvbkeV85rAZ0QJFMDg0FrqsffZG96H1U9AQlNBRLsHVg== -"@tailwindcss/oxide-win32-x64-msvc@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.14.tgz#eaa49fa930ce16b23478d3b58c079a40ac0b6622" - integrity sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA== +"@tailwindcss/oxide-win32-x64-msvc@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.15.tgz#fd4e41ed0014a8da456576be1381c12c345d1a0a" + integrity sha512-NLeHE7jUV6HcFKS504bpOohyi01zPXi2PXmjFfkzTph8xRxDdxkRsXm/xDO5uV5K3brrE1cCwbUYmFUSHR3u1w== -"@tailwindcss/oxide@4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.1.14.tgz#acfc7869142665693b3b08e4e51d0f419ca13662" - integrity sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw== - dependencies: - detect-libc "^2.0.4" - tar "^7.5.1" +"@tailwindcss/oxide@4.1.15": + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.1.15.tgz#80a261e646f67bbaaa84ce2cf6c97e984fc0eae1" + integrity sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ== optionalDependencies: - "@tailwindcss/oxide-android-arm64" "4.1.14" - "@tailwindcss/oxide-darwin-arm64" "4.1.14" - "@tailwindcss/oxide-darwin-x64" "4.1.14" - "@tailwindcss/oxide-freebsd-x64" "4.1.14" - "@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.14" - "@tailwindcss/oxide-linux-arm64-gnu" "4.1.14" - "@tailwindcss/oxide-linux-arm64-musl" "4.1.14" - "@tailwindcss/oxide-linux-x64-gnu" "4.1.14" - "@tailwindcss/oxide-linux-x64-musl" "4.1.14" - "@tailwindcss/oxide-wasm32-wasi" "4.1.14" - "@tailwindcss/oxide-win32-arm64-msvc" "4.1.14" - "@tailwindcss/oxide-win32-x64-msvc" "4.1.14" + "@tailwindcss/oxide-android-arm64" "4.1.15" + "@tailwindcss/oxide-darwin-arm64" "4.1.15" + "@tailwindcss/oxide-darwin-x64" "4.1.15" + "@tailwindcss/oxide-freebsd-x64" "4.1.15" + "@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.15" + "@tailwindcss/oxide-linux-arm64-gnu" "4.1.15" + "@tailwindcss/oxide-linux-arm64-musl" "4.1.15" + "@tailwindcss/oxide-linux-x64-gnu" "4.1.15" + "@tailwindcss/oxide-linux-x64-musl" "4.1.15" + "@tailwindcss/oxide-wasm32-wasi" "4.1.15" + "@tailwindcss/oxide-win32-arm64-msvc" "4.1.15" + "@tailwindcss/oxide-win32-x64-msvc" "4.1.15" "@tailwindcss/postcss@^4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/postcss/-/postcss-4.1.14.tgz#29fd4e082b29460e4062a7bc4bf70b38a97f8fc5" - integrity sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg== + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/postcss/-/postcss-4.1.15.tgz#c39c6de085cfc26c2dc82868734bd63ed5875ec9" + integrity sha512-IZh8IT76KujRz6d15wZw4eoeViT4TqmzVWNNfpuNCTKiaZUwgr5vtPqO4HjuYDyx3MgGR5qgPt1HMzTeLJyA3g== dependencies: "@alloc/quick-lru" "^5.2.0" - "@tailwindcss/node" "4.1.14" - "@tailwindcss/oxide" "4.1.14" + "@tailwindcss/node" "4.1.15" + "@tailwindcss/oxide" "4.1.15" postcss "^8.4.41" - tailwindcss "4.1.14" + tailwindcss "4.1.15" "@tailwindcss/vite@^4.1.14": - version "4.1.14" - resolved "https://registry.yarnpkg.com/@tailwindcss/vite/-/vite-4.1.14.tgz#94d0fb87b11030138a45cef8ae9c3a7b080d4007" - integrity sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA== + version "4.1.15" + resolved "https://registry.yarnpkg.com/@tailwindcss/vite/-/vite-4.1.15.tgz#da8d5432a89f8ff091a3c34681c33bf155cf58f5" + integrity sha512-B6s60MZRTUil+xKoZoGe6i0Iar5VuW+pmcGlda2FX+guDuQ1G1sjiIy1W0frneVpeL/ZjZ4KEgWZHNrIm++2qA== dependencies: - "@tailwindcss/node" "4.1.14" - "@tailwindcss/oxide" "4.1.14" - tailwindcss "4.1.14" + "@tailwindcss/node" "4.1.15" + "@tailwindcss/oxide" "4.1.15" + tailwindcss "4.1.15" "@tanstack/table-core@8.21.3": version "8.21.3" @@ -2407,11 +2404,11 @@ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== "@types/node@*": - version "24.8.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.8.1.tgz#74c8ae00b045a0a351f2837ec00f25dfed0053be" - integrity sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q== + version "24.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.9.1.tgz#b7360b3c789089e57e192695a855aa4f6981a53c" + integrity sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg== dependencies: - undici-types "~7.14.0" + undici-types "~7.16.0" "@types/parse-path@^7.0.0": version "7.1.0" @@ -2446,78 +2443,78 @@ integrity sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA== "@typescript-eslint/eslint-plugin@^8.39.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz#20876354024140aabc8b400bc95735fdcade17d5" - integrity sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ== + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz#dc4ab93ee3d7e6c8e38820a0d6c7c93c7183e2dc" + integrity sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.46.1" - "@typescript-eslint/type-utils" "8.46.1" - "@typescript-eslint/utils" "8.46.1" - "@typescript-eslint/visitor-keys" "8.46.1" + "@typescript-eslint/scope-manager" "8.46.2" + "@typescript-eslint/type-utils" "8.46.2" + "@typescript-eslint/utils" "8.46.2" + "@typescript-eslint/visitor-keys" "8.46.2" graphemer "^1.4.0" ignore "^7.0.0" natural-compare "^1.4.0" ts-api-utils "^2.1.0" "@typescript-eslint/parser@^8.39.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.46.1.tgz#81751f46800fc6b01ce1a72760cd17f06e7f395b" - integrity sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA== - dependencies: - "@typescript-eslint/scope-manager" "8.46.1" - "@typescript-eslint/types" "8.46.1" - "@typescript-eslint/typescript-estree" "8.46.1" - "@typescript-eslint/visitor-keys" "8.46.1" + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.46.2.tgz#dd938d45d581ac8ffa9d8a418a50282b306f7ebf" + integrity sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g== + dependencies: + "@typescript-eslint/scope-manager" "8.46.2" + "@typescript-eslint/types" "8.46.2" + "@typescript-eslint/typescript-estree" "8.46.2" + "@typescript-eslint/visitor-keys" "8.46.2" debug "^4.3.4" -"@typescript-eslint/project-service@8.46.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.46.1.tgz#07be0e6f27fa90a17d8e5f6996ee02329c9a8c2e" - integrity sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg== +"@typescript-eslint/project-service@8.46.2": + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.46.2.tgz#ab2f02a0de4da6a7eeb885af5e059be57819d608" + integrity sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.46.1" - "@typescript-eslint/types" "^8.46.1" + "@typescript-eslint/tsconfig-utils" "^8.46.2" + "@typescript-eslint/types" "^8.46.2" debug "^4.3.4" -"@typescript-eslint/scope-manager@8.46.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz#590dd2e65e95af646bdaf50adeae9af39e25e8c1" - integrity sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A== +"@typescript-eslint/scope-manager@8.46.2": + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz#7d37df2493c404450589acb3b5d0c69cc0670a88" + integrity sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA== dependencies: - "@typescript-eslint/types" "8.46.1" - "@typescript-eslint/visitor-keys" "8.46.1" + "@typescript-eslint/types" "8.46.2" + "@typescript-eslint/visitor-keys" "8.46.2" -"@typescript-eslint/tsconfig-utils@8.46.1", "@typescript-eslint/tsconfig-utils@^8.46.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz#24405888560175c6c209c39df11ac06a2efef9d7" - integrity sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g== +"@typescript-eslint/tsconfig-utils@8.46.2", "@typescript-eslint/tsconfig-utils@^8.46.2": + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz#d110451cb93bbd189865206ea37ef677c196828c" + integrity sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag== -"@typescript-eslint/type-utils@8.46.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz#14d4307dd6045f6b48a888cde1513d6ec305537f" - integrity sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw== +"@typescript-eslint/type-utils@8.46.2": + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz#802d027864e6fb752e65425ed09f3e089fb4d384" + integrity sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA== dependencies: - "@typescript-eslint/types" "8.46.1" - "@typescript-eslint/typescript-estree" "8.46.1" - "@typescript-eslint/utils" "8.46.1" + "@typescript-eslint/types" "8.46.2" + "@typescript-eslint/typescript-estree" "8.46.2" + "@typescript-eslint/utils" "8.46.2" debug "^4.3.4" ts-api-utils "^2.1.0" -"@typescript-eslint/types@8.46.1", "@typescript-eslint/types@^8.34.0", "@typescript-eslint/types@^8.35.0", "@typescript-eslint/types@^8.39.1", "@typescript-eslint/types@^8.42.0", "@typescript-eslint/types@^8.44.0", "@typescript-eslint/types@^8.46.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.46.1.tgz#4c5479538ec10b5508b8e982e172911c987446d8" - integrity sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ== +"@typescript-eslint/types@8.46.2", "@typescript-eslint/types@^8.34.0", "@typescript-eslint/types@^8.35.0", "@typescript-eslint/types@^8.39.1", "@typescript-eslint/types@^8.42.0", "@typescript-eslint/types@^8.46.1", "@typescript-eslint/types@^8.46.2": + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.46.2.tgz#2bad7348511b31e6e42579820e62b73145635763" + integrity sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ== -"@typescript-eslint/typescript-estree@8.46.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz#1c146573b942ebe609c156c217ceafdc7a88e6ed" - integrity sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg== +"@typescript-eslint/typescript-estree@8.46.2": + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz#ab547a27e4222bb6a3281cb7e98705272e2c7d08" + integrity sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ== dependencies: - "@typescript-eslint/project-service" "8.46.1" - "@typescript-eslint/tsconfig-utils" "8.46.1" - "@typescript-eslint/types" "8.46.1" - "@typescript-eslint/visitor-keys" "8.46.1" + "@typescript-eslint/project-service" "8.46.2" + "@typescript-eslint/tsconfig-utils" "8.46.2" + "@typescript-eslint/types" "8.46.2" + "@typescript-eslint/visitor-keys" "8.46.2" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -2525,22 +2522,22 @@ semver "^7.6.0" ts-api-utils "^2.1.0" -"@typescript-eslint/utils@8.46.1", "@typescript-eslint/utils@^8.39.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.46.1.tgz#c572184d9227d66b10a954b90249a20c48b22452" - integrity sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ== +"@typescript-eslint/utils@8.46.2", "@typescript-eslint/utils@^8.39.1": + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.46.2.tgz#b313d33d67f9918583af205bd7bcebf20f231732" + integrity sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg== dependencies: "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.46.1" - "@typescript-eslint/types" "8.46.1" - "@typescript-eslint/typescript-estree" "8.46.1" + "@typescript-eslint/scope-manager" "8.46.2" + "@typescript-eslint/types" "8.46.2" + "@typescript-eslint/typescript-estree" "8.46.2" -"@typescript-eslint/visitor-keys@8.46.1": - version "8.46.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz#da35f1d58ec407419d68847cfd358b32746ac315" - integrity sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA== +"@typescript-eslint/visitor-keys@8.46.2": + version "8.46.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz#803fa298948c39acf810af21bdce6f8babfa9738" + integrity sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w== dependencies: - "@typescript-eslint/types" "8.46.1" + "@typescript-eslint/types" "8.46.2" eslint-visitor-keys "^4.2.1" "@ungap/structured-clone@^1.0.0": @@ -2944,6 +2941,15 @@ "@vueuse/metadata" "13.9.0" "@vueuse/shared" "13.9.0" +"@vueuse/core@14.0.0", "@vueuse/core@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-14.0.0.tgz#a3d9520935a191b167cb91e08f698545e46bf0a6" + integrity sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ== + dependencies: + "@types/web-bluetooth" "^0.0.21" + "@vueuse/metadata" "14.0.0" + "@vueuse/shared" "14.0.0" + "@vueuse/core@^10.8.0": version "10.11.1" resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6" @@ -2987,6 +2993,21 @@ resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-13.9.0.tgz#57c738d99661c33347080c0bc4cd11160e0d0881" integrity sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg== +"@vueuse/metadata@14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-14.0.0.tgz#139231dc8503f172a7a45ce1ceaa7a415befbf3c" + integrity sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA== + +"@vueuse/nuxt@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@vueuse/nuxt/-/nuxt-14.0.0.tgz#55f817da27444b48e8b0c707eea11ad3486839e5" + integrity sha512-6DNZ1DLw3UI52TtVX4jmL8oD/L3jFgtWeQmsSXP+0myMhpmlBpWB2xe1x5UdwdHly9j1Fq9DI8wz+LsW6wvdeg== + dependencies: + "@nuxt/kit" "^4.1.3" + "@vueuse/core" "14.0.0" + "@vueuse/metadata" "14.0.0" + local-pkg "^1.1.2" + "@vueuse/shared@10.11.1": version "10.11.1" resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.11.1.tgz#62b84e3118ae6e1f3ff38f4fbe71b0c5d0f10938" @@ -3006,6 +3027,11 @@ resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-13.9.0.tgz#7168b4ed647e625b05eb4e7e80fe8aabd00e3923" integrity sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g== +"@vueuse/shared@14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-14.0.0.tgz#15a424285fd6d453d1a99d1caba8cc293992868d" + integrity sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw== + "@webcontainer/env@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@webcontainer/env/-/env-1.1.1.tgz#23021b2bb24befeeef53dba8996d1886b7016515" @@ -3064,9 +3090,9 @@ ajv@^6.12.4: uri-js "^4.2.2" alien-signals@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-3.0.1.tgz#73362f460ca91534e0a7becd9b32a6dbf37a9b7c" - integrity sha512-ec02Wv5iOg7yG979PH9ykv5KN/KHznOxMlKy/Jr8lnBo3T94d4MUGo7FVdM8B2fM0e94twzEcWCyWzfIyeV19g== + version "3.0.3" + resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-3.0.3.tgz#f54e89180e490a9577e4f6f7203720e4335a8715" + integrity sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q== ansi-regex@^5.0.1: version "5.0.1" @@ -3200,14 +3226,14 @@ balanced-match@^1.0.0: integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== bare-events@^2.5.4, bare-events@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.8.0.tgz#ec962fa9e2bfafd4edd444942df1ed0c7aba8e4a" - integrity sha512-AOhh6Bg5QmFIXdViHbMc2tLDsBIRxdkIaIddPslJF9Z5De3APBScuqGP2uThXnIpqFrgoxMNC6km7uXNIMLHXA== + version "2.8.1" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.8.1.tgz#121afaeee9e9a8eb92e71d125bc85753d39913d0" + integrity sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ== bare-fs@^4.0.1: - version "4.4.11" - resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.4.11.tgz#e9fe3deefb294ed02e696e5a5dfcc14252a2c806" - integrity sha512-Bejmm9zRMvMTRoHS+2adgmXw1ANZnCNx+B5dgZpGwlP1E3x6Yuxea8RToddHUbWtVV0iUMWqsgZr8+jcgUI2SA== + version "4.5.0" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.5.0.tgz#f3227b4bc79a65ca7e91a1c05be5919c7c7d8340" + integrity sha512-GljgCjeupKZJNetTqxKaQArLK10vpmK28or0+RwWjEl5Rk+/xG3wkpmkv+WrcBm3q1BwHKlnhXzR8O37kcvkXQ== dependencies: bare-events "^2.5.4" bare-path "^3.0.0" @@ -3235,9 +3261,9 @@ bare-stream@^2.6.4: streamx "^2.21.0" bare-url@^2.2.2: - version "2.3.0" - resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.3.0.tgz#06bb523cfdd5bb8dd224bf4e0062ff0231eca998" - integrity sha512-c+RCqMSZbkz97Mw1LWR0gcOqwK82oyYKfLoHJ8k13ybi1+I80ffdDzUy0TdAburdrR/kI0/VuN8YgEnJqX+Nyw== + version "2.3.1" + resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.3.1.tgz#95e33e99bdc768766ca94246fdc397e88675ec9c" + integrity sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw== dependencies: bare-path "^3.0.0" @@ -3251,10 +3277,10 @@ base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -baseline-browser-mapping@^2.8.9: - version "2.8.17" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.17.tgz#85aff3f7dd6326ea25b77ce834b96bb698545dc6" - integrity sha512-j5zJcx6golJYTG6c05LUZ3Z8Gi+M62zRT/ycz4Xq4iCOdpcxwg7ngEYD4KA0eWZC7U17qh/Smq8bYbACJ0ipBA== +baseline-browser-mapping@^2.8.19: + version "2.8.19" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.19.tgz#8d99bb7f06bc6ea5c9c1b961e631a1713069bbe0" + integrity sha512-zoKGUdu6vb2jd3YOq0nnhEDQVbPcHhco3UImJrv5dSkvxTc2pl2WjOPsjZXDwPDSl5eghIMuY3R6J9NDKF3KcQ== better-sqlite3@^12.4.1: version "12.4.1" @@ -3330,15 +3356,15 @@ brotli@^1.3.2: base64-js "^1.1.2" browserslist@^4.0.0, browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.1, browserslist@^4.26.3: - version "4.26.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.3.tgz#40fbfe2d1cd420281ce5b1caa8840049c79afb56" - integrity sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w== + version "4.27.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.27.0.tgz#755654744feae978fbb123718b2f139bc0fa6697" + integrity sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw== dependencies: - baseline-browser-mapping "^2.8.9" - caniuse-lite "^1.0.30001746" - electron-to-chromium "^1.5.227" - node-releases "^2.0.21" - update-browserslist-db "^1.1.3" + baseline-browser-mapping "^2.8.19" + caniuse-lite "^1.0.30001751" + electron-to-chromium "^1.5.238" + node-releases "^2.0.26" + update-browserslist-db "^1.1.4" buffer-crc32@^1.0.0: version "1.0.0" @@ -3428,7 +3454,7 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001746: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001751: version "1.0.30001751" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz#dacd5d9f4baeea841641640139d2b2a4df4226ad" integrity sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw== @@ -3704,12 +3730,12 @@ cookie@^1.0.2: resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== -copy-anything@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0" - integrity sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w== +copy-anything@^4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-4.0.5.tgz#16cabafd1ea4bb327a540b750f2b4df522825aea" + integrity sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA== dependencies: - is-what "^4.1.8" + is-what "^5.2.0" core-js-compat@^3.44.0: version "3.46.0" @@ -4015,15 +4041,15 @@ detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== -detect-libc@^2.0.0, detect-libc@^2.0.2, detect-libc@^2.0.3, detect-libc@^2.0.4: +detect-libc@^2.0.0, detect-libc@^2.0.2, detect-libc@^2.0.3: version "2.1.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== devalue@^5.3.2: - version "5.4.1" - resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.4.1.tgz#6086910772fa055d707f60a3e5858d26ef9dcf55" - integrity sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ== + version "5.4.2" + resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.4.2.tgz#d002d836f9e92fc0c3bd9b76ea69129cbf99dca7" + integrity sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw== devlop@^1.0.0, devlop@^1.1.0: version "1.1.0" @@ -4104,10 +4130,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.5.227: - version "1.5.237" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz#eacf61cef3f6345d0069ab427585c5a04d7084f0" - integrity sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg== +electron-to-chromium@^1.5.238: + version "1.5.238" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.238.tgz#cdf5ee412df435174490f5aa5793df815b4ee157" + integrity sha512-khBdc+w/Gv+cS8e/Pbnaw/FXcBUeKrRVik9IxfXtgREOWyJhR4tj43n3amkVogJ/yeQUqzkrZcFhtIxIdqmmcQ== embla-carousel-auto-height@^8.6.0: version "8.6.0" @@ -4449,23 +4475,22 @@ eslint-visitor-keys@^4.2.0, eslint-visitor-keys@^4.2.1: integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== eslint@^9.37.0: - version "9.37.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.37.0.tgz#ac0222127f76b09c0db63036f4fe289562072d74" - integrity sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig== + version "9.38.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.38.0.tgz#3957d2af804e5cf6cc503c618f60acc71acb2e7e" + integrity sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw== dependencies: "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.21.0" - "@eslint/config-helpers" "^0.4.0" + "@eslint/config-array" "^0.21.1" + "@eslint/config-helpers" "^0.4.1" "@eslint/core" "^0.16.0" "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.37.0" + "@eslint/js" "9.38.0" "@eslint/plugin-kit" "^0.4.0" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/retry" "^0.4.2" "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.6" @@ -4840,9 +4865,9 @@ get-stream@^9.0.0: is-stream "^4.0.1" get-tsconfig@^4.10.1: - version "4.12.0" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.12.0.tgz#cfb3a4446a2abd324a205469e8bda4e7e44cbd35" - integrity sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw== + version "4.13.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7" + integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ== dependencies: resolve-pkg-maps "^1.0.0" @@ -5322,9 +5347,9 @@ ini@~1.3.0: integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ioredis@^5.8.1: - version "5.8.1" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.8.1.tgz#2d2dae406be71665607906f57b3c971bb4b089ae" - integrity sha512-Qho8TgIamqEPdgiMadJwzRMW3TudIg6vpg4YONokGDudy4eqRIJtDbVX72pfLBcWxvbn3qm/40TyGUObdW4tLQ== + version "5.8.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.8.2.tgz#c7a228a26cf36f17a5a8011148836877780e2e14" + integrity sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q== dependencies: "@ioredis/commands" "1.4.0" cluster-key-slot "^1.1.0" @@ -5400,7 +5425,7 @@ is-builtin-module@^5.0.0: dependencies: builtin-modules "^5.0.0" -is-core-module@^2.16.0: +is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== @@ -5518,10 +5543,10 @@ is-wayland@^0.1.0: resolved "https://registry.yarnpkg.com/is-wayland/-/is-wayland-0.1.0.tgz#ed966c54a608af5ba3c407922589859a0d424fe5" integrity sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA== -is-what@^4.1.8: - version "4.1.16" - resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.16.tgz#1ad860a19da8b4895ad5495da3182ce2acdd7a6f" - integrity sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A== +is-what@^5.2.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-5.5.0.tgz#a3031815757cfe1f03fed990bf6355a2d3f628c4" + integrity sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw== is-wsl@^2.2.0: version "2.2.0" @@ -5726,73 +5751,79 @@ lighthouse-logger@^2.0.1: debug "^4.4.1" marky "^1.2.2" -lightningcss-darwin-arm64@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz#3d47ce5e221b9567c703950edf2529ca4a3700ae" - integrity sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ== - -lightningcss-darwin-x64@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz#e81105d3fd6330860c15fe860f64d39cff5fbd22" - integrity sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA== - -lightningcss-freebsd-x64@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz#a0e732031083ff9d625c5db021d09eb085af8be4" - integrity sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig== - -lightningcss-linux-arm-gnueabihf@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz#1f5ecca6095528ddb649f9304ba2560c72474908" - integrity sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q== - -lightningcss-linux-arm64-gnu@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz#eee7799726103bffff1e88993df726f6911ec009" - integrity sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw== - -lightningcss-linux-arm64-musl@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz#f2e4b53f42892feeef8f620cbb889f7c064a7dfe" - integrity sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ== - -lightningcss-linux-x64-gnu@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz#2fc7096224bc000ebb97eea94aea248c5b0eb157" - integrity sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw== - -lightningcss-linux-x64-musl@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz#66dca2b159fd819ea832c44895d07e5b31d75f26" - integrity sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ== - -lightningcss-win32-arm64-msvc@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz#7d8110a19d7c2d22bfdf2f2bb8be68e7d1b69039" - integrity sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA== - -lightningcss-win32-x64-msvc@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz#fd7dd008ea98494b85d24b4bea016793f2e0e352" - integrity sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg== - -lightningcss@1.30.1: - version "1.30.1" - resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.30.1.tgz#78e979c2d595bfcb90d2a8c0eb632fe6c5bfed5d" - integrity sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg== +lightningcss-android-arm64@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz#6966b7024d39c94994008b548b71ab360eb3a307" + integrity sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A== + +lightningcss-darwin-arm64@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz#a5fa946d27c029e48c7ff929e6e724a7de46eb2c" + integrity sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA== + +lightningcss-darwin-x64@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz#5ce87e9cd7c4f2dcc1b713f5e8ee185c88d9b7cd" + integrity sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ== + +lightningcss-freebsd-x64@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz#6ae1d5e773c97961df5cff57b851807ef33692a5" + integrity sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA== + +lightningcss-linux-arm-gnueabihf@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz#62c489610c0424151a6121fa99d77731536cdaeb" + integrity sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA== + +lightningcss-linux-arm64-gnu@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz#2a3661b56fe95a0cafae90be026fe0590d089298" + integrity sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A== + +lightningcss-linux-arm64-musl@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz#d7ddd6b26959245e026bc1ad9eb6aa983aa90e6b" + integrity sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA== + +lightningcss-linux-x64-gnu@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz#5a89814c8e63213a5965c3d166dff83c36152b1a" + integrity sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w== + +lightningcss-linux-x64-musl@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz#808c2e91ce0bf5d0af0e867c6152e5378c049728" + integrity sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA== + +lightningcss-win32-arm64-msvc@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz#ab4a8a8a2e6a82a4531e8bbb6bf0ff161ee6625a" + integrity sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ== + +lightningcss-win32-x64-msvc@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz#f01f382c8e0a27e1c018b0bee316d210eac43b6e" + integrity sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw== + +lightningcss@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.30.2.tgz#4ade295f25d140f487d37256f4cd40dc607696d0" + integrity sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ== dependencies: detect-libc "^2.0.3" optionalDependencies: - lightningcss-darwin-arm64 "1.30.1" - lightningcss-darwin-x64 "1.30.1" - lightningcss-freebsd-x64 "1.30.1" - lightningcss-linux-arm-gnueabihf "1.30.1" - lightningcss-linux-arm64-gnu "1.30.1" - lightningcss-linux-arm64-musl "1.30.1" - lightningcss-linux-x64-gnu "1.30.1" - lightningcss-linux-x64-musl "1.30.1" - lightningcss-win32-arm64-msvc "1.30.1" - lightningcss-win32-x64-msvc "1.30.1" + lightningcss-android-arm64 "1.30.2" + lightningcss-darwin-arm64 "1.30.2" + lightningcss-darwin-x64 "1.30.2" + lightningcss-freebsd-x64 "1.30.2" + lightningcss-linux-arm-gnueabihf "1.30.2" + lightningcss-linux-arm64-gnu "1.30.2" + lightningcss-linux-arm64-musl "1.30.2" + lightningcss-linux-x64-gnu "1.30.2" + lightningcss-linux-x64-musl "1.30.2" + lightningcss-win32-arm64-msvc "1.30.2" + lightningcss-win32-x64-msvc "1.30.2" lilconfig@^3.1.3: version "3.1.3" @@ -6523,9 +6554,9 @@ motion-utils@^12.23.6: integrity sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ== motion-v@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/motion-v/-/motion-v-1.7.2.tgz#90c51269ce2107b2f640cfb47037f8e1df4b810a" - integrity sha512-h2qfae2LUMLw5KIjQF5cT+r0MrLwP4AFDMOisyp25x/oDI3PHgjLHJrhHx77q8iBNegk4llt5p6deC12EJ5fvQ== + version "1.7.3" + resolved "https://registry.yarnpkg.com/motion-v/-/motion-v-1.7.3.tgz#2dc491559f66b3ccb02f46d0d2e2e8958ddb0dd9" + integrity sha512-lwgrge7Y7FF9LxBxZ//MsSLPPjh4z1yYE9ie236Pa89mRCsBlONXbLcseln6G6LyAG8pt3PpzUxWt2grwMcBAA== dependencies: framer-motion "12.23.12" hey-listen "^1.0.8" @@ -6711,10 +6742,10 @@ node-mock-http@^1.0.2, node-mock-http@^1.0.3: resolved "https://registry.yarnpkg.com/node-mock-http/-/node-mock-http-1.0.3.tgz#4e55e093267a3b910cded7354389ce2d02c89e77" integrity sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog== -node-releases@^2.0.21: - version "2.0.25" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.25.tgz#95479437bd409231e03981c1f6abee67f5e962df" - integrity sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA== +node-releases@^2.0.26: + version "2.0.26" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.26.tgz#fdfa272f2718a1309489d18aef4ef5ba7f5dfb52" + integrity sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA== nopt@^8.0.0: version "8.1.0" @@ -6756,9 +6787,9 @@ nth-check@^2.0.1, nth-check@^2.1.1: boolbase "^1.0.0" nuxt-component-meta@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/nuxt-component-meta/-/nuxt-component-meta-0.14.0.tgz#09b9b054cf3a83321b50003443b7be63021ff774" - integrity sha512-RaL6bHJujuZmw/G+uNWAHYktf3k4hdlBIy+FqudXji42IefrJKdSMkh5ixyhsfEHWsuTYGKxD2NU3sq990KGrQ== + version "0.14.1" + resolved "https://registry.yarnpkg.com/nuxt-component-meta/-/nuxt-component-meta-0.14.1.tgz#9ccf05b4ae1c3c1a390220c47aa9cb05b2476bf1" + integrity sha512-Q8NuynMJleiutBx4cD33TLp5HCGGmGfz/dE+PNOsAScSAG54hmpPk2beeIwh2VZJoMzPAyPPAqbsdIKpMcOHbA== dependencies: "@nuxt/kit" "^4.1.1" citty "^0.1.6" @@ -6826,28 +6857,28 @@ nuxt-schema-org@^5.0.9: pkg-types "^2.3.0" sirv "^3.0.2" -nuxt-site-config-kit@3.2.10: - version "3.2.10" - resolved "https://registry.yarnpkg.com/nuxt-site-config-kit/-/nuxt-site-config-kit-3.2.10.tgz#337099640471c7a488d2214bd604a8da2f4dd1d2" - integrity sha512-HHBqcOLxYuw1HiAinaR/aGBvTZUdv7YcLsYDW2kuKAhLpiChACYM6lX3RTyrpLe6BNGRUJB69DbJ1VM8+QYWEA== +nuxt-site-config-kit@3.2.11: + version "3.2.11" + resolved "https://registry.yarnpkg.com/nuxt-site-config-kit/-/nuxt-site-config-kit-3.2.11.tgz#0ef33cd4dc4f9347d5bf10eb4ad8c72a970f4d20" + integrity sha512-Um9/JiJpskAC8H18pHs3D/c7ikKj37/OsM1rvTqDgdzShSzOAxGGN+8qBVtGaqp1V+9BuPFSXhr1+TV7+RRsRA== dependencies: "@nuxt/kit" "^4.1.3" pkg-types "^2.3.0" - site-config-stack "3.2.10" + site-config-stack "3.2.11" std-env "^3.10.0" ufo "^1.6.1" nuxt-site-config@^3.2.5, nuxt-site-config@^3.2.9: - version "3.2.10" - resolved "https://registry.yarnpkg.com/nuxt-site-config/-/nuxt-site-config-3.2.10.tgz#175be858bb607f0285fa9cf09804c4762b7bc9ea" - integrity sha512-2iyjOy3B1gIKlzNFMw3D9WpFw9kMlT5H4DqL3NAvmRf+Sem2vrhOzm8snON7Fxn94OYA5jNjgzusivNEM6InEQ== + version "3.2.11" + resolved "https://registry.yarnpkg.com/nuxt-site-config/-/nuxt-site-config-3.2.11.tgz#a0883e54cdd58eacca0d59a63a8773e07b022437" + integrity sha512-hU78O5f0/n1LOIorDe6iKbW3xw19bao8YbQ7RCiUtVM+1XbD11JWzUXWygX7atV+KtzGhZUbTbhjxmfbnlF//A== dependencies: "@nuxt/kit" "^4.1.3" - nuxt-site-config-kit "3.2.10" + nuxt-site-config-kit "3.2.11" pathe "^2.0.3" pkg-types "^2.3.0" sirv "^3.0.2" - site-config-stack "3.2.10" + site-config-stack "3.2.11" ufo "^1.6.1" nuxt@^4.1.3: @@ -7121,9 +7152,9 @@ package-json-from-dist@^1.0.0: integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== package-manager-detector@^1.1.0, package-manager-detector@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-1.4.1.tgz#8f683009e02cc2ca1c4e2c887316075108239189" - integrity sha512-dSMiVLBEA4XaNJ0PRb4N5cV/SEP4BWrWZKBmfF+OUm2pQTiZ6DDkKeWaltwu3JRhLoy59ayIkJ00cx9K9CaYTg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-1.5.0.tgz#8dcf7b78554047ddf5da453e6ba07ebc915c507e" + integrity sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw== pako@^0.2.5: version "0.2.9" @@ -7853,7 +7884,7 @@ rehype-sort-attributes@^5.0.1: "@types/hast" "^3.0.0" unist-util-visit "^5.0.0" -reka-ui@2.5.1, reka-ui@^2.0.0: +reka-ui@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/reka-ui/-/reka-ui-2.5.1.tgz#4623b9ad0281d1747b76dffdb620496a92f7ac1d" integrity sha512-QJGB3q21wQ1Kw28HhhNDpjfFe8qpePX1gK4FTBRd68XTh9aEnhR5bTJnlV0jxi8FBPh0xivZBeNFUc3jiGx7mQ== @@ -7869,6 +7900,22 @@ reka-ui@2.5.1, reka-ui@^2.0.0: defu "^6.1.4" ohash "^2.0.11" +reka-ui@^2.0.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/reka-ui/-/reka-ui-2.6.0.tgz#3b96712744decb9da8f4b1af79d16a499c84b826" + integrity sha512-NrGMKrABD97l890mFS3TNUzB0BLUfbL3hh0NjcJRIUSUljb288bx3Mzo31nOyUcdiiW0HqFGXJwyCBh9cWgb0w== + dependencies: + "@floating-ui/dom" "^1.6.13" + "@floating-ui/vue" "^1.1.6" + "@internationalized/date" "^3.5.0" + "@internationalized/number" "^3.5.0" + "@tanstack/vue-virtual" "^3.12.0" + "@vueuse/core" "^12.5.0" + "@vueuse/shared" "^12.5.0" + aria-hidden "^1.2.4" + defu "^6.1.4" + ohash "^2.0.11" + remark-emoji@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-5.0.2.tgz#2a66df174806ed2dc68c329d475b7d993d2d4506" @@ -7991,11 +8038,11 @@ resolve-pkg-maps@^1.0.0: integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== resolve@^1.22.1: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.16.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -8025,34 +8072,34 @@ rollup-plugin-visualizer@^6.0.4: yargs "^17.5.1" rollup@^4.43.0, rollup@^4.52.4: - version "4.52.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.4.tgz#71e64cce96a865fcbaa6bb62c6e82807f4e378a1" - integrity sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ== + version "4.52.5" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.5.tgz#96982cdcaedcdd51b12359981f240f94304ec235" + integrity sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw== dependencies: "@types/estree" "1.0.8" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.52.4" - "@rollup/rollup-android-arm64" "4.52.4" - "@rollup/rollup-darwin-arm64" "4.52.4" - "@rollup/rollup-darwin-x64" "4.52.4" - "@rollup/rollup-freebsd-arm64" "4.52.4" - "@rollup/rollup-freebsd-x64" "4.52.4" - "@rollup/rollup-linux-arm-gnueabihf" "4.52.4" - "@rollup/rollup-linux-arm-musleabihf" "4.52.4" - "@rollup/rollup-linux-arm64-gnu" "4.52.4" - "@rollup/rollup-linux-arm64-musl" "4.52.4" - "@rollup/rollup-linux-loong64-gnu" "4.52.4" - "@rollup/rollup-linux-ppc64-gnu" "4.52.4" - "@rollup/rollup-linux-riscv64-gnu" "4.52.4" - "@rollup/rollup-linux-riscv64-musl" "4.52.4" - "@rollup/rollup-linux-s390x-gnu" "4.52.4" - "@rollup/rollup-linux-x64-gnu" "4.52.4" - "@rollup/rollup-linux-x64-musl" "4.52.4" - "@rollup/rollup-openharmony-arm64" "4.52.4" - "@rollup/rollup-win32-arm64-msvc" "4.52.4" - "@rollup/rollup-win32-ia32-msvc" "4.52.4" - "@rollup/rollup-win32-x64-gnu" "4.52.4" - "@rollup/rollup-win32-x64-msvc" "4.52.4" + "@rollup/rollup-android-arm-eabi" "4.52.5" + "@rollup/rollup-android-arm64" "4.52.5" + "@rollup/rollup-darwin-arm64" "4.52.5" + "@rollup/rollup-darwin-x64" "4.52.5" + "@rollup/rollup-freebsd-arm64" "4.52.5" + "@rollup/rollup-freebsd-x64" "4.52.5" + "@rollup/rollup-linux-arm-gnueabihf" "4.52.5" + "@rollup/rollup-linux-arm-musleabihf" "4.52.5" + "@rollup/rollup-linux-arm64-gnu" "4.52.5" + "@rollup/rollup-linux-arm64-musl" "4.52.5" + "@rollup/rollup-linux-loong64-gnu" "4.52.5" + "@rollup/rollup-linux-ppc64-gnu" "4.52.5" + "@rollup/rollup-linux-riscv64-gnu" "4.52.5" + "@rollup/rollup-linux-riscv64-musl" "4.52.5" + "@rollup/rollup-linux-s390x-gnu" "4.52.5" + "@rollup/rollup-linux-x64-gnu" "4.52.5" + "@rollup/rollup-linux-x64-musl" "4.52.5" + "@rollup/rollup-openharmony-arm64" "4.52.5" + "@rollup/rollup-win32-arm64-msvc" "4.52.5" + "@rollup/rollup-win32-ia32-msvc" "4.52.5" + "@rollup/rollup-win32-x64-gnu" "4.52.5" + "@rollup/rollup-win32-x64-msvc" "4.52.5" fsevents "~2.3.2" run-applescript@^7.0.0: @@ -8270,10 +8317,10 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -site-config-stack@3.2.10: - version "3.2.10" - resolved "https://registry.yarnpkg.com/site-config-stack/-/site-config-stack-3.2.10.tgz#c2bbae84aaf55bd35b8071a9623237b4f47890f7" - integrity sha512-y6cMJWSRUup1DX1KILvrUH9mVInqvtjFSYOSJMkWC0eIm9n43qYtxM9DMDKEgS5kZXEGmW8A0H3svunyIPLtTg== +site-config-stack@3.2.11: + version "3.2.11" + resolved "https://registry.yarnpkg.com/site-config-stack/-/site-config-stack-3.2.11.tgz#51ff6fb61a9fdb745aa1adc0a1e111449f4c30ac" + integrity sha512-KRJ49L58VtJJo3WdB7hXv6lq3oEJNOoBpig1v+OuHSppiBp7X6xqcAByJHveeBpBE9kHwqy/sn1LEnIibQ4nOA== dependencies: ufo "^1.6.1" @@ -8517,11 +8564,11 @@ stylehacks@^7.0.5: postcss-selector-parser "^7.1.0" superjson@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.2.tgz#9d52bf0bf6b5751a3c3472f1292e714782ba3173" - integrity sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q== + version "2.2.3" + resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.3.tgz#c42236fff6ecc449b7ffa7f023a9a028a5ec9c87" + integrity sha512-ay3d+LW/S6yppKoTz3Bq4mG0xrS5bFwfWEBmQfbC7lt5wmtk+Obq0TxVuA9eYRirBTQb1K3eEpBRHMQEo0WyVw== dependencies: - copy-anything "^3.0.2" + copy-anything "^4" supports-color@^10.0.0: version "10.2.2" @@ -8591,10 +8638,10 @@ tailwind-variants@^3.1.1: resolved "https://registry.yarnpkg.com/tailwind-variants/-/tailwind-variants-3.1.1.tgz#40a87a7f24a3c372faecedd23a9c40e40bb2033d" integrity sha512-ftLXe3krnqkMHsuBTEmaVUXYovXtPyTK7ckEfDRXS8PBZx0bAUas+A0jYxuKA5b8qg++wvQ3d2MQ7l/xeZxbZQ== -tailwindcss@4.1.14, tailwindcss@^4.1.14: - version "4.1.14" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.14.tgz#a5907cc2202a2a1f5f15bac6f2031e53117e43a8" - integrity sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA== +tailwindcss@4.1.15, tailwindcss@^4.1.14: + version "4.1.15" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.15.tgz#1437ee75ed8f66d8d866da5e25daa38d06ce5cea" + integrity sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ== tapable@^2.2.0: version "2.3.0" @@ -8642,7 +8689,7 @@ tar-stream@^3.0.0, tar-stream@^3.1.5: fast-fifo "^1.2.0" streamx "^2.15.0" -tar@^7.4.0, tar@^7.4.3, tar@^7.5.1: +tar@^7.4.0, tar@^7.4.3: version "7.5.1" resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.1.tgz#750a8bd63b7c44c1848e7bf982260a083cf747c9" integrity sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g== @@ -8796,10 +8843,10 @@ unctx@^2.4.1: magic-string "^0.30.17" unplugin "^2.1.0" -undici-types@~7.14.0: - version "7.14.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.14.0.tgz#4c037b32ca4d7d62fae042174604341588bc0840" - integrity sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA== +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== undici@^7.16.0: version "7.16.0" @@ -8978,9 +9025,9 @@ unplugin-utils@^0.3.0, unplugin-utils@^0.3.1: picomatch "^4.0.3" unplugin-vue-components@^29.1.0: - version "29.1.0" - resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-29.1.0.tgz#6064dfd43ebfc2d54fc04d6c391d30c9f66d3360" - integrity sha512-z/9ACPXth199s9aCTCdKZAhe5QGOpvzJYP+Hkd0GN1/PpAmsu+W3UlRY3BJAewPqQxh5xi56+Og6mfiCV1Jzpg== + version "29.2.0" + resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-29.2.0.tgz#5cf1aace82f6e7481fb068e6d23f33ef1ef849d6" + integrity sha512-QxBeBdmEflgtJRgMQMc/z/JVV5lcwXN5nOy5ehX6CKDGylIu6Qn4Goy8X95S0qOxF7EdI+uNhdBd4v5i0bvzCw== dependencies: chokidar "^3.6.0" debug "^4.4.3" @@ -8989,7 +9036,7 @@ unplugin-vue-components@^29.1.0: mlly "^1.8.0" tinyglobby "^0.2.15" unplugin "^2.3.10" - unplugin-utils "^0.3.0" + unplugin-utils "^0.3.1" unplugin-vue-router@^0.15.0: version "0.15.0" @@ -9096,10 +9143,10 @@ unwasm@^0.3.11: pkg-types "^2.2.0" unplugin "^2.3.6" -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== +update-browserslist-db@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a" + integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== dependencies: escalade "^3.2.0" picocolors "^1.1.1" @@ -9219,9 +9266,9 @@ vite-plugin-vue-tracer@^1.0.0: source-map-js "^1.2.1" "vite@^5.0.0 || ^6.0.0 || ^7.0.0-0", vite@^7.1.9: - version "7.1.10" - resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.10.tgz#47c9970f3b0fe9057bfbcfeff8cd370edd7bd41b" - integrity sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA== + version "7.1.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.11.tgz#4d006746112fee056df64985191e846ebfb6007e" + integrity sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg== dependencies: esbuild "^0.25.0" fdir "^6.5.0" From 1bf90e83f145f0b81fbb11efbcd18c1854e469ae Mon Sep 17 00:00:00 2001 From: Dan Pastori Date: Wed, 22 Oct 2025 16:24:53 -0500 Subject: [PATCH 218/304] Fixed OG Images --- docs/app/components/OgImage/OgImageDocs.vue | 99 ++++++--------------- 1 file changed, 29 insertions(+), 70 deletions(-) diff --git a/docs/app/components/OgImage/OgImageDocs.vue b/docs/app/components/OgImage/OgImageDocs.vue index dadabc9a5..bc5448b8d 100644 --- a/docs/app/components/OgImage/OgImageDocs.vue +++ b/docs/app/components/OgImage/OgImageDocs.vue @@ -1,76 +1,35 @@ - + \ No newline at end of file From ce7f643eab98dd5e75b425bb5dc0d5c654c0d5d0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 16:30:34 -0500 Subject: [PATCH 219/304] Add migration guide from official PHP images to serversideup/php, detailing key differences, default configurations, and step-by-step instructions for updating Dockerfiles and docker-compose.yml files, enhancing user onboarding and transition experience. --- .../1.migrating-from-official-php-images.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/content/docs/5.guide/1.migrating-from-official-php-images.md diff --git a/docs/content/docs/5.guide/1.migrating-from-official-php-images.md b/docs/content/docs/5.guide/1.migrating-from-official-php-images.md new file mode 100644 index 000000000..cdd616b5f --- /dev/null +++ b/docs/content/docs/5.guide/1.migrating-from-official-php-images.md @@ -0,0 +1,89 @@ +--- +head.title: 'Migrating from official PHP Docker images - Docker PHP - Server Side Up' +description: 'Learn how easy it is to move from the official PHP docker images to serversideup/php.' +layout: docs +title: Migrating from official PHP images +--- + +::lead-p +Migrating from the official PHP images to serversideup/php is easy because our images are based on the official PHP images. We just give you a "batteries included" experience that's ready for production. +:: + +## Understand our default configurations +Before you make the switch, you should understand our default configurations and make sure they line up with your requirements. As always, perform these changes on a test branch and use automated testing to ensure your application is working as expected. + +:u-button{to="/docs/getting-started/default-configurations" label="Learn more about our default configurations" aria-label="Learn more about our default configurations" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Key differences +::warning +Because our images run as `www-data` by default, you may need to update file permissions for mounted volumes. +:: + +| | **Official PHP Images** |**serversideup/php** | +|-------------------------|-------------------------|---------------------| +| Base Operating System | Debian, Alpine | Debian, Alpine | +| PHP Compilation | PHP Source Code | PHP Source Code (based on official PHP images) | +| Run PHP, pinned to the minor version | ✅ | ✅ | +| Multi-arch support | ✅ | ✅ | +| Init System | Docker CMD | Docker CMD or [S6-Overlay](https://github.com/just-containers/s6-overlay) | +| Published Registry| DockerHub | [DockerHub](https://hub.docker.com/r/serversideup/php), [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php) | +| Unprivileged by default | ❌ | ✅ | +| Variable-first configuration | ❌ | ✅ | +| Includes `composer` | ❌ | ✅ | +| Includes [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer) | ❌ | ✅ | +| Production-Ready by default| ❌ | ✅ | +| Built-in security optimizations | ❌ | ✅ | +| Optimized for Laravel & WordPress| ❌ | ✅ | +| NGINX + FPM variation| ❌ | ✅ | +| FrankenPHP variation| ❌ | ✅ | +| Native health checks | ❌ | ✅ | + +:u-button{to="/docs/getting-started/choosing-an-image" label="Learn more about choosing an image" aria-label="Learn more about choosing an image" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Making the change +Making the change will literally take you two seconds. + +::steps{level="4"} + +#### Figure out which image you'd like to use +Review our [choosing an image](/docs/getting-started/choosing-an-image) guide to help you decide which image you'd like to use. + +#### Update your `Dockerfile` or `docker-compose.yml` file +:::tip +We simply change `php:8.4-apache` to `serversideup/php:8.4-fpm-apache` +::: +**Dockerfile** +:::code-group +```dockerfile [ORIGINAL: Dockerfile]{1} +FROM php:8.4-apache + +# Rest of your Dockerfile... +``` +```dockerfile [UPDATED: Dockerfile]{1} +FROM serversideup/php:8.4-fpm-apache + +# Rest of your Dockerfile... +``` +::: + +**docker-compose.yml** +:::code-group +```yml [ORIGINAL: docker-compose.yml]{3,5-6} +services: + php: + image: php:8.4-apache + ports: + - 80:80 + - 443:443 +``` +```yml [UPDATED: docker-compose.yml]{3,5-6} +services: + php: + image: serversideup/php:8.4-fpm-apache + ports: + - 80:8080 + - 443:8443 +``` +::: + +:: \ No newline at end of file From ae1dfb4ec0e3e751044836a44bde92d6b610ff72 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 16:30:40 -0500 Subject: [PATCH 220/304] Add 'diff' language support to Nuxt configuration, enhancing syntax highlighting for documentation and improving readability for users working with code diffs. --- docs/nuxt.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index d24c20172..00219ab09 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -26,6 +26,7 @@ export default defineNuxtConfig({ langs: [ 'jinja', 'bash', + 'diff', 'dockerfile', 'nginx', 'php', From 44d36262d02fa0863d81b4855f0b6fdbabbdcbd2 Mon Sep 17 00:00:00 2001 From: Dan Pastori Date: Wed, 22 Oct 2025 16:32:11 -0500 Subject: [PATCH 221/304] Added about --- docs/app/components/About.vue | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/app/components/About.vue diff --git a/docs/app/components/About.vue b/docs/app/components/About.vue new file mode 100644 index 000000000..9619ebd8d --- /dev/null +++ b/docs/app/components/About.vue @@ -0,0 +1,34 @@ + \ No newline at end of file From 2c1f2c49a8cc5e2e96eec6b1e37806e6193b7906 Mon Sep 17 00:00:00 2001 From: Dan Pastori Date: Thu, 23 Oct 2025 07:45:43 -0500 Subject: [PATCH 222/304] Add new components and enhance existing documentation for improved user engagement and onboarding experience. Introduced 'FollowAlong', 'GetStarted', 'LandingSignup', and 'Testimonials' components to showcase community involvement and provide clear calls to action. Updated Nuxt configuration for Plausible analytics and refined CSS styles for better UI consistency. Updated OG images for social sharing. --- docs/app/app.config.ts | 32 ++++- docs/app/assets/css/main.css | 4 + docs/app/components/AppFooter.vue | 2 +- docs/app/components/AppHeader.vue | 124 ++++++++++-------- docs/app/components/FollowAlong.vue | 72 ++++++++++ docs/app/components/GetStarted.vue | 14 ++ docs/app/components/LandingSignup.vue | 41 ++++++ docs/app/components/Testimonials.vue | 39 ++++++ docs/app/pages/index.vue | 4 +- .../1.getting-started/4.choosing-an-image.md | 12 +- docs/content/index.md | 31 ++--- docs/nuxt.config.ts | 4 + docs/public/images/social-image.jpg | Bin 0 -> 229668 bytes .../images/testimonials/chris-fidao.png | Bin 0 -> 39044 bytes .../images/testimonials/johan-janssens.png | Bin 0 -> 36857 bytes docs/public/images/testimonials/ziga-zajc.png | Bin 0 -> 26483 bytes 16 files changed, 295 insertions(+), 84 deletions(-) create mode 100644 docs/app/components/FollowAlong.vue create mode 100644 docs/app/components/GetStarted.vue create mode 100644 docs/app/components/LandingSignup.vue create mode 100644 docs/app/components/Testimonials.vue create mode 100644 docs/public/images/social-image.jpg create mode 100644 docs/public/images/testimonials/chris-fidao.png create mode 100644 docs/public/images/testimonials/johan-janssens.png create mode 100644 docs/public/images/testimonials/ziga-zajc.png diff --git a/docs/app/app.config.ts b/docs/app/app.config.ts index 60977991b..e81b3495a 100644 --- a/docs/app/app.config.ts +++ b/docs/app/app.config.ts @@ -30,6 +30,36 @@ export default defineAppConfig({ 'Dockerfile': 'i-services-docker', 'nginx.conf': 'i-services-nginx', 'Terminal': 'i-ph-terminal-window-duotone' + }, + callout: { + slots: { + base: 'text-white [&_a]:text-white [&_a]:hover:text-white [&_a]:hover:border-white' + }, + variants: { + color: { + primary: { + base: '[&_a]:text-white [&_a]:hover:text-white [&_a]:hover:border-white' + }, + secondary: { + base: '[&_a]:text-white [&_a]:hover:text-white [&_a]:hover:border-white' + }, + success: { + base: '[&_a]:text-white [&_a]:hover:text-white [&_a]:hover:border-white' + }, + info: { + base: '[&_a]:text-white [&_a]:hover:text-white [&_a]:hover:border-white' + }, + warning: { + base: '[&_a]:text-white [&_a]:hover:text-white [&_a]:hover:border-white' + }, + error: { + base: '[&_a]:text-white [&_a]:hover:text-white [&_a]:hover:border-white' + }, + neutral: { + base: '[&_a]:text-white [&_a]:hover:text-white [&_a]:hover:border-white' + } + } + } } }, mode: 'dark', @@ -43,7 +73,7 @@ export default defineAppConfig({ root: 'border-t border-default', left: 'text-sm text-muted' } - } + }, }, seo: { siteName: 'PHP Docker Images (serversideup/php)' diff --git a/docs/app/assets/css/main.css b/docs/app/assets/css/main.css index af39d6b01..075cb4764 100644 --- a/docs/app/assets/css/main.css +++ b/docs/app/assets/css/main.css @@ -31,3 +31,7 @@ :root { --ui-container: var(--container-8xl); } + +ul ul ul { + border: none !important; +} \ No newline at end of file diff --git a/docs/app/components/AppFooter.vue b/docs/app/components/AppFooter.vue index f7a3c7fd9..947d8d20c 100644 --- a/docs/app/components/AppFooter.vue +++ b/docs/app/components/AppFooter.vue @@ -3,7 +3,7 @@ const { footer } = useAppConfig() diff --git a/docs/app/components/FollowAlong.vue b/docs/app/components/FollowAlong.vue new file mode 100644 index 000000000..7462446aa --- /dev/null +++ b/docs/app/components/FollowAlong.vue @@ -0,0 +1,72 @@ + \ No newline at end of file diff --git a/docs/app/components/GetStarted.vue b/docs/app/components/GetStarted.vue new file mode 100644 index 000000000..4495c6009 --- /dev/null +++ b/docs/app/components/GetStarted.vue @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/docs/app/components/LandingSignup.vue b/docs/app/components/LandingSignup.vue new file mode 100644 index 000000000..b3cea8fcc --- /dev/null +++ b/docs/app/components/LandingSignup.vue @@ -0,0 +1,41 @@ + + + \ No newline at end of file diff --git a/docs/app/components/Testimonials.vue b/docs/app/components/Testimonials.vue new file mode 100644 index 000000000..a8c69a889 --- /dev/null +++ b/docs/app/components/Testimonials.vue @@ -0,0 +1,39 @@ + \ No newline at end of file diff --git a/docs/app/pages/index.vue b/docs/app/pages/index.vue index 90d5cefbf..aeb520f34 100644 --- a/docs/app/pages/index.vue +++ b/docs/app/pages/index.vue @@ -23,7 +23,7 @@ useSeoMeta({ ogTitle: title, description, ogDescription: description, - ogImage: 'https://ui.nuxt.com/assets/templates/nuxt/docs-light.png', - twitterImage: 'https://ui.nuxt.com/assets/templates/nuxt/docs-light.png' + ogImage: 'images/social-image.jpg', + twitterImage: 'images/social-image.jpg' }) diff --git a/docs/content/docs/1.getting-started/4.choosing-an-image.md b/docs/content/docs/1.getting-started/4.choosing-an-image.md index e442deb83..f6f8c0b36 100644 --- a/docs/content/docs/1.getting-started/4.choosing-an-image.md +++ b/docs/content/docs/1.getting-started/4.choosing-an-image.md @@ -35,12 +35,12 @@ If you don't specify a variation, it defaults to `cli` and the latest supported Our most popular tags include: | ⚙️ Variation | 🚀 Version | | ------------ | ---------- | -| cli | **Debian Based** [![serversideup/php:8.4-cli](https://img.shields.io/docker/image-size/serversideup/php/8.4-cli?label=serversideup%2Fphp%3A8.4-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-cli&page=1&ordering=-name) [![serversideup/php:8.3-cli](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli?label=serversideup%2Fphp%3A8.3-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli&page=1&ordering=-name) [![serversideup/php:8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli?label=serversideup%2Fphp%3A8.2-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli&page=1&ordering=-name) [![serversideup/php:8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli?label=serversideup%2Fphp%3A8.1-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli&page=1&ordering=-name) [![serversideup/php:8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli?label=serversideup%2Fphp%3A8.0-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli&page=1&ordering=-name) [![serversideup/php:7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli?label=serversideup%2Fphp%3A7.4-cli){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-cli-alpine?label=serversideup%2Fphp%3A8.4-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.3-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli-alpine?label=serversideup%2Fphp%3A8.3-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.2-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli-alpine?label=serversideup%2Fphp%3A8.2-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.1-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli-alpine?label=serversideup%2Fphp%3A8.1-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.0-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli-alpine?label=serversideup%2Fphp%3A8.0-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli-alpine&page=1&ordering=-name) [![serversideup/php:7.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli-alpine?label=serversideup%2Fphp%3A7.4-cli-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli-alpine&page=1&ordering=-name) | -| fpm | **Debian Based** [![serversideup/php:8.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm?label=serversideup%2Fphp%3A8.4-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm&page=1&ordering=-name) [![serversideup/php:8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm?label=serversideup%2Fphp%3A8.3-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm&page=1&ordering=-name) [![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm&page=1&ordering=-name) [![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm&page=1&ordering=-name) [![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm&page=1&ordering=-name) [![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-alpine?label=serversideup%2Fphp%3A8.4-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.3-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-alpine?label=serversideup%2Fphp%3A8.3-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.2-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-alpine?label=serversideup%2Fphp%3A8.2-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.1-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-alpine?label=serversideup%2Fphp%3A8.1-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.0-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-alpine?label=serversideup%2Fphp%3A8.0-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-alpine&page=1&ordering=-name) [![serversideup/php:7.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-alpine?label=serversideup%2Fphp%3A7.4-fpm-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-alpine&page=1&ordering=-name) | -| fpm-apache | **Debian Based** [![serversideup/php:8.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-apache?label=serversideup%2Fphp%3A8.4-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-apache?label=serversideup%2Fphp%3A8.3-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache&page=1&ordering=-name) [![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache&page=1&ordering=-name) | -| fpm-nginx | **Debian Based** [![serversideup/php:8.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx?label=serversideup%2Fphp%3A8.4-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx?label=serversideup%2Fphp%3A8.3-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx&page=1&ordering=-name) [![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.4-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.3-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.3-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.2-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.2-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.1-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.1-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.0-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.0-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:7.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A7.4-fpm-nginx-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx-alpine&page=1&ordering=-name) | -| frankenphp | **Debian Based** [![serversideup/php:8.4-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp?label=serversideup%2Fphp%3A8.4-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp?label=serversideup%2Fphp%3A8.3-frankenphp){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp-alpine?label=serversideup%2Fphp%3A8.4-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp-alpine&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp-alpine?label=serversideup%2Fphp%3A8.3-frankenphp-alpine){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp-alpine&page=1&ordering=-name) | -| unit (deprecated) | **Debian Based** [![serversideup/php:8.4-unit](https://img.shields.io/docker/image-size/serversideup/php/8.4-unit?label=serversideup%2Fphp%3A8.4-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-unit&page=1&ordering=-name) [![serversideup/php:8.3-unit](https://img.shields.io/docker/image-size/serversideup/php/8.3-unit?label=serversideup%2Fphp%3A8.3-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-unit&page=1&ordering=-name) [![serversideup/php:8.2-unit](https://img.shields.io/docker/image-size/serversideup/php/8.2-unit?label=serversideup%2Fphp%3A8.2-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-unit&page=1&ordering=-name) [![serversideup/php:8.1-unit](https://img.shields.io/docker/image-size/serversideup/php/8.1-unit?label=serversideup%2Fphp%3A8.1-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-unit&page=1&ordering=-name) [![serversideup/php:8.0-unit](https://img.shields.io/docker/image-size/serversideup/php/8.0-unit?label=serversideup%2Fphp%3A8.0-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-unit&page=1&ordering=-name) [![serversideup/php:7.4-unit](https://img.shields.io/docker/image-size/serversideup/php/7.4-unit?label=serversideup%2Fphp%3A7.4-unit){.h-5.w-auto}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-unit&page=1&ordering=-name) | +| cli | **Debian Based** [![serversideup/php:8.4-cli](https://img.shields.io/docker/image-size/serversideup/php/8.4-cli?label=serversideup%2Fphp%3A8.4-cli){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-cli&page=1&ordering=-name) [![serversideup/php:8.3-cli](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli?label=serversideup%2Fphp%3A8.3-cli){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli&page=1&ordering=-name) [![serversideup/php:8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli?label=serversideup%2Fphp%3A8.2-cli){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli&page=1&ordering=-name) [![serversideup/php:8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli?label=serversideup%2Fphp%3A8.1-cli){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli&page=1&ordering=-name) [![serversideup/php:8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli?label=serversideup%2Fphp%3A8.0-cli){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli&page=1&ordering=-name) [![serversideup/php:7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli?label=serversideup%2Fphp%3A7.4-cli){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-cli-alpine?label=serversideup%2Fphp%3A8.4-cli-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.3-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli-alpine?label=serversideup%2Fphp%3A8.3-cli-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.2-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli-alpine?label=serversideup%2Fphp%3A8.2-cli-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.1-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli-alpine?label=serversideup%2Fphp%3A8.1-cli-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli-alpine&page=1&ordering=-name) [![serversideup/php:8.0-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli-alpine?label=serversideup%2Fphp%3A8.0-cli-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli-alpine&page=1&ordering=-name) [![serversideup/php:7.4-cli-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli-alpine?label=serversideup%2Fphp%3A7.4-cli-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli-alpine&page=1&ordering=-name) | +| fpm | **Debian Based** [![serversideup/php:8.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm?label=serversideup%2Fphp%3A8.4-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm&page=1&ordering=-name) [![serversideup/php:8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm?label=serversideup%2Fphp%3A8.3-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm&page=1&ordering=-name) [![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm&page=1&ordering=-name) [![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm&page=1&ordering=-name) [![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm&page=1&ordering=-name) [![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-alpine?label=serversideup%2Fphp%3A8.4-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.3-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-alpine?label=serversideup%2Fphp%3A8.3-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.2-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-alpine?label=serversideup%2Fphp%3A8.2-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.1-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-alpine?label=serversideup%2Fphp%3A8.1-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.0-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-alpine?label=serversideup%2Fphp%3A8.0-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-alpine&page=1&ordering=-name) [![serversideup/php:7.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-alpine?label=serversideup%2Fphp%3A7.4-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-alpine&page=1&ordering=-name) | +| fpm-apache | **Debian Based** [![serversideup/php:8.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-apache?label=serversideup%2Fphp%3A8.4-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-apache?label=serversideup%2Fphp%3A8.3-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache&page=1&ordering=-name) [![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache&page=1&ordering=-name) | +| fpm-nginx | **Debian Based** [![serversideup/php:8.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx?label=serversideup%2Fphp%3A8.4-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx?label=serversideup%2Fphp%3A8.3-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx&page=1&ordering=-name) [![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.4-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.3-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.3-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.2-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.2-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.1-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.1-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.0-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.0-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:7.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A7.4-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx-alpine&page=1&ordering=-name) | +| frankenphp | **Debian Based** [![serversideup/php:8.4-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp?label=serversideup%2Fphp%3A8.4-frankenphp){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp?label=serversideup%2Fphp%3A8.3-frankenphp){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp-alpine?label=serversideup%2Fphp%3A8.4-frankenphp-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp-alpine&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp-alpine?label=serversideup%2Fphp%3A8.3-frankenphp-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp-alpine&page=1&ordering=-name) | +| unit (deprecated) | **Debian Based** [![serversideup/php:8.4-unit](https://img.shields.io/docker/image-size/serversideup/php/8.4-unit?label=serversideup%2Fphp%3A8.4-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-unit&page=1&ordering=-name) [![serversideup/php:8.3-unit](https://img.shields.io/docker/image-size/serversideup/php/8.3-unit?label=serversideup%2Fphp%3A8.3-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-unit&page=1&ordering=-name) [![serversideup/php:8.2-unit](https://img.shields.io/docker/image-size/serversideup/php/8.2-unit?label=serversideup%2Fphp%3A8.2-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-unit&page=1&ordering=-name) [![serversideup/php:8.1-unit](https://img.shields.io/docker/image-size/serversideup/php/8.1-unit?label=serversideup%2Fphp%3A8.1-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-unit&page=1&ordering=-name) [![serversideup/php:8.0-unit](https://img.shields.io/docker/image-size/serversideup/php/8.0-unit?label=serversideup%2Fphp%3A8.0-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-unit&page=1&ordering=-name) [![serversideup/php:7.4-unit](https://img.shields.io/docker/image-size/serversideup/php/7.4-unit?label=serversideup%2Fphp%3A7.4-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-unit&page=1&ordering=-name) | ## The full tag schema Our tagging process is quite flexible, and it can become quite complex, but don't worry -- we've spent a ton of time applying sensible defaults to give you the best experience. diff --git a/docs/content/index.md b/docs/content/index.md index 34bf5d857..740f864a1 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -173,23 +173,18 @@ Horizon, queues, tasks, storage linking, migrations - these images handle it all --- :: -::u-page-section{class="dark:bg-gradient-to-b from-neutral-950 to-neutral-900"} - :::u-page-c-t-a - --- - links: - - label: Start building - to: '/docs/getting-started' - trailingIcon: i-lucide-arrow-right - - label: View on GitHub - to: 'https://github.com/nuxt-ui-templates/docs' - target: _blank - variant: subtle - icon: i-simple-icons-github - title: Ready to build an amazing documentation? - description: Join thousands of developers building with Nuxt and Nuxt UI. Get this template and start shipping today. - class: dark:bg-neutral-950 - --- +::u-page-section +#title +Built to run from Dev → Prod - :stars-bg - ::: +#description +Get the full benefits of containerization. Stop using containers in dev only. Works with Kubernetes, Docker Swarm and more. :: + +:get-started + +:testimonials + +:landing-signup + +:follow-along \ No newline at end of file diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index 7bcb79eb2..265d6043b 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -81,6 +81,10 @@ export default defineNuxtConfig({ env: process.env.NUXT_SITE_ENV || 'production' }, + plausible: { + apiHost: 'https://a.521dimensions.com' + }, + llms: { domain: 'https://serversideup.net/open-source/docker-php/', title: 'PHP Docker Images (serversideup/php)', diff --git a/docs/public/images/social-image.jpg b/docs/public/images/social-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9ad70ff90fafb180541913a1dc6db92d2b1e9912 GIT binary patch literal 229668 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
|<*oCk_I-K2 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

2Sk;#WFnjp9BY8w@y+C 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 z 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

7@VMJqqg**eyWb>sXjCgkv7HW}4ljgUQ5@Xo& zNPE9aFkCcLX$4HpQK&qdU{P6+d>Hw{e#;2QI4VE z5lBCZYO*YWwWO$sR+_nR0fQsM3h)>BeFLMTnr0-pt&22Asaz#D#-B?o414AE zd1c3?>PETJv?)pJuoP~6&0WRnpI_y8@yL+(!p}>ft<_RSr6u6r6xhP@f+5;uhVH+#+!-Ak!7y)1Pj?qNBL;Jq zPU9DU;ipiVuc^1x)0bo1(Km%1zHd9%*tF&PGGuOrnG5Zk)Eu6p^e!;f)D10js*?4V z$bo3!3I$`DamDUpzveV8mzK~+5>)CXWs*gM+C-A(`WZ5^;b*O!kvuaI(%IQrY}zVZ zQ%*5g7lm<#uUV$YD2b0}NT6=ovK42~PVqX|Qus4CKQpgh_1ROWamD4AX<@I#J}VUF zv^1}im6ZXEn)>Xy6si{-euVGH;aLlk^pv-hL}%B+j@Mr8eoq9ezFgT^UR)wq(u3ud z6?l?&E%qmrRE?M#YaO<%8N_qP=Tx9vV^;~TYADWLQ#wj%8YerQ- zHg4R&8`F)RoZKLKUox5$#-r)#qM)f@&{X6k`A_{ju4xmgsMic5;u~E{ z^oUohsSY7nt^7K$RojI$6okdDPRNW^cC)WLKTm`CqOsnS2v#~hyfYM+k@%{A`#AC> z+j4;<^3f&c0w13`LzZ_0S6)7bd4@a&N!)GQvYD)PMoWbH7$WE)N3n)x#l+I`^sahI zC^CTAogp(_((DJx0+3vG320_~WA0?^u4nMS1*GDm7;;qrx`tzfH={S?%akuQ`X)uh zMCuI&k@7O}5+$XhTw7G{tgG0IYQywGID1C&%0}?uBah?SomZlVI0~A>pH}Z|pr?xh z*QI7nvMI_^?7F;!jXZ;6CntH%-PqdOr}4;Qu3)f-(qLWUWP zQ7g&j(sHg;O7WJk^l96ZO9a2RT%y)2VQ8RVQ@R7iX>}9_drS*S&7ci+ZjXkhZF>Sm zs2w*Jaa$NVZ?@;Zgw%C-kd9k4{|J*0Y-^(!tJ(}CuV4My{99j@EPS3Uyb2bBixcB( zM;HTYVw|Mi@K867&&^l+-y)VUc( z>a%*IB&Ao93#DRabj@01xCx|djT9kB40BofpE3s6Khm$%gU|`mCnC%1I)DB=CMbCG z^9uy}DP4N;Aj{NV6B83kqK=G?W6RcUm^pV=Z_?IH8yJ&okn>70j8*2j=5h4Iaa^(M zYMeiPmgiMezcA19kiCBf^;=ouJkK5rC?St{Mg|(a}mb-NLwnw;yQQX zLIN^u)6(8tu>x)t4nAfVrHH|&0925r$&)JOJ_?X{k<+aJ%XP_QsH2ygBU8v|%O7cC z<~qTYsCBkAV~h<^i&29~MNT(8yqS5zfoL<$eGdiq0)?u8lO$HB&dp+Y^E!+(IRE9R zo`%*m>yt`Ybt3wGBZasVVURIM@{+UV^8AdH@*&2v-8+Oo-3Wo zYc)@a-zTP0+Cav17?hF`nBuoxHXp0Gzl(+L1^-?pOzr1g4++K5u4h;B`u6)A%Wt~# zxHM<^XQ-1eRIoO!AGliT-K62fVDD92r1Pko*dxtd8pB1J2x6hMWo`mVsX-~omplb-GJV@+XO0HfE!*}^M$l2~s<|R}iBmcOg&MYo#A0}ah{W!1Z-LwS@k^vxK(xmEo9V)jY&eZYcoqgV2uLO5= z_Br<*1!3Y(z-scM{k;8bEyScb%JGItV=`$hD*`=r;XFnMMld@^fyq+<28J~>Bh@-b z&z#2ia4!leDaIhkMjeYQG+o^jnnHc%)LD%6_aVJd#iqC${ayj5o;ZN%&wd$Ui5la% z6R6RHtG=d;%8AoBv2Q<8Z@P&D=rWo|PT}MeM@T8GgMZ=}%7rm>j}Ky^FsQtgR4oa2 zB}{#RxbzIQ-S+;?SliQ&G=*iy<_Xjn=P^z!cl79fY~8XEXHK8SFfr`{wVr5lsl80w z0*NSP+CE`5v|6?HQrQFxH+oYYP?H=`#klYAvI3wRQq&lW{$n!qp)DIehde*1l>j$~0x;#89HKq$FLE*n0c> zbtlCU1(F^x3L(~wj-iLTW&X%HR315mOnw~R-UFyUa{|i`pTO8s4Ly?#uK(pj7$}EG zPtPNsdk_VlZ{KM$>wAx)N3`#;85B-Zdk&RxNqL1#|I?WDo2b!wdE%Kv*mCWa=wfIr zSYRlM=lk@&XK=~74d~{23u~XF_L`ffx5)KgAjVymMzzF>LsD}nEw}mnEOBi=O}AK> ztl*!PX%X3B&sN3#VmnsGafqss;5?<(f|4LI_u8m^2Rbp;s$h1fP?>l4Eu6S-La3gd zQa{$D=akdF>Y*~v!x@vhD>ZH6if2-$V~b>cX3||+#28sZQ$sYq>%s8i0(0@`j@5BM zuj4g#2qfAjvaJu@A$4=6Kvq&?q&_c4?{tpgsBU_qk{(^Hm(e%S3)#?82(oFzdi9W` zHIF33^)!hL>NF`*ZeIIj!mPGK~^Q8Rsq38bqlv!qdE3TuSJ4?KpE zo*_h2=WzbcFJfrxCakUTGv_Mk9T`L`%X6O?#M~uo)f6o*l8U+MYDOP?oIP+DJv+9M z`J~tR{2`27bqPa7-I&??Fa`(m=-W;KIdBpUlEYO(iu7y^J~7_1bd#wP6Fz>FR-p&Q zrA3mZvnZ|~#n_sDm&!!`gk~v< zMiPiD6Gx?Qk`zitwnJVfYrZ{Y)N0z!RgP>wP;E)>2QR9XUQ9C?+wJg~Z+Sd#wavC% zNphW@GFlqT^E^9SM>^T@de6~~?;T2z!v9Y8NhS80CHoH-Brxfmk2 zZahVk_r<5FiBibcTG&D`-k{p(x_Uk8`7%;J@+wpomg(i4!IeMyX2$Or^P4(PhI0@L z)LiFR@)+9Ki(1%?bMO9S33P)$BF|u(C zjfEv@-9A)g0$R0#!IU9}J!xa$!(u2Tb%C9U1IhAwopNPzQMA98=OvjBuw0?s1V1u~ z0QxrTDT*iBvJ-Okzv7^o{<0G(y0H@(>9sB&E>q#ftrc&B0bT*cVPsUJa7Kc2lq3*M zl=2eZ#LO@@M|(zb^rEhBXJavAyG@#G=e7Gb@*Z2#&*lFpS3+T0St)V1=9FlZQnG0- zbZ@pmkUk)&lQ)7|qC9|cnw#Q4pVl;HnmqSX1C>ucf|Xg`*dxS4)-Ww<(1vx%aWn#x(cOm6CPN@KJ}%H&~eXdt)l?r8WwenX%2%gj*4ADD8%D zdtTD2NU|%l^rTWQC~5_yrHTD*ZPhtAay2Ozukr#NxOW%PnUb+;cg1G=$&4AvZ4zuH z0pLYyr(p_Cf#$kVHlYzopsO~@#3|H9#9h*MY5u8ak)EpK%$N6Jt+Y!di{E|yHuSym zRSaFFaCqtz#@F}h!W4)tSHc>xn2%S#?ls8t6*VlgMp^<;AV$)|%5e!}pQWn4ilM5M z7UD|ig#l&4g6UFlp>iefK|KWRZs#Ze!-ETuHLt!nhpufD~nj}XV7}*X4DHsY@mNS z{kIPzCqVsy^C&^0Azx^wfQu3X|1h z60ald<8=sBY~#Fcz1v=4oG6n{(C!X{fum%&`3sZ%tg!j<#+1xymW)WXk+!*+EM6Gf zHieQM;I(-sxxs?R#lg>&r3Ed|5=K@s zB1GK}4i0L#MOq#SvmTdXG#E}PEVnR!`~oq|QA{720*VDxF5N^*j$x-;Ux}xle1f5r zHFQjJ7-3BFO(a2c68~$NRO29@GFhq0{{$v{1lM}q)0xjv(ktGye$)WDv}1}4Wbk6s zl3eHg2qi+Hl%8CRNOqrNz9znR-H=14L|67zg&0luAhF}hos=aw>LEK{De`L zLtEdt*@MzB!n55;JkO4JGpnmR*mXs=AD$%jT_iXcYbf{kHuG+odV4Oi6U_*>7&QiU z7syvh%J7+)c^o}GP5x>ITgQhq;V*vhB+8FYqSEhU?T@_{)y=&qZW<$aN?~-{Ms!ij zsn&24QhhUtJ~qWXs-0bJN2kf~Y+a$!9fI>j;|FdgPHg(J+HNf_I^YJUn^kZI)tG3|grs_F5jrr%ed58mh278fZ zq@-T2YIdwfAUvZj1!uyN*6pZIj7~P^;``v)b2h4}<9GXr?X{RVZ=^~+irXMRG?hVp z!B76o+xM6z_rCV6GHtT0mhnuklk?c*bq1pw62Q$I*8FLQle~7`xeAHztul$7KQ9)v zcI}!~zrJTDG{hQY77SFQyPxqgsuj-A9pAie0=>m<3@$cNef|v2KX(jWBu2;I_G**| zNidA`A;*6ghlh|Ox#$OJYYL1vYl5vXg$Z7Jp-o@;C`qB#S%XoWF( zf>%U*at6)G^EgeycHQNdXuV-GCle;P$>tX|2#!>65#JfV04sI=&G%mX`9zp(xm)sg z&x|jYBk>C5b5zCxQ?Vk20y3UNs(f_MztRyPp?Xjf?S>9IL`Kcp;o|LWblual&n- zq+Pkh10}fZ+gW}}1xUwXYaeuhEjOY~P?NUQk)5Su%iulJ;!#h^etCe^oJX6tieA)P z)P^G#&xlY-8M`s51ls95UG0V@+-11|2IvHxoaC;Jo7nQMw_?5kbgv!4NWDh`=K=?% z`o*P&DNYI$B|(T;)j0a-0Hk(T`W?fWU^S0UkOZV%u*Wc^$_EwwGW zp8XwdiR#>NjwTRFB4v~I0Br%4rWd*WhL@p5IwJF$U5K(h3|=#se()5Y{n)3m(|a>A z8wOAbq<8x~MuS09QEfMm@$4fl7`|9woI<#m+;(QY(;HmnG1`W1osL3EY`Rg`rrCq4 z;YgKOB9U5>C2=4jDVxSb3W2JFgKW=rGt1piF2Z587fgQW@H8A??~ES`mhoMKG8>na z8*_khz%wKVuekauWa<&pCobUpgHMuh97kct26!8KG1S+sZoLd~1Vgti&9 zM>?__P}VRqqw6$CLGN{R{RV0T{=2t`1_e#RH$7KQVDRrB#<|Zvg3()Fjr_KLbV-$w zOq~ttrU$-!M`-Si6O>Mj>u}S>TI=G&y{BdS(yNw=O*xt5^y~d^h0&xmOp}X}V6zOa zG>zSCBiT>^e#=L*JgTO0Xy@pL4bXS_=@|1Yv_jm+PT8xg%g>f2@^(j-E` zJT;m9LRWfjdLG->Z$!SosCa8=atX~Z9n@yK>6cxK+S);68Dvi9x^%FC6j>-_mE1D* z)}%A2xrwng?4sLdZa z;B0SAhoCx5X1zu|57$sP(n=!VrYK3Zk<>#@$aQDr(N)=oGLt;cBu!6|UFBPF}hkfCih)=9mScB$pl`)7lk+84e< zs6->RcZI9VJ_q_7UZ6^?v9t0i+|>(?ST+zW5hD6%(R4jIdUBeO`))e$$OjBQb) zB(eRjQ0McsH+7QUbdd?XI3ZIhJQo{Wb$!P3Rb`{pi(itUbt!`FAY+_rI}J5uW(?Cr z=-gyk7#;efZRQncxvnrSna?RDhsosMu21@^SvU?DchsgCtYYTSI@2NzW2Ke1l#_Ll z4c}QD#K6JxSUxv{fj7P!#p|y`as4=5Ym=62X7jY&X_we0z1SUgazf#RI<}62y5;uf zn$3cQZT2i^Pc}h^up}Ij6#^%qN`t1%1P!+%9Zz?WT&+5^u-CijHTDSH_1W*$xj_u& zboCT*+4a{VvvCxU)~3<@vK=`1)B)96xoS(jY2p7Gv1Q8R)#dY@(Bxvk?*5*vNe5`x z=fjq^7iieE3H5zZi5k``i3+R9BFm(w*!)pN$uqM6q0(r;nIXfFNT|yutxHL`M7ARX ziXErnh8x+=>dEUn&7aqSS7c%3q@}v$?3uGDF+>wdFZJabQU}gr<>^CMcf*zR{zxY< z04=6Y?YrlyNj%d6z-pzonX+IyidSJe6#{$1-R@Jtwi?#G*1rf}c?MjeLyflu&Auja z2)qu{ckvCxYR%Aw2yIVGUuA_`xv7LlvmT{yhJf~y@4AI#YI*YzcD(D&sFOghp1i>5 zV+s`p>>GA|cHpg!@3}|k@nVXgQ|O#vg;D!&iq{ogcqN|#aZ<{`MTn$EP6Q~l^O!|Q z^!KI#;GvyQC>6~jrOx#ZtptbmM(NJhFK;D_tCLhy0Xj1n-0$ur-QXiP#;unm;Yi;g zy2;1Xj?G}|*<;9WS%>9~y~xu9x=E`}Q%FZZSEL~wmE+LFG?vKrj${2b4EadAhkn&&0v z)8QKlC&|>Vp57h|T`_@R$0)kDjN;^DPr;inqqS7l`c7+7l56Y~E_YvmHfPl-aEXw0 z{9Ep^8TMdeyDdXgP5QivCB;+8?lEReE=FjGmJ)lWy`krOiF>Itn=c|i^<*r6)ycxf z>KHVW=dtR?78thYLTIQ3 zKd=qff+R$uyU+4zfeushJ8pbi)ktM@WC5j{C79gs*eseME)6tuK(^h*qJ2%)*1+YQ z8|>7PNekmV#15n<*KX?rJU`KF=%6pm_d5y%tF&=bEiyHAHo(Bp0J^)f$nF@&NLLRQ zPMpPXu}{a%cowg93f0A)VJGfNxGKA5j)`~I>RHd<^l~+F6gFEYX$s>>LH9MmQuGXD zfTU(DST99`j|}HGrF9}K0FB!r2$zuOI2&=O9IkRZ&)_q|$hw;pCp*OeYSA`nSva0Y z({E~9A4z>)xUhic;WNl*8T)&~weT-pi&*CRNOWK9zHgXc7lV@^vUoUAQ<|~La3Pb@ zqY)Wd`oNUSYo&F{j=A&9`NKLMg4Glfs3&Oqy)!@^BBN6`piTNs^IT!bW6}Yl;ibWC z(+drZh@CJ&J4u@xCOFh~$`j$$O&cjbQMv~XgSCleT|zs8&UAi>^x@eQQO`bPCbaZo z*FjkPn65Qr$Zy|_{AC+(_UKWB^UK<}S2nC9OB#A7a*%xD2lXQqWrx^FU>IR?J$pwmL+f zM&yD}qgJG5<4+yA!c~_~YARBf^Qmc0ydS5vdPX`oOO}=&i8G+77L2r2DM z99YdHXBzrZ()KFBfQdH2DhI(xuaQOHdg}Uj5okI--NS?EyXsP`z3MXTfA}#%3SJjA znhe`;n;LNk90@z|R=f&-=~URz%vDSn1kbY^q19}bB&C4wr#%VwN4`b-)|-=7J*l)! z?eIMvz!XJxHlUhCPx+pf7g~Z;q_m}kqG$mf?h~33Zvi@TvZA;Hw3{{2KI^>EUHQD4 zFzQPJ_IWHURpC#sVCngDI`c2j;42u(_bpc>cr8NvGu#a7&<25H`CTfE=ut*#E`VfL zTCZ13f5rvIBO`2bolR-9mQ85}t>n~e+KFGVjc#kocS(Js%$ZI_X6o7e!h&jB>7IPy z+!Xo;#xOd*hQbk{tGi$laD>N86|DI+rdbi3AGJFe`evgT=EJnxc4P8@c8t~Ua6DFF zI~XrG@%HSE&#W6m&lOuy+B=VsvBlt$F&V+E`C@V0#*oS2bldF1IWoo}@^$n23Dhy? z=_n|5epK*mmP6o|QXhEN2PQxCXaA){X1!0mDVe??m(Yr1B`SR*)TSatB^CN)nx?_? zEtQ}l5TmL^t*3QBfT^PKUG}M+L>gNGaNt(#vZz}5DIN6Am*y~keug)`h-ddciq_P5 zy!pr8j8Y-MST@iU9vz^W@!@qQAVy|pvv{{UI6xLSbPH?lK&iZOL#?@rrA$8VXzo zX_wv8-%XQK$7^5xYFu^MWzq^2Lj&DdGu}@OBwcC5bW)3D3fdYxvLjZ62$d`aS!OA# z$3^r&;s;pG6emuG$^A+2n9V}*7=Z8Hunt{a2QhQ-IL3JnQW2i(>qk3I*~Yq2v^vDr zxxN}tw$70<2a(|cWgS}90?BhXyy~a-YYHM6_sJj~>{Mz&8R|gm4CQ7^1&{2iX57+B zUge9-dkksknJh^)eK6Qd0={C8BN-23aZA*6m9@rel(<)Hy&E}Esj!x!01@6ocOU9! zR!}`Nk1bbhN3eMSeQU=Rqv*I?8?g_p`tS6Xv|p&ja-oz`WnwLj(sq_I`8}Jqn;|p> zBr=w+DkCvkC}s<&m&$xLgJ+*Uh{qn=hX)@yh<(o-!py=7Hyv+Mu~&z4hv5SHa#;)% zdwBB-I9sNM35r-rcj1|n3kZ&!1F98#<$>o^&=<;0o%FC_ycd^VvH`ET`6k@(it8~p z+M;I5nF;rDPSWXCI(tdRMJAnSFXwcu(o*s23=!Y!%u~0eS-kdKe-DN)--;uDdp|PI zpG5wWbx3s;HL)Ock%+_*uyDn4m&yV(TZ!3?HO?5=0Di0ldNXw7xtK=mSD2(98U#-@ zTj5SWXGqR+r9wtGqc4`prZUq;1~I9(6r>ChnRrRY7HdCu=Y>T^Gx=yUyDm;>(sDfY z{9wBe>nL76Cq1S7YKUqp#`4j#2+z-B>#eUv_hxCzR!|KhW>&*!kuk%%i>+FA-5rDq zNmEEN>6Z4EigISOZgr)MLb{isq8id-?s;A^Dp%S*PCY+~g}Eg>{_s8=<>Ei~@Do@w zGLChHtd6OhPiJZ7`e_2o7{6iz`ui`%HQTo#Tv@?G`w!rm*$Af_KB}moK<8t}`Vp)p zxf>EQu5i;lc6188)E)(z)aujEy>J-QDQV?)S~nqlwi zgefT;!r)Q&nH|_pxycR(OsFO28Ny|nhGwLU7qz zTGpK~JwJymP2$}1hp~fkz_mAQMrvSK`$i-gt8qK0saY@UC|n)*9hzOMREP*MpRZIF zkXIX%*3Xx*EYiE(AwyZj>(REr12EP5`RK=sL|fD`i!UJqz*GFSpTw!v|T zW20*v!43Y5w4GFt;4KX)MJ9d9({apZG)ky5!K0_zf)&Sm<$RjjnmVoJUQuLJph{zC zh5+PbGM7w45KwM&xWx05#uE~CH-InFF(>NtNCt$p&xX}$^C!NYeHU1gQ%d(|xcj>p zu|NIDQ8a30T>a)BLcO<}g2T_&2KwfLU5As{X0RG>&txb_7J!)5f|x)*k1FGesW^wA zKwQ{OGa6QLoZ9IV|LYSt^xzZN*pwOB>gM5(7~4cxmY&PY-V}Pt8<7&ymyUu2(TNHHYn2Zp7%;H3+0`@c3C=fBh9$ zAafhW(V{6&^E$5G+DCjs=VfjIjj0)o_xgD3&;k6(qYvPb2Oq>4;q(kEjjhd%DtwU^ zU0F^mjN5n(aS|JJQ3yvBDY%!`CPnhxU|treM3 zX@a2)){1q+hTPw5I-^2na^aQR)>5dXIf%YCNWu>`o81VX{7pSMDUb~)1UbynyE-*B zg#ummFMaf0?Ao>tum0cO1#e=4pWB2PF^dGO%A6jV$XX}3)^r!&fNfdK`MFsXiaCuj zmdiBjsV+<%JB{W_9mgJc3Saotz1XsL7*}7u8QZt7M}dM7jL>%>)9hc@ri^6?9fAubEI)G?OEkCQ0FG|ih;XP6ixjLb*%U+L z$Pgzs)1Fq>#F7I?lbp-+LfUM$s&kCm)=)cM!ntS9;?rMw8c$tV!dFgBqe^LcdLr^y~iwqQqD4enB|L$l9sS?-w64*ZcvTlS*bI%>alb_#++$l$U>=m;eW`}q-_tYHgGgU!Xr!b`^iL5&w zAjpJv8J>-E%NX?Xxb&*aks0bSoDM;EtQx{-EG<6pO$a1Sp_EIMgg9Z(Q>i=#heov8 zB_wi9<-73w6VKpp|L0#IIy;A#T{?iD|Ebqt=+ez7l0=MkNC3Y##~YE!*kF8MigzQ~ zEMnRz2-5$PcucX5jS&YzmZfd?MP`m1+RaJqqkJc6+` z{EjY@Zfzhjxuz_ljKo~?lA%Z(+o1Q>k1gx_@t#XZarE>#e0kpy-20^`@xT`!#b>|t z46eMSAHV&7{X1N}bE^`{mby;_DvmnXdf#?Kbc{5=x_4q68+TobvnM7oI5K30v}VG({*;G6N|#T5-R7hr_#g^;=)lPh#{<>TxL`Hnf0nM4Hd!n`)id zP5z8MN39zWXuXgqYD<5tn#p{gFybnPU2~`KaT^*RjJ=0=lM8uZW^EZ)aSJK*EQE%ry3wS``re0AwC!0LdwMqMmX>c$QWdEQb6p`{9lxc0+$-+9j?s|4$& zTVJ0e;Jyus;Fx`-o`~*(P#Jjv?kO9Ka{*8>dtxqKDVlv3<27 zIs%7}pFlTF*5jXKOmIU#E_=&swcDLfbJI#?lfp|gJSfyiy_!@_O@W3vq$zENubm^1 z{?t8xgHL|+f1$s$fLmXFHEw+M<>=os2)}=bULwJMjw(Jd;h7+$=8#RK?HG+Vlwp|C zZykE$Z17nq65oV9JebT5Gf2VLO_R|*-_X#>P)|P|zW+g_WS%v@t7~}3m)qRIsXjYY&WjE^ow8u<4}WM+yPtQ#9aA#9+B?EjHcNG~(SfrCf!r7wLM zy?sS&*}6d!_I#Vo>()E6k%qtl5lPFIR!;Ot7LK05LG-=g7PVX(O-Uggvx!#5{!!&`S_ zh%qjR`3c=LnLs(Gz~KeoYB1ufK&nz!NFij4II!<1?)vb@aO9yc;fAdnaXmSkjS~aN z_U9DeM2l54D`jdL(+v=nmJayle;{3KlvktBMhsyYY|>J-uQ~TNI4qtJ62{!KF1ML5 zMyWzdIvv+4*uH)(`bj%9r|E@VI7^D7hSqWkzWiL6fwxjZJimna!VGHXE}(v44)Jmo z4blR>Cuzl|&9_Vj8_EY**PA75Si!j!X&5|2Q#pk%KKd+!^-H*Z7omepDr-u|z)*(| z&;x-CMMpxI!WNUz`@V1iOXp@V#*G$q=QZtI1U-VE^jquXr(B{YVB&&JnuxQh!#o+Dij(i%9xqw%`GEd zqQ;;X8i;#cYZ$E}s$WOo+PXSe@6)FDCb?=h!$JeMl9R^IL9I?Qwoc8EL7q;6=&{pl9CZl@$7o|B9X*m(8drNJtdtv= zJw1)d<0o+Z51+tXroHBe z`)D@l-gRY7Phc^L64pwYHcx_&B$(}j!J4T6Q^g~grB2+JlQ<|jIoaV-{E*|v%pIJ> zkyGb!j%5EiVx}nyvdqcuA``yEO(xek-rt8yWNNAiJ*2&W0p|sJxxHNYt2Pedp_9`* zwQj@}34)UYd*C4c^Y8uve(U!4QyD}ik$}{S?`Kz~#wb?hzqP7*VgtT>-fT?!>pzsJ()$Fkw*)6q zFtXMw3~iQ$1Nc&ZD19lk`-=@pgw70#X5kK2JmoulPA!=zsfvXIeoteXb|Io*9|L32;b?b-lvWX(r7V=r*Q1tB91Ubw4gxLWW>tiOK#zTSq z=PNk0P{l%Bn#iqCd}s>2SSP0r5K#9QQ^aN|;?5ZSRiEo)=tu->O-~PcbITwi@*(`k-~2yyHgwb?Cz)>NmBp3@QeojEM~9*?*so?S6Q{Ifuc1KS zr6M^_3r2ys3|-$4u;M{Wc#h_9@|HLL{N9%oEPcP{-%V+JpDTp$v~CbdRzM&nS)w`o zlonE&86?$lGJ5&rmQ2(A!f)aeTp`o*8q2_sR^4y(apBqhDMIg2C17-PWv0@;5VFdB9w@VR1S z%_31@3~VA_qzWRIe)`f;P&mGHYVXBz^uj6jzJD*T_T8kK?klz^R4ZyE zf2pBj>)VPOIR}}l_0S_v;B$ZXG{(5_Yr1@_$#-MTsMHol=nr!wMpN2e=( z47=j#<`R~xCheo8Iq=+ZODlL_eucYx28F1GD+l{9)4PZ>#6{OKtRqQ-)wqH*HHben zNNFgLf68eZ4IJ}rwX5)dO=7e>&8AEQt4j6+Nnja;oa06+GLXH#(Bei}#OMabAnQfO zNO_ZF2s*Wu&kZ=n7-KIr<(h#mY#yUP$MjIc3i_!z1wu3!Ug{xLa^UnVQauIyC7JZA z2D7+gT@M~U8sT%F{32fe%2(hOFWW@(-EX@pBZ*R|XD5Zn{Mk<~>zT)�Y;16%kw0 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 literal 0 HcmV?d00001 diff --git a/docs/public/images/testimonials/ziga-zajc.png b/docs/public/images/testimonials/ziga-zajc.png new file mode 100644 index 0000000000000000000000000000000000000000..1586c8ba4a8d860d9fed4fe2b4e04d3136ccd4b5 GIT binary patch literal 26483 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 eo8R Date: Thu, 23 Oct 2025 08:32:26 -0500 Subject: [PATCH 223/304] Update GitHub link in app configuration to point to the correct Docker PHP repository for improved user navigation. --- docs/app/app.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/app/app.config.ts b/docs/app/app.config.ts index e81b3495a..99957b4e1 100644 --- a/docs/app/app.config.ts +++ b/docs/app/app.config.ts @@ -163,7 +163,7 @@ export default defineAppConfig({ { icon: 'i-lucide-star', label: 'Star on GitHub', - to: 'https://github.com/serversideup/', + to: 'https://github.com/serversideup/docker-php', target: '_blank' }, { From d48b8b32bac2ff8f27042a64a661aa711becd8df Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 08:40:48 -0500 Subject: [PATCH 224/304] Update Laravel task scheduler, queue, horizon, and reverb documentation to promote Spin Pro for zero-downtime deployments, enhancing clarity and user experience. --- .../docs/3.framework-guides/1.laravel/2.task-scheduler.md | 2 +- docs/content/docs/3.framework-guides/1.laravel/3.queue.md | 4 ++-- docs/content/docs/3.framework-guides/1.laravel/4.horizon.md | 4 ++-- docs/content/docs/3.framework-guides/1.laravel/4.reverb.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md index 65c04d207..7c36a08ea 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md +++ b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md @@ -10,7 +10,7 @@ Running a Laravel task scheduler with Docker can be a little different from trad ## Important concepts ::tip{to="https://getspin.pro/docs/services/laravel-scheduler" target="_blank"} -If you want the easiest way to get up and running with Laravel Task Scheduler on a VPS using Docker and zero downtime deployments, we recommend using [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){target="_blank"}. +Want to skip the setup? [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){target="_blank"} handles Laravel schedulers on your VPS with Docker and zero-downtime deployments—all configured for you. :: 1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) diff --git a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md index d93de2674..26548b114 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md +++ b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md @@ -13,8 +13,8 @@ php artisan queue:work --tries=3 ``` ## Important concepts -::tip{to="https://getspin.pro/docs/services/laravel-queues" target="_blank"} -If you want the easiest way to get up and running with Laravel Queues on a VPS using Docker and zero downtime deployments, we recommend using [Spin Pro](https://getspin.pro/docs/services/laravel-queues){target="_blank"}. +::tip{to="https://getspin.pro/docs/services/laravel-scheduler" target="_blank"} +Want to skip the setup? [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){target="_blank"} handles Laravel schedulers on your VPS with Docker and zero-downtime deployments—all configured for you. :: 1. It's usually best to run the queue as a separate container (but using the same image) 1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md index c9d4c0a6d..1cd773482 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md +++ b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md @@ -13,8 +13,8 @@ php artisan horizon ``` ## Important concepts -::tip{to="https://getspin.pro/docs/services/laravel-horizon" target="_blank"} -If you want the easiest way to get up and running with Laravel Horizon on a VPS using Docker and zero downtime deployments, we recommend using [Spin Pro](https://getspin.pro/docs/services/laravel-horizon){target="_blank"}. +::tip{to="https://getspin.pro/docs/services/laravel-scheduler" target="_blank"} +Want to skip the setup? [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){target="_blank"} handles Laravel schedulers on your VPS with Docker and zero-downtime deployments—all configured for you. :: 1. In most cases, you probably want to run this as a separate container from your web container 1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md index 6e104f873..1c8613928 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md +++ b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md @@ -13,8 +13,8 @@ php artisan reverb:start ``` ## Important concepts -::tip{to="https://getspin.pro/docs/services/laravel-reverb" target="_blank"} -If you want the easiest way to get up and running with Laravel Reverb on a VPS using Docker and zero downtime deployments, we recommend using [Spin Pro](https://getspin.pro/docs/services/laravel-reverb){target="_blank"}. +::tip{to="https://getspin.pro/docs/services/laravel-scheduler" target="_blank"} +Want to skip the setup? [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){target="_blank"} handles Laravel schedulers on your VPS with Docker and zero-downtime deployments—all configured for you. :: 1. You will need to follow the [Laravel Reverb setup instructions](https://laravel.com/docs/12.x/reverb) to install the Laravel Reverb package into your Laravel application. 1. In most cases, you probably want to run this as a separate container from your web container From a840bd1d1d6ac03a32913f06ab57817bfe1f3fb7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 08:58:19 -0500 Subject: [PATCH 225/304] Enhance navigation processing in documentation layout by adding functions to check active paths and manage section expansion. This improves user experience by ensuring relevant sections are expanded based on the current route. --- docs/app/layouts/docs.vue | 53 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/docs/app/layouts/docs.vue b/docs/app/layouts/docs.vue index f2d1194e2..7e9b6919d 100644 --- a/docs/app/layouts/docs.vue +++ b/docs/app/layouts/docs.vue @@ -1,12 +1,59 @@ From 2ceda24deec639492a8c68af943d5ebb87c43806 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 09:09:41 -0500 Subject: [PATCH 226/304] Update social media links in documentation components to reflect the rebranding of Twitter to 'X', enhancing consistency across the site. Updated links in About.vue, FollowAlong.vue, and ServerSideUp.vue components, as well as in the About section of the documentation. --- docs/app/components/About.vue | 4 ++-- docs/app/components/FollowAlong.vue | 2 +- docs/app/components/ServerSideUp.vue | 2 +- docs/content/docs/1.getting-started/8.about.md | 3 +++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/app/components/About.vue b/docs/app/components/About.vue index 9619ebd8d..19e2931e1 100644 --- a/docs/app/components/About.vue +++ b/docs/app/components/About.vue @@ -3,7 +3,7 @@

Dan Pastori
- + @@ -18,7 +18,7 @@
Jay Rogers
- + diff --git a/docs/app/components/FollowAlong.vue b/docs/app/components/FollowAlong.vue index 7462446aa..d5baa5149 100644 --- a/docs/app/components/FollowAlong.vue +++ b/docs/app/components/FollowAlong.vue @@ -9,7 +9,7 @@
+ :to="'https://x.com/serversideup'" target="_blank">
diff --git a/docs/app/components/ServerSideUp.vue b/docs/app/components/ServerSideUp.vue index 19f17ed60..ed5e65528 100644 --- a/docs/app/components/ServerSideUp.vue +++ b/docs/app/components/ServerSideUp.vue @@ -172,7 +172,7 @@
- - + + + - Twitter + X (Twitter)

From 16285363d5521832169a1e293ff3bfea660ff145 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 09:44:11 -0500 Subject: [PATCH 231/304] Update documentation to change background color from neutral to black for improved visual consistency across multiple sections. --- docs/content/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/index.md b/docs/content/index.md index 740f864a1..17a62a2ec 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -4,7 +4,7 @@ seo: description: Production-ready PHP Docker images for Laravel, WordPress, and more. --- -::u-page-hero{class="dark:bg-neutral-950"} +::u-page-hero{class="dark:bg-black"} --- orientation: vertical --- @@ -45,7 +45,7 @@ Built upon the official PHP images, our production-ready serversideup/php images --- :: -::u-page-section{class="dark:bg-neutral-950"} +::u-page-section{class="dark:bg-black"} #title These images [give a lot more]{.text-pink-500} than other PHP Docker Images. @@ -159,7 +159,7 @@ These images [give a lot more]{.text-pink-500} than other PHP Docker Images. ::: :: -::u-page-section{class="dark:bg-neutral-950"} +::u-page-section{class="dark:bg-black"} #title Highly optimized for Laravel 💪 From 5e4ebcb01074554498a3b0d0d39e32c6f801046d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 10:20:20 -0500 Subject: [PATCH 232/304] Add documentation for installing additional PHP extensions in Docker images, including usage examples and best practices for security and performance. This enhances user guidance for customizing their PHP environment. --- .../2.installing-additional-php-extensions.md | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/content/docs/6.customizing-the-image/2.installing-additional-php-extensions.md diff --git a/docs/content/docs/6.customizing-the-image/2.installing-additional-php-extensions.md b/docs/content/docs/6.customizing-the-image/2.installing-additional-php-extensions.md new file mode 100644 index 000000000..093b43af5 --- /dev/null +++ b/docs/content/docs/6.customizing-the-image/2.installing-additional-php-extensions.md @@ -0,0 +1,115 @@ +--- +head.title: 'Installing additional PHP extensions - Docker PHP - Server Side Up' +description: 'Learn how to add any PHP extension that you need for your application.' +layout: docs +title: Installing PHP extensions +--- + +::lead-p +serversideup/php includes the [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer){target="_blank"} tool by default. This tool allows you to install almost any PHP module that you'll need. +:: + +## Default extensions +By default, we include a number of PHP extensions to get you up and running. You can learn more why we have certain defaults and what's all included on our default configurations page. + +:u-button{to="/docs/getting-started/default-configurations#default-php-extensions" label="Learn more about default extensions" aria-label="Learn more about default extensions" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## What extensions are supported? +Since we're using `install-php-extensions`, we have a wide support of extensions across many versions of PHP. You can find the full list of supported extensions on the project's README. + +:u-button{to="https://github.com/mlocati/docker-php-extension-installer#supported-php-extensions" label="View the supported extensions" aria-label="View the supported extensions" size="md" color="primary" variant="outline" target="_blank" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Installing extensions +Once you have your extensions ready for installation, you need to use `root` permissions to install them. In most cases, the best experience is to use a `Dockerfile` to do this while you package your application in a container. + +If you're not familiar with the concept of packaging your application for deployment, we recommend you to read our guide on how to do it. + +:u-button{to="/docs/deployment-and-production/packaging-your-app-for-deployment" label="Learn more about packaging your application for deployment" aria-label="Learn more about packaging your application for deployment" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### Preparing your Dockerfile +::warning +**Our images are unprivileged by default.** This means you'll need to switch to `root` to do "root things", then switch back to the `www-data` user. This ensures your container image is hardened against security vulnerabilities. +:: + +```dockerfile [Dockerfile] +# Choose our base image +FROM serversideup/php:8.4-fpm-nginx + +# Switch to root so we can do root things +USER root + +# Install the intl and bcmath extensions with root permissions +RUN install-php-extensions intl bcmath + +# Drop back to our unprivileged user +USER www-data +``` + +## Building images with Docker Compose +Here's a simple example with Docker Compose that builds an image with the `intl` and `bcmath` extensions. + +::code-tree{defaultValue="Dockerfile"} + +```dockerfile [Dockerfile] +# Choose our base image +FROM serversideup/php:8.4-fpm-nginx + +# Switch to root so we can do root things +USER root + +# Install the intl and bcmath extensions with root permissions +RUN install-php-extensions intl bcmath + +# Drop back to our unprivileged user +USER www-data +``` + +```yml [compose.yml] +services: + php: + # Use "build" instead of "image" + build: + # Use the Dockerfile in the current directory + context: . + dockerfile: Dockerfile + # Expose localhost:80 to NGINX's port 8080 + ports: + - 80:8080 + # Mount current directory to /var/www/html + volumes: + - ./:/var/www/html +``` + +```php [public/index.php] + +``` +:: + +Once we have our project ready, we can bring our container up with: + +::tip +We use the `--build` flag to tell Docker to rebuild the image from scratch. Good practice in development if you're making changes to your Dockerfile. +:: + +```bash [Terminal] +docker compose up --build +``` + +## Real-life example showing development to production +If you're looking for a more realistic example how this looks from development to production, check out our guide below. + +:u-button{to="/docs/deployment-and-production/development-to-production" label="Learn more about development to production" aria-label="Learn more about development to production" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Common PHP extensions that you might need +We compiled a list of extensions for you to reference. + +| Extension | Description | Why it's not included by default | +|-----------|-------------|-------------------------------- +[intl](https://www.php.net/manual/en/intro.intl.php){target="_blank"} | Internationalization functions, [used by Laravel for validating emails](https://laravel.com/docs/10.x/validation#rule-email){target="_blank"} with "DNS" or "spoof" validation. | Our tests showed this module will add about 40 MB of space to the Docker image, so we decided to not include it by default. | + +::tip{to="https://github.com/serversideup/docker-php/discussions/new?category=q-a" target="_blank"} +Don't see the extension you need? Having trouble? [Open a discussion on GitHub →](https://github.com/serversideup/docker-php/discussions/new?category=q-a) +:: From 6d78f9b529551a198cab590cceef02eef18ddaf1 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 10:32:35 -0500 Subject: [PATCH 233/304] Add a tip in the documentation encouraging users to support our work by purchasing products or sponsoring us on GitHub. This enhances community engagement and appreciation for our open source efforts. --- docs/content/docs/1.getting-started/8.about.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/content/docs/1.getting-started/8.about.md b/docs/content/docs/1.getting-started/8.about.md index eb9329ee3..a88d67a40 100644 --- a/docs/content/docs/1.getting-started/8.about.md +++ b/docs/content/docs/1.getting-started/8.about.md @@ -11,6 +11,10 @@ We're taking the extra effort to open source as much as we can. Not only could t ::about :: +::tip{to="https://serversideup.net/products/" target="_blank"} +If you appreciate our work, consider supporting us by buying our products or sponsoring us on GitHub. +:: + ## About Us We're [Dan](https://x.com/danpastori){target="_blank"} and [Jay](https://x.com/jaydrogers){target="_blank"} - a two person team with a passion for open source products. We created [Server Side Up](https://serversideup.net){target="_blank"} to help share what we learn. From 8c844c027a335a34e396cbb9c8d415d0d2434519 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 11:08:31 -0500 Subject: [PATCH 234/304] Update documentation for FrankenPHP to include a warning about known performance issues on Alpine, advising users to consider the Debian version for better stability. This enhances user awareness and guides them towards optimal choices for their PHP environment. --- docs/content/docs/1.getting-started/4.choosing-an-image.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/1.getting-started/4.choosing-an-image.md b/docs/content/docs/1.getting-started/4.choosing-an-image.md index ef026a644..2ce8ec862 100644 --- a/docs/content/docs/1.getting-started/4.choosing-an-image.md +++ b/docs/content/docs/1.getting-started/4.choosing-an-image.md @@ -39,7 +39,7 @@ Our most popular tags include: | fpm | **Debian Based** [![serversideup/php:8.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm?label=serversideup%2Fphp%3A8.4-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm&page=1&ordering=-name) [![serversideup/php:8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm?label=serversideup%2Fphp%3A8.3-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm&page=1&ordering=-name) [![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm&page=1&ordering=-name) [![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm&page=1&ordering=-name) [![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm&page=1&ordering=-name) [![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-alpine?label=serversideup%2Fphp%3A8.4-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.3-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-alpine?label=serversideup%2Fphp%3A8.3-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.2-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-alpine?label=serversideup%2Fphp%3A8.2-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.1-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-alpine?label=serversideup%2Fphp%3A8.1-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-alpine&page=1&ordering=-name) [![serversideup/php:8.0-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-alpine?label=serversideup%2Fphp%3A8.0-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-alpine&page=1&ordering=-name) [![serversideup/php:7.4-fpm-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-alpine?label=serversideup%2Fphp%3A7.4-fpm-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-alpine&page=1&ordering=-name) | | fpm-apache | **Debian Based** [![serversideup/php:8.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-apache?label=serversideup%2Fphp%3A8.4-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-apache?label=serversideup%2Fphp%3A8.3-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache&page=1&ordering=-name) [![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache&page=1&ordering=-name) [![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache&page=1&ordering=-name) | | fpm-nginx | **Debian Based** [![serversideup/php:8.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx?label=serversideup%2Fphp%3A8.4-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx?label=serversideup%2Fphp%3A8.3-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx&page=1&ordering=-name) [![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx&page=1&ordering=-name) [![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.4-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.3-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.3-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.2-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.2-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.1-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.1-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:8.0-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx-alpine?label=serversideup%2Fphp%3A8.0-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx-alpine&page=1&ordering=-name) [![serversideup/php:7.4-fpm-nginx-alpine](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx-alpine?label=serversideup%2Fphp%3A7.4-fpm-nginx-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx-alpine&page=1&ordering=-name) | -| frankenphp | **Debian Based** [![serversideup/php:8.4-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp?label=serversideup%2Fphp%3A8.4-frankenphp){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp?label=serversideup%2Fphp%3A8.3-frankenphp){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp&page=1&ordering=-name)
**Alpine Based** [![serversideup/php:8.4-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp-alpine?label=serversideup%2Fphp%3A8.4-frankenphp-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp-alpine&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp-alpine?label=serversideup%2Fphp%3A8.3-frankenphp-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp-alpine&page=1&ordering=-name) | +| frankenphp | **Debian Based** [![serversideup/php:8.4-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp?label=serversideup%2Fphp%3A8.4-frankenphp){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp?label=serversideup%2Fphp%3A8.3-frankenphp){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp&page=1&ordering=-name)
**Alpine Based**
⚠️ Some users are experiencing [known performance issues](https://frankenphp.dev/docs/known-issues/){target="_blank"} with FrankenPHP on Alpine. Consider using the Debian version.
[![serversideup/php:8.4-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.4-frankenphp-alpine?label=serversideup%2Fphp%3A8.4-frankenphp-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-frankenphp-alpine&page=1&ordering=-name) [![serversideup/php:8.3-frankenphp-alpine](https://img.shields.io/docker/image-size/serversideup/php/8.3-frankenphp-alpine?label=serversideup%2Fphp%3A8.3-frankenphp-alpine){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-frankenphp-alpine&page=1&ordering=-name) | | unit (deprecated) | **Debian Based** [![serversideup/php:8.4-unit](https://img.shields.io/docker/image-size/serversideup/php/8.4-unit?label=serversideup%2Fphp%3A8.4-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.4-unit&page=1&ordering=-name) [![serversideup/php:8.3-unit](https://img.shields.io/docker/image-size/serversideup/php/8.3-unit?label=serversideup%2Fphp%3A8.3-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.3-unit&page=1&ordering=-name) [![serversideup/php:8.2-unit](https://img.shields.io/docker/image-size/serversideup/php/8.2-unit?label=serversideup%2Fphp%3A8.2-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.2-unit&page=1&ordering=-name) [![serversideup/php:8.1-unit](https://img.shields.io/docker/image-size/serversideup/php/8.1-unit?label=serversideup%2Fphp%3A8.1-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.1-unit&page=1&ordering=-name) [![serversideup/php:8.0-unit](https://img.shields.io/docker/image-size/serversideup/php/8.0-unit?label=serversideup%2Fphp%3A8.0-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=8.0-unit&page=1&ordering=-name) [![serversideup/php:7.4-unit](https://img.shields.io/docker/image-size/serversideup/php/7.4-unit?label=serversideup%2Fphp%3A7.4-unit){.h-5.w-auto :zoom=false}](https://hub.docker.com/r/serversideup/php/tags?name=7.4-unit&page=1&ordering=-name) | ## The full tag schema From e0fa43e3e7394da798e5a7dc7bc10e1092090ddc Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 11:11:38 -0500 Subject: [PATCH 235/304] Update documentation for FPM variations to include enhanced tips linking to the Environment Variable Specification. This improves user guidance for environment variable customization across FPM-Apache, FPM-NGINX, and FPM sections. --- docs/content/docs/2.image-variations/fpm-apache.md | 4 ++-- docs/content/docs/2.image-variations/fpm-nginx.md | 4 ++-- docs/content/docs/2.image-variations/fpm.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/docs/2.image-variations/fpm-apache.md b/docs/content/docs/2.image-variations/fpm-apache.md index 9ed604cf9..07dfd4184 100644 --- a/docs/content/docs/2.image-variations/fpm-apache.md +++ b/docs/content/docs/2.image-variations/fpm-apache.md @@ -211,7 +211,7 @@ The FPM-Apache variation supports extensive customization through environment va | `SSL_PRIVATE_KEY_FILE` | `/etc/ssl/private/self-signed-web.key` | Path to SSL private key | | `HEALTHCHECK_PATH` | `/healthcheck` | Path for health check endpoint | -::tip +::tip{to="/docs/reference/environment-variable-specification"} For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). :: @@ -230,7 +230,7 @@ For a complete list of available environment variables, see the [Environment Var | `PHP_UPLOAD_MAX_FILE_SIZE` | `100M` | Maximum upload file size | | `PHP_POST_MAX_SIZE` | `100M` | Maximum POST request size | -::tip +::tip{to="/docs/reference/environment-variable-specification"} For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). :: diff --git a/docs/content/docs/2.image-variations/fpm-nginx.md b/docs/content/docs/2.image-variations/fpm-nginx.md index c44bed551..6b5785f37 100644 --- a/docs/content/docs/2.image-variations/fpm-nginx.md +++ b/docs/content/docs/2.image-variations/fpm-nginx.md @@ -215,7 +215,7 @@ The FPM-NGINX variation supports extensive customization through environment var | `SSL_PRIVATE_KEY_FILE` | `/etc/ssl/private/self-signed-web.key` | Path to SSL private key | | `HEALTHCHECK_PATH` | `/healthcheck` | Path for health check endpoint | -::tip +::tip{to="/docs/reference/environment-variable-specification"} For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). :: @@ -275,7 +275,7 @@ services: PHP_OPCACHE_ENABLE: "1" ``` -::note +::note{to="/docs/reference/environment-variable-specification"} These are just examples. Review the [Environment Variable Specification](/docs/reference/environment-variable-specification) for a complete list of available environment variables to match your needs. :: diff --git a/docs/content/docs/2.image-variations/fpm.md b/docs/content/docs/2.image-variations/fpm.md index 802027470..74e6989b4 100644 --- a/docs/content/docs/2.image-variations/fpm.md +++ b/docs/content/docs/2.image-variations/fpm.md @@ -188,7 +188,7 @@ The FPM variation supports extensive customization through environment variables | `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | | `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | -::tip +::tip{to="/docs/reference/environment-variable-specification"} For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). :: From 79dc2314e7cc0a945a9496047264fb2d76554faa Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 11:13:06 -0500 Subject: [PATCH 236/304] Update documentation for installing additional PHP extensions to include a direct link to the `install-php-extensions` project. This enhances clarity and provides users with easy access to the full list of supported extensions across PHP versions. --- .../2.installing-additional-php-extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/6.customizing-the-image/2.installing-additional-php-extensions.md b/docs/content/docs/6.customizing-the-image/2.installing-additional-php-extensions.md index 093b43af5..3c7517d40 100644 --- a/docs/content/docs/6.customizing-the-image/2.installing-additional-php-extensions.md +++ b/docs/content/docs/6.customizing-the-image/2.installing-additional-php-extensions.md @@ -15,7 +15,7 @@ By default, we include a number of PHP extensions to get you up and running. You :u-button{to="/docs/getting-started/default-configurations#default-php-extensions" label="Learn more about default extensions" aria-label="Learn more about default extensions" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} ## What extensions are supported? -Since we're using `install-php-extensions`, we have a wide support of extensions across many versions of PHP. You can find the full list of supported extensions on the project's README. +Since we're using [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer){target="_blank"}, we have a wide support of extensions across many versions of PHP. You can find the full list of supported extensions on the project's README. :u-button{to="https://github.com/mlocati/docker-php-extension-installer#supported-php-extensions" label="View the supported extensions" aria-label="View the supported extensions" size="md" color="primary" variant="outline" target="_blank" trailing-icon="i-lucide-arrow-right" class="font-bold"} From 4dba9d554c5b3624288f1ae1a1ac5914378c12d2 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 11:15:19 -0500 Subject: [PATCH 237/304] Link Unit warning --- docs/content/docs/2.image-variations/unit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/2.image-variations/unit.md b/docs/content/docs/2.image-variations/unit.md index 20c0917bd..82274e1fa 100644 --- a/docs/content/docs/2.image-variations/unit.md +++ b/docs/content/docs/2.image-variations/unit.md @@ -4,7 +4,7 @@ description: 'Unit was offered as a variation, but the entire project was archiv --- ## Unit was archived by NGINX and is no longer maintained -::caution +::caution{to="https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit" target="_blank"} In October 2025, NGINX stopped supporting NGINX Unit and archived the project. NGINX Unit will eventually be removed from our project. [See the official announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit){target="_blank"} :: From 828903906d317cdd6dd56ae39f909c03a480da43 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 23 Oct 2025 11:24:44 -0500 Subject: [PATCH 238/304] Updated reference on entrypoint script --- docs/content/docs/8.reference/2.command-reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/8.reference/2.command-reference.md b/docs/content/docs/8.reference/2.command-reference.md index 71a0cbed0..02263e4db 100644 --- a/docs/content/docs/8.reference/2.command-reference.md +++ b/docs/content/docs/8.reference/2.command-reference.md @@ -33,7 +33,7 @@ docker-php-serversideup-dep-install-debian git ``` ## docker-php-serversideup-entrypoint -For our images that **DO NOT use S6 Overlay**, this is our default entrypoint script. +Our default entrypoint script that is used for all images. ## docker-php-serversideup-install-php-ext-installer This is an internal helper script to shorten up the syntax for the installation of the PHP extension installer. From fdf834250621c99ef09807a1181b64a4c01df2a3 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 24 Oct 2025 09:50:15 -0500 Subject: [PATCH 239/304] Unit archive clarification --- docs/content/docs/2.image-variations/unit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/2.image-variations/unit.md b/docs/content/docs/2.image-variations/unit.md index 82274e1fa..972ca0fd0 100644 --- a/docs/content/docs/2.image-variations/unit.md +++ b/docs/content/docs/2.image-variations/unit.md @@ -3,7 +3,7 @@ title: Unit description: 'Unit was offered as a variation, but the entire project was archived in October 2025. This variation is no longer supported.' --- -## Unit was archived by NGINX and is no longer maintained +## Unit is no longer maintained by NGINX ::caution{to="https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit" target="_blank"} In October 2025, NGINX stopped supporting NGINX Unit and archived the project. NGINX Unit will eventually be removed from our project. [See the official announcement →](https://github.com/nginx/unit?tab=readme-ov-file#nginx-unit){target="_blank"} :: From 89c44d13a8e8205dbdfe151ac48fdf26e434c543 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 24 Oct 2025 10:08:21 -0500 Subject: [PATCH 240/304] Remove CADDY_GLOBAL_LOG_LEVEL because it doesn't exists. Fixes https://github.com/serversideup/docker-php/issues/573#issuecomment-3439676871 --- .../docs/8.reference/1.environment-variable-specification.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index 352fca842..5c6fa2a08 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -51,7 +51,6 @@ Setting environment variables all depends on what method you're using to run you `CADDY_HTTP_SERVER_ADDRESS`
*Default: "http://"*|Set the server address for HTTP. (
Official docs)|frankenphp `CADDY_HTTPS_SERVER_ADDRESS`
*Default: "https://"*|Set the server address for HTTPS. (Official docs)|frankenphp `CADDY_LOG_FORMAT`
*Default: "console"*|Set the format for the Caddy log. (Official docs)|frankenphp -`CADDY_GLOBAL_LOG_LEVEL`
*Default: "warn"*|Set the global log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_GLOBAL_LOG_LEVEL` takes precedence. (Official docs)|frankenphp `CADDY_SERVER_LOG_LEVEL`
*Default: "warn"*|Set the server log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_SERVER_LOG_LEVEL` takes precedence. (Official docs)|frankenphp `CADDY_LOG_OUTPUT`
*Default: "stdout"*|Set the output for the Caddy log. (Official docs)|frankenphp `CADDY_PHP_SERVER_OPTIONS`
*Default: ""*|Set PHP server options for the Caddy server. (Official docs)|frankenphp From c95e4ab3b4d9f9e538a69fde318c1952e1a58f64 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 24 Oct 2025 10:17:45 -0500 Subject: [PATCH 241/304] Enhance migration documentation by removing outdated section on default configurations and adding new sections on testing applications and related resources. This improves clarity and provides users with essential steps for a successful transition to serversideup/php images. --- .../1.migrating-from-official-php-images.md | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/5.guide/1.migrating-from-official-php-images.md b/docs/content/docs/5.guide/1.migrating-from-official-php-images.md index cdd616b5f..ce8a897ae 100644 --- a/docs/content/docs/5.guide/1.migrating-from-official-php-images.md +++ b/docs/content/docs/5.guide/1.migrating-from-official-php-images.md @@ -9,11 +9,6 @@ title: Migrating from official PHP images Migrating from the official PHP images to serversideup/php is easy because our images are based on the official PHP images. We just give you a "batteries included" experience that's ready for production. :: -## Understand our default configurations -Before you make the switch, you should understand our default configurations and make sure they line up with your requirements. As always, perform these changes on a test branch and use automated testing to ensure your application is working as expected. - -:u-button{to="/docs/getting-started/default-configurations" label="Learn more about our default configurations" aria-label="Learn more about our default configurations" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} - ## Key differences ::warning Because our images run as `www-data` by default, you may need to update file permissions for mounted volumes. @@ -46,7 +41,7 @@ Making the change will literally take you two seconds. ::steps{level="4"} #### Figure out which image you'd like to use -Review our [choosing an image](/docs/getting-started/choosing-an-image) guide to help you decide which image you'd like to use. +Review our [choosing an image](/docs/getting-started/choosing-an-image) guide to help you decide which image you'd like to use. Also, make sure our [default configurations](/docs/getting-started/default-configurations) satisfy your requirements. #### Update your `Dockerfile` or `docker-compose.yml` file :::tip @@ -86,4 +81,17 @@ services: ``` ::: -:: \ No newline at end of file +#### Test your application +Make sure to test your application to ensure it's working as expected. + +#### Deploy and enjoy! +Making the change is that simple. + +:: + +## Related resources +If you need to customize the base image, review our guides below: + +- [Changing common PHP settings](/docs/customizing-the-image/changing-common-php-settings) +- [Installing additional PHP extensions](/docs/customizing-the-image/installing-additional-php-extensions) +- [Startup scripts](/docs/customizing-the-image/adding-your-own-start-up-scripts) \ No newline at end of file From 96a1132e5427940184428dbb42b127b8513d0295 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 10:47:07 -0500 Subject: [PATCH 242/304] Fix typos in documentation: correct "confiugration" to "configuration" in .env.example and "ouput" to "output" in automations.md. This improves clarity and accuracy for users. --- docs/.env.example | 2 +- docs/content/docs/3.framework-guides/1.laravel/1.automations.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/.env.example b/docs/.env.example index 5b2ecd5c3..20f5b43aa 100644 --- a/docs/.env.example +++ b/docs/.env.example @@ -3,7 +3,7 @@ NUXT_SITE_URL=https://localhost:3000 NUXT_SITE_NAME="Docker PHP - Server Side Up" NUXT_SITE_ENV="development" -# Used for Nuxt App confiugration +# Used for Nuxt App configuration PLAUSIBLE_ENABLED=false BASE_PATH=http://localhost:3000/open-source/docker-php NUXT_APP_BASE_URL=/open-source/docker-php diff --git a/docs/content/docs/3.framework-guides/1.laravel/1.automations.md b/docs/content/docs/3.framework-guides/1.laravel/1.automations.md index 0dc20ed8a..e130c75a8 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/1.automations.md +++ b/docs/content/docs/3.framework-guides/1.laravel/1.automations.md @@ -151,7 +151,7 @@ In most cases, this is due to a bug in their application code that causes a migr If a failure occurs in the Laravel Automations script, it will exit with a non-zero exit code -- preventing the container from starting. :: -If you are experiencing issues, you can enable the `AUTORUN_DEBUG` environment variable to get more detailed ouput of what could be going wrong. +If you are experiencing issues, you can enable the `AUTORUN_DEBUG` environment variable to get more detailed output of what could be going wrong. If you need even more information, you can set `LOG_OUTPUT_LEVEL` to `debug` to get **A TON** of output of what's exactly happening. From f6607b7b864be2a47a5e107818a9d1abec1f58c1 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 11:55:16 -0500 Subject: [PATCH 243/304] Add structured comments to Caddyfile for improved readability and organization. This includes sections for global configuration, common snippets, and dynamic imports, enhancing clarity for users managing Caddy configurations. --- .../frankenphp/etc/frankenphp/Caddyfile | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index cdd989e2a..3a31f4027 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -1,5 +1,7 @@ +######################################################## +# Global Caddy configuration +######################################################## { - # Global Caddy configuration admin {$CADDY_ADMIN:off} import auto-https/{$CADDY_AUTO_HTTPS:off}.caddyfile @@ -55,9 +57,9 @@ fd00::/8 \ {$CADDY_GLOBAL_OPTIONS} } -(auto-https-off) { - tls {$SSL_CERTIFICATE_FILE} {$SSL_CERTIFICATE_KEY_FILE} -} +######################################################## +# Common snippets +######################################################## (auto-https-on) { # tls directive is not needed when auto_https is enabled @@ -150,8 +152,12 @@ fd00::/8 \ } } +######################################################## +# Dynamic imports +######################################################## + # Pull in the per-mode listeners (off|mixed|full) import ssl-mode/{$SSL_MODE:off}.caddyfile -# Add your web servers here +# Add additional Caddy configuration files from the caddyfile.d directory import caddyfile.d/*.caddyfile From ea324bf4538b7afe79a5a6d6c012cd2a3e0e670e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 12:57:53 -0500 Subject: [PATCH 244/304] Update Apache configuration to support customizable HTTP and HTTPS ports via environment variables. Added `APACHE_HTTP_PORT` and `APACHE_HTTPS_PORT` to documentation for improved user guidance on server configuration. --- .../docs/8.reference/1.environment-variable-specification.md | 2 ++ .../fpm-apache/etc/apache2/sites-available/ssl-full.conf | 4 ++-- .../fpm-apache/etc/apache2/sites-available/ssl-mixed.conf | 4 ++-- .../fpm-apache/etc/apache2/sites-available/ssl-off.conf | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index 5c6fa2a08..e127d8362 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -16,6 +16,8 @@ Setting environment variables all depends on what method you're using to run you **Variable Name**|**Description**|**Used in variation** :-----:|:-----:|:-----: `APACHE_DOCUMENT_ROOT`
*Default: "/var/www/html/public"*|Sets the directory from which Apache will serve files. (Official docs)|fpm-apache +`APACHE_HTTP_PORT`
*Default: "8080"*|Set the port for HTTP. (Official docs)|fpm-apache +`APACHE_HTTPS_PORT`
*Default: "8443"*|Set the port for HTTPS. (Official docs)|fpm-apache `APACHE_MAX_CONNECTIONS_PER_CHILD`
*Default: "0"*|Sets the limit on the number of connections that an individual child server process will handle.(Official docs)|fpm-apache `APACHE_MAX_REQUEST_WORKERS`
*Default: "150"*|Sets the limit on the number of simultaneous requests that will be served. (Official docs)|fpm-apache `APACHE_MAX_SPARE_THREADS`
*Default: "75"*|Maximum number of idle threads. (Official docs)|fpm-apache diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf index 180a477ae..8b82a15cc 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf @@ -1,4 +1,4 @@ - + # Configure ServerAdmin and ServerName ServerName localhost ServerAdmin webmaster@localhost @@ -28,6 +28,6 @@ - + Include /etc/apache2/vhost-templates/https.conf \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf index b3d46bfaf..568b1817b 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf @@ -1,7 +1,7 @@ - + Include /etc/apache2/vhost-templates/http.conf - + Include /etc/apache2/vhost-templates/https.conf \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-off.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-off.conf index 81c73ca06..2e353dbbc 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-off.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-off.conf @@ -1,3 +1,3 @@ - + Include /etc/apache2/vhost-templates/http.conf \ No newline at end of file From 641f8fbd1c6ea593ae44d16b7ea8e8bc6217c3e7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 13:01:18 -0500 Subject: [PATCH 245/304] Introduce environment variables for customizable Apache HTTP and HTTPS ports in Dockerfile. This enhances flexibility for users configuring server settings. --- src/variations/fpm-apache/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 0171a05bf..2e52e80a7 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -38,6 +38,8 @@ LABEL org.opencontainers.image.title="serversideup/php (fpm-apache)" \ org.opencontainers.image.licenses="GPL-3.0-or-later" ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ + APACHE_HTTP_PORT="8080" \ + APACHE_HTTPS_PORT="8443" \ APACHE_START_SERVERS="2" \ APACHE_MIN_SPARE_THREADS="10" \ APACHE_MAX_SPARE_THREADS="75" \ From b8d4b7e19b0f5fc993e5789dbfccc75fda9b1726 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 13:12:51 -0500 Subject: [PATCH 246/304] Update health check messages for Apache and NGINX to improve clarity during startup. Changed failure messages to indicate waiting for services to start, enhancing user experience during deployment. --- .../fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check | 2 +- .../fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check index 90cc5b6cd..119e0b4b4 100644 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check +++ b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check @@ -10,7 +10,7 @@ if is_online; then echo "✅ Apache + PHP-FPM is running correctly." exit 0 else - echo "❌ There seems to be a failure in checking the Apache + PHP-FPM." + echo "Health check waiting for Apache + PHP-FPM to start..." status_code=$(curl $curl_options -w "%{http_code}" "$healthcheck_url") echo "HTTP Status Code: $status_code" exit 1 diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index e718322f8..117d018c0 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -10,7 +10,7 @@ if is_online; then echo "✅ NGINX + PHP-FPM is running correctly." exit 0 else - echo "👉 NGINX + PHP-FPM is not online. Waiting for it to start..." + echo "Health check waiting for NGINX + PHP-FPM to start..." if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then status_code=$(curl $curl_options -w "%{http_code}" "$healthcheck_url") echo "HTTP Status Code: $status_code" From be547d1bda37ff3baf310d3a0732fa801fd8aa14 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 13:37:51 -0500 Subject: [PATCH 247/304] Refactor Apache and NGINX configurations to utilize environment variables for HTTP and HTTPS ports. This change enhances flexibility and consistency across configurations, allowing users to customize server settings more easily. Updated health check scripts to reflect the new port variables. --- src/variations/fpm-apache/Dockerfile | 2 +- src/variations/fpm-apache/etc/apache2/ports.conf | 6 +++--- .../fpm-apache/etc/apache2/sites-available/ssl-full.conf | 8 ++++---- .../fpm-apache/etc/apache2/sites-available/ssl-mixed.conf | 4 ++-- .../fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check | 2 +- src/variations/fpm-nginx/Dockerfile | 4 +++- .../fpm-nginx/etc/nginx/site-opts.d/http.conf.template | 4 ++-- .../fpm-nginx/etc/nginx/site-opts.d/https.conf.template | 4 ++-- .../fpm-nginx/etc/nginx/sites-available/ssl-full | 4 ++-- .../fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check | 2 +- 10 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 2e52e80a7..456458018 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -177,4 +177,4 @@ WORKDIR ${APP_BASE_DIR} CMD ["/init"] HEALTHCHECK --start-period=60s --start-interval=3s --interval=10s --timeout=3s --retries=3 \ - CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH || exit 1" ] + CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:${APACHE_HTTP_PORT}${HEALTHCHECK_PATH} || exit 1" ] diff --git a/src/variations/fpm-apache/etc/apache2/ports.conf b/src/variations/fpm-apache/etc/apache2/ports.conf index 5284a27e6..78d20efb0 100644 --- a/src/variations/fpm-apache/etc/apache2/ports.conf +++ b/src/variations/fpm-apache/etc/apache2/ports.conf @@ -1,9 +1,9 @@ -Listen 8080 +Listen ${APACHE_HTTP_PORT} - Listen 8443 + Listen ${APACHE_HTTPS_PORT} - Listen 8443 + Listen ${APACHE_HTTPS_PORT} \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf index 8b82a15cc..056ddde91 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf @@ -1,4 +1,4 @@ - + # Configure ServerAdmin and ServerName ServerName localhost ServerAdmin webmaster@localhost @@ -9,9 +9,9 @@ # Turn on rewrite engine RewriteEngine On - # Redirect traffic from localhost to https://localhost:8443 + # Redirect traffic from localhost to https://localhost:${APACHE_HTTPS_PORT} RewriteCond %{SERVER_NAME} =localhost - RewriteRule ^ https://%{SERVER_NAME}:8443%{REQUEST_URI} [END,NE,R=permanent] + RewriteRule ^ https://%{SERVER_NAME}:${APACHE_HTTPS_PORT}%{REQUEST_URI} [END,NE,R=permanent] # Redirect all other traffic to https://host:443 RewriteCond %{SERVER_NAME} !=localhost @@ -28,6 +28,6 @@ - + Include /etc/apache2/vhost-templates/https.conf \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf index 568b1817b..1e03cefd2 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf @@ -1,7 +1,7 @@ - + Include /etc/apache2/vhost-templates/http.conf - + Include /etc/apache2/vhost-templates/https.conf \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check index 119e0b4b4..0ad09772d 100644 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check +++ b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check @@ -1,6 +1,6 @@ #!/command/with-contenv sh curl_options="--fail --location --insecure --silent --show-error --output /dev/null" -healthcheck_url="http://localhost:8080${HEALTHCHECK_PATH}" +healthcheck_url="http://localhost:${APACHE_HTTP_PORT}${HEALTHCHECK_PATH}" is_online() { curl $curl_options "$healthcheck_url" diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index db2ff711e..84081ea61 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -124,6 +124,8 @@ ENV APP_BASE_DIR=/var/www/html \ NGINX_ERROR_LOG="/dev/stderr" \ NGINX_FASTCGI_BUFFERS="8 8k" \ NGINX_FASTCGI_BUFFER_SIZE="8k" \ + NGINX_HTTP_PORT="8080" \ + NGINX_HTTPS_PORT="8443" \ NGINX_LISTEN_IP_PROTOCOL="all" \ NGINX_SERVER_TOKENS=off \ NGINX_WEBROOT=/var/www/html/public \ @@ -259,4 +261,4 @@ WORKDIR ${APP_BASE_DIR} CMD ["/init"] HEALTHCHECK --start-period=60s --start-interval=3s --interval=10s --timeout=3s --retries=3 \ - CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH || exit 1" ] + CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:${NGINX_HTTP_PORT}${HEALTHCHECK_PATH} || exit 1" ] diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template index 341e4b188..1d6ee8e6d 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template @@ -1,5 +1,5 @@ -listen 8080 default_server; -listen [::]:8080 default_server; +listen ${NGINX_HTTP_PORT} default_server; +listen [::]:${NGINX_HTTP_PORT} default_server; root $NGINX_WEBROOT; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template index 78f024953..0685ac17c 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template @@ -1,5 +1,5 @@ -listen 8443 ssl default_server; -listen [::]:8443 ssl default_server; +listen ${NGINX_HTTPS_PORT} ssl default_server; +listen [::]:${NGINX_HTTPS_PORT} ssl default_server; http2 on; root $NGINX_WEBROOT; diff --git a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full index 850f5449b..df640dea0 100644 --- a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full +++ b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full @@ -1,8 +1,8 @@ # HTTP Redirect configuration # server { - listen 8080 default_server; - listen [::]:8080 default_server; + listen ${NGINX_HTTP_PORT} default_server; + listen [::]:${NGINX_HTTP_PORT} default_server; server_name _; diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index 117d018c0..28d87220e 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -1,6 +1,6 @@ #!/command/with-contenv sh curl_options="--fail --location --insecure --silent --show-error --output /dev/null" -healthcheck_url="http://localhost:8080${HEALTHCHECK_PATH}" +healthcheck_url="http://localhost:${NGINX_HTTP_PORT}${HEALTHCHECK_PATH}" is_online() { curl $curl_options "$healthcheck_url" From 750363b223c9c8d525e92eebff577ddb58ebf047 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 13:38:48 -0500 Subject: [PATCH 248/304] Update health check command in Dockerfile to use CADDY_HTTP_PORT environment variable for improved configurability. This change enhances the flexibility of health checks in the frankenphp variation, aligning with recent updates to server configurations. --- src/variations/frankenphp/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index b08b6e1c2..041a8f5b6 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -227,4 +227,4 @@ ENTRYPOINT ["docker-php-serversideup-entrypoint"] CMD ["frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"] HEALTHCHECK --start-period=60s --start-interval=3s --interval=10s --timeout=3s --retries=3 \ - CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH || exit 1" ] \ No newline at end of file + CMD [ "sh", "-c", "curl --insecure --silent --location --show-error --fail http://localhost:${CADDY_HTTP_PORT}${HEALTHCHECK_PATH} || exit 1" ] \ No newline at end of file From 9c61c452c896e3fb288ab1d7b1f1235503c2310b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 13:53:44 -0500 Subject: [PATCH 249/304] Add SSL full configuration template for NGINX and update entrypoint script to process it. This enhances the web server setup by providing a complete SSL configuration, improving security and flexibility for users deploying Laravel applications. --- src/s6/etc/entrypoint.d/10-init-webserver-config.sh | 1 + .../etc/nginx/sites-available/{ssl-full => ssl-full.template} | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename src/variations/fpm-nginx/etc/nginx/sites-available/{ssl-full => ssl-full.template} (91%) diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index 1b1652103..9a5ed8225 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -171,6 +171,7 @@ if [ "$DISABLE_DEFAULT_CONFIG" = false ]; then process_template /etc/nginx/nginx.conf.template /etc/nginx/nginx.conf process_template /etc/nginx/site-opts.d/http.conf.template /etc/nginx/site-opts.d/http.conf process_template /etc/nginx/site-opts.d/https.conf.template /etc/nginx/site-opts.d/https.conf + process_template /etc/nginx/sites-available/ssl-full.template /etc/nginx/sites-available/ssl-full # Configure NGINX IP listening protocol if NGINX is installed nginx_config_files="/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/https.conf /etc/nginx/sites-available/ssl-full" diff --git a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full.template similarity index 91% rename from src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full rename to src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full.template index df640dea0..c8841095f 100644 --- a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full +++ b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full.template @@ -23,7 +23,7 @@ server { if ($redirect_to_local_https) { access_log off; - return 301 https://localhost:8443$request_uri; + return 301 https://localhost:${NGINX_HTTPS_PORT}$request_uri; } return 301 https://$host$request_uri; From a34c6116705b8b8f5d6a15da466acc5e634b283a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 14:09:30 -0500 Subject: [PATCH 250/304] Update SSL generation script to improve clarity in Caddy Auto HTTPS notice. This change enhances user understanding by refining the messaging when SSL mode is off or when Caddy Auto HTTPS is enabled. --- src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh index b7f408b72..8f3525500 100644 --- a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh +++ b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh @@ -13,7 +13,7 @@ if [ "$SSL_MODE" = "off" ]; then echo "ℹ️ NOTICE ($script_name): SSL mode is off, so we won't generate a self-signed SSL key pair." return 0 elif [ "$CADDY_AUTO_HTTPS" != "off" ]; then - echo "ℹ️ NOTICE ($script_name): Caddy Auto HTTPS is enabled, so we won't generate a default, self-signed, SSL key pair." + echo "ℹ️ NOTICE ($script_name): Caddy Auto HTTPS is enabled, so we won't generate a self-signed SSL key pair." return 0 fi From cf1c66f997fe1d1a320bbedd81dec94be269a2ac Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 16:20:23 -0500 Subject: [PATCH 251/304] Refactor Dockerfile to create and set permissions for Composer cache directory. This change ensures the cache directory is properly established for improved performance and user experience in the frankenphp variation. --- src/variations/frankenphp/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 041a8f5b6..6d6bf82cb 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -37,10 +37,10 @@ RUN set -eux; \ # Set the image version echo "${REPOSITORY_BUILD_VERSION}" > /etc/serversideup-php-version; \ echo "${FRANKENPHP_VERSION}" > /etc/serversideup-php-frankenphp-version; - # \ - # # Make composer cache directory - # mkdir -p "${COMPOSER_HOME}"; \ - # chown -R www-data:www-data "${COMPOSER_HOME}"; + \ + # Make composer cache directory + mkdir -p "${COMPOSER_HOME}"; \ + chown -R www-data:www-data "${COMPOSER_HOME}"; #################### # Go Image From f0e9ec01fc22fc34c8b2f5664e96f5123bb3e4b8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 16:24:20 -0500 Subject: [PATCH 252/304] Enhance file permission settings in Docker image by including /config and /data directories. This update ensures proper ownership is applied to these directories, improving security and consistency in the frankenphp variation. --- .../local/bin/docker-php-serversideup-set-file-permissions | 4 ++++ src/variations/frankenphp/Dockerfile | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 4d039889b..8762ef1bd 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -212,6 +212,8 @@ case "$OS" in /composer /var/www /etc/ssl/private + /config + /data $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) @@ -276,6 +278,8 @@ case "$OS" in /composer /var/www /etc/ssl/private + /config + /data $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" ;; *) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 6d6bf82cb..bfaaa9b1f 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -40,7 +40,7 @@ RUN set -eux; \ \ # Make composer cache directory mkdir -p "${COMPOSER_HOME}"; \ - chown -R www-data:www-data "${COMPOSER_HOME}"; + docker-php-serversideup-set-file-permissions --owner www-data:www-data #################### # Go Image From 2c2cf78368809719d3f2ecb9911a0da2cdd984f8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 28 Oct 2025 16:31:26 -0500 Subject: [PATCH 253/304] Fixed syntax error and removed redundant code --- src/variations/frankenphp/Dockerfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index bfaaa9b1f..53dc0bdd5 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -36,11 +36,7 @@ RUN set -eux; \ \ # Set the image version echo "${REPOSITORY_BUILD_VERSION}" > /etc/serversideup-php-version; \ - echo "${FRANKENPHP_VERSION}" > /etc/serversideup-php-frankenphp-version; - \ - # Make composer cache directory - mkdir -p "${COMPOSER_HOME}"; \ - docker-php-serversideup-set-file-permissions --owner www-data:www-data + echo "${FRANKENPHP_VERSION}" > /etc/serversideup-php-frankenphp-version #################### # Go Image From d05acf70213972c7d6194a2ad2c88dbb8085d5c0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 29 Oct 2025 10:48:27 -0500 Subject: [PATCH 254/304] Refactor Caddyfile configurations to replace 'app-common' with 'php-app-common' for improved clarity and consistency. Re-added 'auto-https-off' snippet for explicit TLS configuration and enhanced healthcheck handling for both HTTP and HTTPS modes. --- .../frankenphp/etc/frankenphp/Caddyfile | 6 +++++- .../etc/frankenphp/ssl-mode/full.caddyfile | 18 ++++++++++++++---- .../etc/frankenphp/ssl-mode/mixed.caddyfile | 4 ++-- .../etc/frankenphp/ssl-mode/off.caddyfile | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index 3a31f4027..01bfceebf 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -61,12 +61,16 @@ fd00::/8 \ # Common snippets ######################################################## +(auto-https-off) { + tls {$SSL_CERTIFICATE_FILE} {$SSL_CERTIFICATE_KEY_FILE} +} + (auto-https-on) { # tls directive is not needed when auto_https is enabled } # Common app logic; reused across all modes -(app-common) { +(php-app-common) { root * {$CADDY_APP_PUBLIC_PATH:/var/www/html/public} encode zstd br gzip diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile index c80eca611..eeef2801e 100644 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile @@ -1,5 +1,5 @@ -{$CADDY_HTTP_SERVER_ADDRESS:http://} { - # Redirect localhost healthcheck requests to HTTPS with the correct port +# Healthcheck - HTTP: Redirect to HTTPS +http://localhost:{$CADDY_HTTP_PORT:8080} { @healthcheck { remote_ip 127.0.0.1/8 ::1 path /healthcheck # Caddy healthcheck endpoint @@ -7,13 +7,23 @@ } log_skip @healthcheck redir @healthcheck https://localhost:{$CADDY_HTTPS_PORT:8443}{uri} 308 +} + +# Healthcheck - HTTPS: Use self-signed certificate +https://localhost:{$CADDY_HTTPS_PORT:8443} { + tls {$HEALTHCHECK_SSL_CERTIFICATE_FILE:/etc/ssl/private/healthcheck-localhost.crt} {$HEALTHCHECK_SSL_PRIVATE_KEY_FILE:/etc/ssl/private/healthcheck-localhost.key} + import php-app-common + import security-https +} - # Redirect all other traffic to HTTPS (without explicit port) +# HTTP Traffic: Redirect to HTTPS (without explicit port) +{$CADDY_HTTP_SERVER_ADDRESS:http://} { redir https://{host}{uri} 308 } +# HTTPS Traffic {$CADDY_HTTPS_SERVER_ADDRESS:https://} { import auto-https-{$CADDY_AUTO_HTTPS:off} - import app-common + import php-app-common import security-https } diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile index 5d4a8c395..9dedb439b 100644 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/mixed.caddyfile @@ -1,9 +1,9 @@ {$CADDY_HTTP_SERVER_ADDRESS:http://} { - import app-common + import php-app-common } {$CADDY_HTTPS_SERVER_ADDRESS:https://} { import auto-https-{$CADDY_AUTO_HTTPS:off} - import app-common + import php-app-common import security-https } diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile index 8146ebe93..211c66ded 100644 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/off.caddyfile @@ -1,3 +1,3 @@ {$CADDY_HTTP_SERVER_ADDRESS:http://} { - import app-common + import php-app-common } From 73ab6eaee2cd28b2316610fec0698af2e50e3c7e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 29 Oct 2025 10:48:41 -0500 Subject: [PATCH 255/304] Enhance SSL generation script to include healthcheck SSL keypair creation. This update adds support for generating a self-signed SSL certificate and private key specifically for healthchecks, while also refining the default SSL keypair generation process for improved clarity and functionality. --- .../etc/entrypoint.d/10-generate-ssl.sh | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh index 8f3525500..141a56a34 100644 --- a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh +++ b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh @@ -8,13 +8,12 @@ script_name="generate-ssl" SSL_CERTIFICATE_FILE=${SSL_CERTIFICATE_FILE:-"/etc/ssl/private/self-signed-web.crt"} SSL_PRIVATE_KEY_FILE=${SSL_PRIVATE_KEY_FILE:-"/etc/ssl/private/self-signed-web.key"} SSL_MODE=${SSL_MODE:-"off"} +HEALTHCHECK_SSL_CERTIFICATE_FILE=${HEALTHCHECK_SSL_CERTIFICATE_FILE:-"/etc/ssl/private/healthcheck-localhost.crt"} +HEALTHCHECK_SSL_PRIVATE_KEY_FILE=${HEALTHCHECK_SSL_PRIVATE_KEY_FILE:-"/etc/ssl/private/healthcheck-localhost.key"} if [ "$SSL_MODE" = "off" ]; then echo "ℹ️ NOTICE ($script_name): SSL mode is off, so we won't generate a self-signed SSL key pair." return 0 -elif [ "$CADDY_AUTO_HTTPS" != "off" ]; then - echo "ℹ️ NOTICE ($script_name): Caddy Auto HTTPS is enabled, so we won't generate a self-signed SSL key pair." - return 0 fi if [ -z "$SSL_CERTIFICATE_FILE" ] || [ -z "$SSL_PRIVATE_KEY_FILE" ]; then @@ -22,8 +21,8 @@ if [ -z "$SSL_CERTIFICATE_FILE" ] || [ -z "$SSL_PRIVATE_KEY_FILE" ]; then return 1 fi -if ([ -f "$SSL_CERTIFICATE_FILE" ] && [ ! -f "$SSL_PRIVATE_KEY_FILE" ]) || - ([ ! -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]); then +if [ -f "$SSL_CERTIFICATE_FILE" ] && [ ! -f "$SSL_PRIVATE_KEY_FILE" ] || + [ ! -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]; then echo "🛑 ERROR ($script_name): Only one of the SSL certificate or private key exists. Check the SSL_CERTIFICATE_FILE and SSL_PRIVATE_KEY_FILE variables and try again." echo "🛑 ERROR ($script_name): SSL_CERTIFICATE_FILE: $SSL_CERTIFICATE_FILE" echo "🛑 ERROR ($script_name): SSL_PRIVATE_KEY_FILE: $SSL_PRIVATE_KEY_FILE" @@ -35,7 +34,19 @@ if [ -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]; then return 0 fi -echo "🔐 SSL Keypair not found. Generating self-signed SSL keypair..." -openssl req -x509 -subj "/C=US/ST=Wisconsin/L=Milwaukee/O=IT/CN=*.dev.test,*.test,*.gitpod.io,*.ngrok.io,*.nip.io" -nodes -newkey rsa:2048 -keyout "$SSL_PRIVATE_KEY_FILE" -out "$SSL_CERTIFICATE_FILE" -days 365 >/dev/null 2>&1 +echo "🔐 Generating self-signed Healthcheck SSL keypair..." +openssl req -x509 \ + -subj "/CN=localhost" \ + -nodes -newkey rsa:2048 \ + -keyout "$HEALTHCHECK_SSL_PRIVATE_KEY_FILE" \ + -out "$HEALTHCHECK_SSL_CERTIFICATE_FILE" \ + -days 365 >/dev/null 2>&1 +echo "🔐 Default SSL Keypair not found. Generating self-signed SSL keypair..." +openssl req -x509 \ + -subj "/CN=localhost" \ + -nodes -newkey rsa:2048 \ + -keyout "$SSL_PRIVATE_KEY_FILE" \ + -out "$SSL_CERTIFICATE_FILE" \ + -days 365 >/dev/null 2>&1 exit 0 \ No newline at end of file From aec64b5a7af724b7de955e6dc013a92f602107ac Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 29 Oct 2025 10:53:47 -0500 Subject: [PATCH 256/304] Refactor SSL generation script to improve logic for existing certificate checks. This change moves the check for existing SSL certificate and private key files to a more logical position, enhancing clarity and ensuring proper handling of self-signed SSL keypair generation. --- .../frankenphp/etc/entrypoint.d/10-generate-ssl.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh index 141a56a34..9cafd05a0 100644 --- a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh +++ b/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh @@ -29,11 +29,6 @@ if [ -f "$SSL_CERTIFICATE_FILE" ] && [ ! -f "$SSL_PRIVATE_KEY_FILE" ] || return 1 fi -if [ -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]; then - echo "ℹ️ NOTICE ($script_name): SSL certificate and private key already exist, so we'll use the existing files." - return 0 -fi - echo "🔐 Generating self-signed Healthcheck SSL keypair..." openssl req -x509 \ -subj "/CN=localhost" \ @@ -42,6 +37,11 @@ openssl req -x509 \ -out "$HEALTHCHECK_SSL_CERTIFICATE_FILE" \ -days 365 >/dev/null 2>&1 +if [ -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]; then + echo "ℹ️ NOTICE ($script_name): SSL certificate and private key already exist, so we'll use the existing files." + return 0 +fi + echo "🔐 Default SSL Keypair not found. Generating self-signed SSL keypair..." openssl req -x509 \ -subj "/CN=localhost" \ From 84888cb8c66bc94a1cf0e875a9dd4faf4d679059 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 30 Oct 2025 14:23:57 -0500 Subject: [PATCH 257/304] Enhance Apache health check script to conditionally log HTTP status code based on debug output level. This change improves debugging capabilities by allowing detailed status information only when the LOG_OUTPUT_LEVEL is set to debug. --- .../fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check index 0ad09772d..f2c6b6fc4 100644 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check +++ b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check @@ -11,7 +11,9 @@ if is_online; then exit 0 else echo "Health check waiting for Apache + PHP-FPM to start..." - status_code=$(curl $curl_options -w "%{http_code}" "$healthcheck_url") - echo "HTTP Status Code: $status_code" + if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then + status_code=$(curl $curl_options -w "%{http_code}" "$healthcheck_url") + echo "HTTP Status Code: $status_code" + fi exit 1 fi From 33a302e0d8a7ef678de36291c65dcea8e95ac163 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 30 Oct 2025 14:24:25 -0500 Subject: [PATCH 258/304] Refactor SSL handling by removing validation from webserver config scripts and introducing a dedicated SSL generation script. This change enhances clarity and modularity, allowing for better management of SSL certificates and keys, while ensuring proper handling of self-signed certificates for both general use and healthchecks. --- .../entrypoint.d/10-init-webserver-config.sh | 31 ------------------- .../etc/entrypoint.d/5-generate-ssl.sh} | 0 src/variations/fpm-apache/Dockerfile | 1 + src/variations/fpm-nginx/Dockerfile | 1 + src/variations/frankenphp/Dockerfile | 2 +- 5 files changed, 3 insertions(+), 32 deletions(-) rename src/{variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh => utilities-webservers/etc/entrypoint.d/5-generate-ssl.sh} (100%) diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index 9a5ed8225..a34608d75 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -99,10 +99,6 @@ enable_apache_site (){ # Transform to lowercase ssl_mode=$(echo "$ssl_mode" | tr '[:upper:]' '[:lower:]') - if [ "$ssl_mode" != "off" ]; then - validate_ssl - fi - # Enable the site if [ ! -e "$apache2_enabled_site_path/ssl-$ssl_mode.conf" ]; then echo "ℹ️ NOTICE ($script_name): Enabling Apache site with SSL '$ssl_mode'..." @@ -119,10 +115,6 @@ enable_nginx_site (){ # Transform to lowercase ssl_mode=$(echo "$ssl_mode" | tr '[:upper:]' '[:lower:]') - if [ "$ssl_mode" != "off" ]; then - validate_ssl - fi - # Link the site available to be the active site if [ -f "$default_nginx_site_config" ]; then echo "ℹ️ NOTICE ($script_name): $default_nginx_site_config already exists, so we'll use the provided configuration." @@ -135,29 +127,6 @@ enable_nginx_site (){ fi } -validate_ssl(){ - if [ -z "$SSL_CERTIFICATE_FILE" ] || [ -z "$SSL_PRIVATE_KEY_FILE" ]; then - echo "🛑 ERROR ($script_name): SSL_CERTIFICATE_FILE or SSL_PRIVATE_KEY_FILE is not set." - return 1 - fi - - if ([ -f "$SSL_CERTIFICATE_FILE" ] && [ ! -f "$SSL_PRIVATE_KEY_FILE" ]) || - ([ ! -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]); then - echo "🛑 ERROR ($script_name): Only one of the SSL certificate or private key exists. Check the SSL_CERTIFICATE_FILE and SSL_PRIVATE_KEY_FILE variables and try again." - echo "🛑 ERROR ($script_name): SSL_CERTIFICATE_FILE: $SSL_CERTIFICATE_FILE" - echo "🛑 ERROR ($script_name): SSL_PRIVATE_KEY_FILE: $SSL_PRIVATE_KEY_FILE" - return 1 - fi - - if [ -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]; then - echo "ℹ️ NOTICE ($script_name): SSL certificate and private key already exist, so we'll use the existing files." - return 0 - fi - - echo "🔐 SSL Keypair not found. Generating self-signed SSL keypair..." - openssl req -x509 -subj "/C=US/ST=Wisconsin/L=Milwaukee/O=IT/CN=*.dev.test,*.gitpod.io,*.ngrok.io,*.nip.io" -nodes -newkey rsa:2048 -keyout "$SSL_PRIVATE_KEY_FILE" -out "$SSL_CERTIFICATE_FILE" -days 365 >/dev/null 2>&1 -} - ########## # Main ########## diff --git a/src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh b/src/utilities-webservers/etc/entrypoint.d/5-generate-ssl.sh similarity index 100% rename from src/variations/frankenphp/etc/entrypoint.d/10-generate-ssl.sh rename to src/utilities-webservers/etc/entrypoint.d/5-generate-ssl.sh diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 456458018..37c5854f9 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -104,6 +104,7 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ # copy our scripts COPY --chmod=755 src/common/ / COPY --chmod=755 src/s6/ / +COPY --chmod=755 src/utilities-webservers/ / # copy s6-overlay from s6-build COPY --from=s6-build /opt/s6/ / diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index 84081ea61..e2693a7b7 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -178,6 +178,7 @@ ENV APP_BASE_DIR=/var/www/html \ # copy our scripts COPY --chmod=755 src/common/ / COPY --chmod=755 src/s6/ / +COPY --chmod=755 src/utilities-webservers/ / # copy s6-overlay from s6-build COPY --from=s6-build /opt/s6/ / diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 53dc0bdd5..8d69d3816 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -14,6 +14,7 @@ ARG FRANKENPHP_VERSION # copy our scripts COPY --chmod=755 src/common/ / +COPY --chmod=755 src/utilities-webservers/ / RUN set -eux; \ # Create directories @@ -184,7 +185,6 @@ COPY --from=frankenphp-build /usr/local/bin/frankenphp /usr/local/bin/frankenphp COPY --from=frankenphp-build /usr/local/lib/libwatcher* /usr/local/lib/ COPY src/variations/frankenphp/etc/frankenphp/ /etc/frankenphp/ -COPY src/variations/frankenphp/etc/entrypoint.d/ /etc/entrypoint.d/ RUN \ docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE}"; \ From 5037ba26eadc783ae1fdd328a012d3162175fe91 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 30 Oct 2025 14:34:50 -0500 Subject: [PATCH 259/304] Update healthcheck SSL configuration and paths in Docker setup. This change introduces dedicated SSL certificate and private key files for healthchecks, ensuring proper handling in the FrankenPHP variation. Additionally, it updates the Dockerfile and entrypoint scripts to reflect the new paths, enhancing security and modularity in SSL management. --- .../1.environment-variable-specification.md | 2 ++ ...cker-php-serversideup-set-file-permissions | 2 ++ .../etc/entrypoint.d/5-generate-ssl.sh | 21 +++++++++++-------- src/variations/frankenphp/Dockerfile | 1 + .../etc/frankenphp/ssl-mode/full.caddyfile | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index e127d8362..760ecc17e 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -63,6 +63,8 @@ Setting environment variables all depends on what method you're using to run you `DISABLE_DEFAULT_CONFIG`
*Default: "false"*|Get full customization of the image and disable all default configurations and automations.| all `FRANKENPHP_CONFIG`
*Default: ""*|Set the configuration for FrankenPHP. (Official docs)|frankenphp `HEALTHCHECK_PATH`
*Default: "/healthcheck"*|Set the path for the health check endpoint. (Official docs)|all (except `cli` and `frankenphp`) +`HEALTHCHECK_SSL_CERTIFICATE_FILE`
*Default: "/etc/ssl/healthcheck/localhost.crt"*|Set the path to the SSL certificate for the health check endpoint.| fpm-apache, fpm-nginx, frankenphp +`HEALTHCHECK_SSL_PRIVATE_KEY_FILE`
*Default: "/etc/ssl/healthcheck/localhost.key"*|Set the path to the SSL private key for the health check endpoint.| fpm-apache, fpm-nginx, frankenphp `LOG_OUTPUT_LEVEL`
*Default: "warn"*|Set your container output different verbosity levels: debug, info, off |all `NGINX_ACCESS_LOG`
*Default: "/dev/stdout"*|Set the default output stream for access log.|fpm-nginx `NGINX_ERROR_LOG`
*Default: "/dev/stderr"*|Set the default output stream for error log.|fpm-nginx diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 8762ef1bd..4a1de23a5 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -212,6 +212,7 @@ case "$OS" in /composer /var/www /etc/ssl/private + /etc/ssl/healthcheck /config /data $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" @@ -278,6 +279,7 @@ case "$OS" in /composer /var/www /etc/ssl/private + /etc/ssl/healthcheck /config /data $PHP_INI_DIR/conf.d/zzz-serversideup-docker-php-debug.ini" diff --git a/src/utilities-webservers/etc/entrypoint.d/5-generate-ssl.sh b/src/utilities-webservers/etc/entrypoint.d/5-generate-ssl.sh index 9cafd05a0..94369d171 100644 --- a/src/utilities-webservers/etc/entrypoint.d/5-generate-ssl.sh +++ b/src/utilities-webservers/etc/entrypoint.d/5-generate-ssl.sh @@ -8,8 +8,8 @@ script_name="generate-ssl" SSL_CERTIFICATE_FILE=${SSL_CERTIFICATE_FILE:-"/etc/ssl/private/self-signed-web.crt"} SSL_PRIVATE_KEY_FILE=${SSL_PRIVATE_KEY_FILE:-"/etc/ssl/private/self-signed-web.key"} SSL_MODE=${SSL_MODE:-"off"} -HEALTHCHECK_SSL_CERTIFICATE_FILE=${HEALTHCHECK_SSL_CERTIFICATE_FILE:-"/etc/ssl/private/healthcheck-localhost.crt"} -HEALTHCHECK_SSL_PRIVATE_KEY_FILE=${HEALTHCHECK_SSL_PRIVATE_KEY_FILE:-"/etc/ssl/private/healthcheck-localhost.key"} +HEALTHCHECK_SSL_CERTIFICATE_FILE=${HEALTHCHECK_SSL_CERTIFICATE_FILE:-"/etc/ssl/healthcheck/localhost.crt"} +HEALTHCHECK_SSL_PRIVATE_KEY_FILE=${HEALTHCHECK_SSL_PRIVATE_KEY_FILE:-"/etc/ssl/healthcheck/localhost.key"} if [ "$SSL_MODE" = "off" ]; then echo "ℹ️ NOTICE ($script_name): SSL mode is off, so we won't generate a self-signed SSL key pair." @@ -29,13 +29,16 @@ if [ -f "$SSL_CERTIFICATE_FILE" ] && [ ! -f "$SSL_PRIVATE_KEY_FILE" ] || return 1 fi -echo "🔐 Generating self-signed Healthcheck SSL keypair..." -openssl req -x509 \ - -subj "/CN=localhost" \ - -nodes -newkey rsa:2048 \ - -keyout "$HEALTHCHECK_SSL_PRIVATE_KEY_FILE" \ - -out "$HEALTHCHECK_SSL_CERTIFICATE_FILE" \ - -days 365 >/dev/null 2>&1 +# Generate self-signed Healthcheck SSL keypair for FrankenPHP only +if [ -d "/etc/frankenphp/" ]; then + echo "🔐 Generating self-signed Healthcheck SSL keypair..." + openssl req -x509 \ + -subj "/CN=localhost" \ + -nodes -newkey rsa:2048 \ + -keyout "$HEALTHCHECK_SSL_PRIVATE_KEY_FILE" \ + -out "$HEALTHCHECK_SSL_CERTIFICATE_FILE" \ + -days 365 >/dev/null 2>&1 +fi if [ -f "$SSL_CERTIFICATE_FILE" ] && [ -f "$SSL_PRIVATE_KEY_FILE" ]; then echo "ℹ️ NOTICE ($script_name): SSL certificate and private key already exist, so we'll use the existing files." diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 8d69d3816..5e2b519f4 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -20,6 +20,7 @@ RUN set -eux; \ # Create directories mkdir -p \ /var/www/html/public \ + /etc/ssl/healthcheck \ /config/caddy \ /data/caddy \ /etc/caddy \ diff --git a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile index eeef2801e..fd4b15b66 100644 --- a/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/ssl-mode/full.caddyfile @@ -11,7 +11,7 @@ http://localhost:{$CADDY_HTTP_PORT:8080} { # Healthcheck - HTTPS: Use self-signed certificate https://localhost:{$CADDY_HTTPS_PORT:8443} { - tls {$HEALTHCHECK_SSL_CERTIFICATE_FILE:/etc/ssl/private/healthcheck-localhost.crt} {$HEALTHCHECK_SSL_PRIVATE_KEY_FILE:/etc/ssl/private/healthcheck-localhost.key} + tls {$HEALTHCHECK_SSL_CERTIFICATE_FILE:/etc/ssl/healthcheck/localhost.crt} {$HEALTHCHECK_SSL_PRIVATE_KEY_FILE:/etc/ssl/healthcheck/localhost.key} import php-app-common import security-https } From 50f630cb2b0bd845f0a5dbae7111318ea1d78136 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 30 Oct 2025 14:56:13 -0500 Subject: [PATCH 260/304] fix: Set SSL_PRIVATE_KEY_FILE to proper name on FrankenPHP --- src/variations/frankenphp/Dockerfile | 2 +- src/variations/frankenphp/etc/frankenphp/Caddyfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 5e2b519f4..b5ed10167 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -175,7 +175,7 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ SHOW_WELCOME_MESSAGE=true \ SSL_MODE=off \ SSL_CERTIFICATE_FILE="/etc/ssl/private/self-signed-web.crt" \ - SSL_CERTIFICATE_KEY_FILE="/etc/ssl/private/self-signed-web.key" \ + SSL_PRIVATE_KEY_FILE="/etc/ssl/private/self-signed-web.key" \ XDG_CONFIG_HOME=/config \ XDG_DATA_HOME=/data diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index 01bfceebf..a785add5f 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -62,7 +62,7 @@ fd00::/8 \ ######################################################## (auto-https-off) { - tls {$SSL_CERTIFICATE_FILE} {$SSL_CERTIFICATE_KEY_FILE} + tls {$SSL_CERTIFICATE_FILE} {$SSL_PRIVATE_KEY_FILE} } (auto-https-on) { From 6af0abc574c3a197a944605b0bac6ff17781e843 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 30 Oct 2025 16:26:03 -0500 Subject: [PATCH 261/304] Add SSL configuration documentation for Docker PHP images --- .../4.configuring-ssl.md | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 docs/content/docs/4.deployment-and-production/4.configuring-ssl.md diff --git a/docs/content/docs/4.deployment-and-production/4.configuring-ssl.md b/docs/content/docs/4.deployment-and-production/4.configuring-ssl.md new file mode 100644 index 000000000..6252314aa --- /dev/null +++ b/docs/content/docs/4.deployment-and-production/4.configuring-ssl.md @@ -0,0 +1,250 @@ +--- +head.title: 'Configuring SSL - Docker PHP - Server Side Up' +description: 'Learn how to use SSL with the serversideup/php images.' +layout: docs +title: Configuring SSL +--- + +::lead-p +SSL encryption is natively supported in our images. With FrankenPHP, a trusted certificate can automatically be generated by Let's Encrypt. You can also bring your own certificates or have a self-signed certificate generated for you. +:: + +## Supported Variations +SSL is natively supported in the following variations: + +| Variation | SSL Support | Automated, Signed Certificate Support (via Let's Encrypt) | +|-----------|---------------------|---------------------| +| `cli` | ❌ No | - | +| `fpm` | ❌ No | - | +| `fpm-nginx` | ✅ Yes | ❌ No
(requires a reverse proxy in front of the container) | +| `fpm-apache` | ✅ Yes | ❌ No
(requires a reverse proxy in front of the container) | +| `frankenphp` | ✅ Yes | ✅ Yes | + +## SSL Modes +You can control SSL behavior with the `SSL_MODE` environment variable: + +| SSL Mode | Description | +|----------|-------------| +| `off` (default) | HTTP only. | +| `mixed` | HTTP and HTTPS. | +| `full` | HTTPS only. HTTP requests will be redirected to HTTPS. | + +## Choose How to Run SSL in Production + +You have a few options for running SSL in production: + +| Approach | Certificate Type | Management Type | Zero-Downtime Deployments | Minimal Number of Containers | +|----------|----------------|-----------------|-----------------|--------------------------------| +| Reverse Proxy (like Traefik or Caddy) | ✅ Trusted Certificate (via Let's Encrypt) | ✅ Automatic | ✅ Yes | ⚠️ 2 | +| FrankenPHP's built-in automatic HTTPS | ✅ Trusted Certificate (via Let's Encrypt) | ✅ Automatic | ❌ No | ✅ 1 | +| Bring Your Own Certificate | ✅ Trusted Certificate (through any vendor) | ❌ Manual | ❌ No | ✅ 1 | +| Self-signed | ❌ Self-signed Certificate | ✅ Automatic | ❌ No | ✅ 1 | + +### Reverse Proxy (recommended) +::tip +Reverse proxies don't just terminate SSL—they also give you zero-downtime with rolling updates. +:: +![Reverse Proxy](images/docs/reverse-proxy-ssl-zerodowntime.png){:zoom=false} +Our recommended approach is to use a reverse proxy like [Traefik](https://traefik.io/traefik/){target="_blank"} or [Caddy](https://caddyserver.com/){target="_blank"} that listens on ports 80 (HTTP) and 443 (HTTPS). The reverse proxy will forward traffic to your container on the non-privileged ports of 8080 (HTTP) or 8443 (HTTPS). + +Using a reverse proxy unlocks two major benefits: +1. Automatic SSL certificate management (via Let's Encrypt) +2. Zero-downtime deployments + +When you're running updates on containers, the reverse proxy stays online while updates are deployed to your containers in the background. Configuring a reverse proxy is outside the scope of this documentation, but you can reference the links below to learn more: + +- [Traefik](https://traefik.io/traefik/){target="_blank"} +- [Caddy](https://caddyserver.com/){target="_blank"} + +#### Use a Reverse Proxy When You Want... +- Zero-downtime deployments +- Automatic SSL certificate management (via Let's Encrypt) +- Load balancing + +If you want a simple way to run your own reverse proxy with zero-downtime deployments, consider using [Spin](https://serversideup.net/open-source/spin/){target="_blank"}. + +:u-button{to="https://serversideup.net/open-source/spin/" label="Learn more about Spin" aria-label="Learn more about Spin" target="_blank" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### FrankenPHP's Built-in Automatic HTTPS +::warning +Zero-downtime deployments are not possible with FrankenPHP's built-in automatic HTTPS. +:: + +FrankenPHP provides automated HTTPS through Caddy. To directly expose FrankenPHP to the internet, you'll need to configure the following: +1. Environment variables (for Caddy configuration) +2. Ports (for direct exposure of ports 80 and 443) +3. Volumes (for certificate files) + +#### Environment Variables +Configure the following environment variables: + +| Variable | Expected Value | Description | +|----------|----------------|----------------| +| `CADDY_AUTO_HTTPS`
*Default: "off"* | `on` | Turn on Caddy's [`auto_https`](https://caddyserver.com/docs/caddyfile/options#auto-https){target="_blank"} global directive.| +| `CADDY_HTTPS_SERVER_ADDRESS`
*Default: "https://"* | `example.com` or `https://example.com` | Set the [server address](https://caddyserver.com/docs/caddyfile/concepts#addresses){target="_blank"} for HTTPS. Pro tip: You can use `$APP_URL` from your `.env` file to set this value. | +| `SSL_MODE`
*Default: "off"* | `full` or `mixed` | Configure how Caddy handles HTTP and HTTPS requests.| + +#### Ports +Configure the following ports: + +| Ports to Publish | Description | +|----------------|----------------| +| `80` → `8080` | HTTP traffic will be proxied to the container on port 8080. | +| `443` → `8443` | HTTPS traffic will be proxied to the container on port 8443. | + +::note +Our port mapping remains `80:8080` and `443:8443` because our containers are **unprivileged** by default, meaning we cannot bind to ports less than 1024 (without additional modification). +:: + +#### Volumes +Configure the following volumes: + +| Container Directory to Mount | Description | +|--------------------------------|-------------| +| `/config` | Directory for Caddy's configuration files (such as Caddyfile or JSON) that must persist for settings to be retained. | +| `/data` | Directory where Caddy stores SSL/TLS certificates and CA information, required for automatic HTTPS to consistently function. | + +::note +The `config` and `data` volumes must have read/write permissions for the `www-data` user. Caddy will store its configuration and certificates in these volumes (and you want those to persist). +:: + +#### Example +Here's an example of directly exposing FrankenPHP to the internet with automatic HTTPS via Let's Encrypt: +::code-tree{defaultValue="compose.yml"} + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-frankenphp + ports: + - 80:8080 + - 443:8443 + environment: + CADDY_AUTO_HTTPS: "on" + CADDY_HTTPS_SERVER_ADDRESS: "https://example.com" + SSL_MODE: "full" + # Mount the current directory to /var/www/html + volumes: + - .:/var/www/html + - config:/config + - data:/data + + volumes: + config: + data: +``` + +```php [public/index.php] + +``` +:: + +#### Use FrankenPHP's Built-in Automatic HTTPS When You Want... +- To run your application and handle SSL termination all in one container +- A simple setup without needing zero-downtime deployments + +::note +You can achieve zero-downtime deployments with FrankenPHP by placing a reverse proxy in front of the container. +:: + +### Bringing Your Own Certificate +If automatic HTTPS isn't an option, you can provide your own certificate from a vendor like [ssls.com](https://www.ssls.com/){target="_blank"}. Ensure your certificate issuer provides certificates compatible with your web server in [PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail){target="_blank"}. + +To add your own certificate, mount the certificate files to the container: + +::tip +Set your private key file permissions to `600` (read/write for owner only). Incorrect permissions will cause errors when loading the private key. +:: + +::code-tree{defaultValue="compose.yml"} + +```yml [compose.yml]{8-10,13} +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - 80:8080 + - 443:8443 + environment: + SSL_MODE: "mixed" + SSL_PRIVATE_KEY_FILE: "/etc/ssl/custom/test-key.pem" + SSL_CERTIFICATE_FILE: "/etc/ssl/custom/test.pem" + volumes: + - .:/var/www/html/ + - ./certs/:/etc/ssl/custom/ +``` + +```php [public/index.php] + +``` + +```pem [certs/test-key.pem] +-----BEGIN PRIVATE KEY----- +EXAMPLE_PRIVATE_KEY_DO_NOT_USE +MIIEvQIBADANBgkqhkiG9w0BAQEFASCBKwggSjAgEAAoIBAQDExampleKeyData +ThisIsNotARealPrivateKeyAndShouldNotBeUsedInProduction123456789 +ReplaceThisWithYourActualPrivateKeyFile +-----END PRIVATE KEY----- +``` + +```pem [certs/test.pem] +-----BEGIN CERTIFICATE----- +EXAMPLE_CERTIFICATE_DO_NOT_USE +MIIEIDCCAwigAwIBAgIQCqH+3yBp80lQ9OVmbNmbRzANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0yMTA0MjkwMDAwMDBaFw0zMjA0MjgyMzU5NTlaMFsxCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAumQB+ILtbVLaKTeQeGviJLbfBxMIRZACMpbs +QFmylhSTSSpLc1bNPrRVWWVmv+Lt8i3HuLjPQF+3M2NzBWVYB7Gixgd13KZBquor +2W4Sj5SfR2onVzULfBy6SrwxfSTnnykA1NAzGLbGSukNkY4fO7N4V3C1mLGvL8H +-----END CERTIFICATE----- +``` +:: + +#### Use Your Own Certificates When You... +- Cannot use Let's Encrypt (corporate policy, network restrictions, etc.) +- Have a specific certificate vendor requirement +- Don't need zero-downtime deployments + +::note +You can also bring your own certificate and configure it with a reverse proxy to get zero-downtime deployments. +:: + +### Self-Signed Certificate +::warning +Self-signed certificates will display warnings in the browser. +:: + +While browsers will show warnings, self-signed certificates are useful for specific use cases, such as encrypting traffic between containers in a cluster. If you set `SSL_MODE` to `mixed` or `full` without providing a certificate at `$SSL_CERTIFICATE_FILE` and `$SSL_PRIVATE_KEY_FILE`, a self-signed certificate will be automatically generated. + +```yml [docker-compose.yml]{7-9} +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - 80:8080 + - 443:8443 + environment: + # Set SSL mode to "mixed" (HTTP + HTTPS) + SSL_MODE: "mixed" + volumes: + - .:/var/www/html +``` + +The above will generate a self-signed certificate and configure the server to listen on both HTTP (port 80) and HTTPS (port 443). + +#### Use a Self-Signed Certificate When You... +- Have a reverse proxy in front of the container handling SSL termination +- Need all traffic to be encrypted (even on the internal network between containers) + +::note +If you have a reverse proxy in front of the container handling SSL termination, you don't need to use `SSL_MODE` at all. You can configure your reverse proxy to communicate with your PHP container via HTTP (port 8080), eliminating the need to configure SSL within the container. +:: \ No newline at end of file From b4abf11c0a0349337426012ff79a45a4f1c3acf9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 30 Oct 2025 16:26:08 -0500 Subject: [PATCH 262/304] Add reverse proxy SSL zero downtime image for documentation --- .../docs/reverse-proxy-ssl-zerodowntime.png | Bin 0 -> 175652 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/public/images/docs/reverse-proxy-ssl-zerodowntime.png diff --git a/docs/public/images/docs/reverse-proxy-ssl-zerodowntime.png b/docs/public/images/docs/reverse-proxy-ssl-zerodowntime.png new file mode 100644 index 0000000000000000000000000000000000000000..6ab2ce8a5b2457fc8a2067c18f3accbcb1b694de GIT binary patch literal 175652 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 literal 0 HcmV?d00001 From 644dd4f09d5ea47dde77ff5a748896f98e381d87 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 09:33:32 -0500 Subject: [PATCH 263/304] Revise documentation for 'serversideup/php' to highlight its advantages over traditional PHP deployment methods. The update includes a comprehensive feature comparison, security optimizations, and streamlined configuration processes, emphasizing the benefits of using environment variables for customization. Additionally, the content has been restructured for clarity and improved readability. --- .../2.these-images-vs-others.md | 345 ++++++++++++++++-- 1 file changed, 308 insertions(+), 37 deletions(-) diff --git a/docs/content/docs/1.getting-started/2.these-images-vs-others.md b/docs/content/docs/1.getting-started/2.these-images-vs-others.md index 350476607..652d1c945 100644 --- a/docs/content/docs/1.getting-started/2.these-images-vs-others.md +++ b/docs/content/docs/1.getting-started/2.these-images-vs-others.md @@ -1,56 +1,327 @@ --- -title: These images vs others -description: 'Learn the important differences between serversideup/php and other PHP images.' +title: These Images vs Others +description: 'Discover why thousands of developers trust serversideup/php for their production PHP applications.' --- -| | **Official PHP Images** |**serversideup/php** | -|-------------------------|-------------------------|---------------------| -| Base Operating System | Debian, Alpine | Debian, Alpine | -| PHP Compilation | PHP Source Code | PHP Source Code (based on official PHP images) | -| Run PHP, pinned to the minor version | ✅ | ✅ | -| Multi-arch support | ✅ | ✅ | -| Init System | Docker CMD | Docker CMD or [S6-Overlay](https://github.com/just-containers/s6-overlay) | -| Published Registry| DockerHub | [DockerHub](https://hub.docker.com/r/serversideup/php), [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php) | -| Unprivileged by default | ❌ | ✅ | -| Variable-first configuration | ❌ | ✅ | -| Includes `composer` | ❌ | ✅ | -| Includes [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer) | ❌ | ✅ | -| Production-Ready by default| ❌ | ✅ | -| Built-in security optimizations | ❌ | ✅ | -| Optimized for Laravel & WordPress| ❌ | ✅ | -| NGINX + FPM variation| ❌ | ✅ | -| FrankenPHP variation| ❌ | ✅ | -| Native health checks | ❌ | ✅ | - -## Unprivileged by Default +## The Problem with Traditional PHP Deployment + +If you've ever deployed a PHP application to production, you've probably experienced one (or all) of these frustrations: + +- **"It works on my machine"** - Your local environment doesn't match production, leading to mysterious bugs that only appear after deployment +- **Configuration Hell** - Spending hours tweaking `php.ini`, `www.conf`, and web server configs across multiple servers +- **Security Vulnerabilities** - Running as root, outdated extensions, or misconfigured permissions exposing your application to attacks +- **Manual Server Management** - SSH-ing into servers to update PHP, install extensions, or troubleshoot issues +- **Inconsistent Environments** - Each server is slightly different, making debugging and scaling a nightmare +- **Framework-Specific Tweaks** - Researching and applying dozens of optimizations for Laravel or WordPress performance + +You're not alone. These are the exact problems that led us to create `serversideup/php`. + +## How serversideup/php Solves These Problems + +`serversideup/php` is built on the official PHP images but adds everything needed for real-world production use: + +- ✅ **Works Identically Everywhere** - Same container runs on your laptop, CI/CD, and production +- ✅ **Zero Configuration Required** - Production-ready defaults with simple environment variable customization +- ✅ **Secure by Default** - Runs as unprivileged user, hardened for the open internet +- ✅ **Batteries Included** - Composer, common extensions, and helpful utilities pre-installed +- ✅ **Framework Optimized** - Pre-configured for Laravel and WordPress best practices +- ✅ **Modern Architecture** - FrankenPHP, S6 Overlay, native health checks, and multi-process support + ::note -Running containers as `root` in the open internet is very dangerous. This is why we run our images as `www-data` by default. +**Trusted by the Community** - Over 1 million Docker image pulls and actively used by Laravel and PHP developers worldwide. +:: + +## Feature Comparison + +See how `serversideup/php` stacks up against other PHP deployment options: + +| Feature | **Traditional Server** | **Official PHP Images** | **serversideup/php** | +|---------|------------------------|-------------------------|----------------------| +| Consistent environments | ❌ | ✅ | ✅ | +| Easy to scale | ❌ | ✅ | ✅ | +| Base OS Options | Manual Setup | Debian, Alpine | Debian, Alpine | +| PHP Version Management | Manual Updates | Easy Upgrades | Easy Upgrades | +| Multi-arch support | ❌ | ✅ | ✅ | +| Production-ready defaults | ⚠️ Manual | ❌ | ✅ | +| Runs as non-root user | ⚠️ Manual | ❌ | ✅ | +| Variable-first configuration | ❌ | ❌ | ✅ | +| Includes `composer` | ⚠️ Manual | ❌ | ✅ | +| Includes `install-php-extensions` | ❌ | ❌ | ✅ | +| Built-in security hardening | ⚠️ Manual | ❌ | ✅ | +| Laravel & WordPress optimizations | ⚠️ Manual | ❌ | ✅ | +| NGINX + FPM variation | ⚠️ Manual | ❌ | ✅ | +| FrankenPHP support | ❌ | ❌ | ✅ | +| Native health checks | ⚠️ Manual | ❌ | ✅ | +| S6 Overlay init system | ❌ | ❌ | ✅ | +| Published Registries | N/A | DockerHub | [DockerHub](https://hub.docker.com/r/serversideup/php) + [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php) | + +## Key Advantages Explained + +### Security First: Unprivileged by Default + +::caution +Running containers as `root` in production is a critical security vulnerability. If your application is compromised, an attacker gains root access to your container which could lead to a full system compromise. :: -We believe in the principle of least privilege. Our images run as an unprivileged user by default. This means that if your application is compromised, the attacker will have a harder time escalating their privileges to the root user. +Our images run as the `www-data` user by default, following the principle of least privilege. This means: + +- **Limited Blast Radius** - If your application is compromised, don't have root privileges +- **Kubernetes Compatible** - Many Kubernetes clusters require non-root containers by policy +- **Production Best Practice** - Aligns with NIST and CIS security benchmarks + +We also include additional security hardening: +- Disabled dangerous PHP functions by default (but you control them) +- Proper file permissions out of the box +- CloudFlare trusted proxy support for accurate IP logging +- Regular security updates from official PHP base images + +### Performance Optimized + +Every image includes production-tuned defaults based on real-world PHP applications: + +**OPcache Configuration** +- Pre-configured for optimal memory usage and caching strategy +- Easily toggle between development and production modes +- Smart defaults that work for most applications + +**Process Management** +- PHP-FPM tuned for typical low resource usage for Laravel/WordPress workloads +- S6 Overlay for intelligent process supervision +- Graceful shutdown handling for zero-downtime deployments -Running unprivileged images also improves compatibility of running your containers in a Kubernetes environment, where running as root is not allowed. +**Modern Options** +- FrankenPHP support for incredible performance gains (2-3x faster than FPM in many claims made by developers) +- Native support for NGINX Unit +- HTTP/2 and HTTP/3 ready configurations -## Variable-first Configuration ::tip -Setting environment variables saves a ton of headache when customizing your container. View our [environment variable specification](/docs/reference/environment-variable-specification) for all the available variables. +Need to customize performance settings? Just set an environment variable like `PHP_OPCACHE_ENABLE=1` or `PHP_MEMORY_LIMIT=512M`. No config files needed. +:: + +### Developer Experience: Variable-First Configuration + +Stop editing config files. Stop rebuilding images for simple changes. Just set environment variables: + +```yml [compose.yml] {5-9,11-12} +services: + php: + image: serversideup/php:8.4-fpm-nginx + environment: + # Change any PHP setting with environment variables + PHP_MEMORY_LIMIT: "512M" + PHP_UPLOAD_MAX_FILE_SIZE: "100M" + PHP_MAX_EXECUTION_TIME: "180" + PHP_OPCACHE_ENABLE: "1" + + # Run Migrations, Storage Link, Caching, and more + AUTORUN_ENABLED: "true" +``` + +No Dockerfile modifications. No config file mounting. No image rebuilds. Just simple environment variables with production-ready defaults. + +:u-button{to="/docs/reference/environment-variable-specification" label="View all environment variables" aria-label="View all environment variables" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### Production-Ready Out of the Box + +Our images are built for the wild internet: + +**Security Hardening** +- Unprivileged user execution +- Disabled dangerous PHP functions (you control which ones) +- Proper file permissions and ownership +- Regular security updates + +**Monitoring & Observability** +- Built-in health check endpoints +- All logs to STDOUT/STDERR for centralized logging +- Compatible with Prometheus, DataDog, and other monitoring tools + +**Deployment Features** +- Zero-downtime deployment support +- Graceful shutdown handling +- Queue worker and scheduler support for Laravel +- Automatic migration running on container start (optional) + +**Infrastructure as Code** +- Version your entire PHP stack in a `compose.yml` file +- Reproduce every environment with 100% consistency +- Easy rollbacks to previous versions + +### Framework Optimized for Laravel & WordPress + +We've done the hard work of optimizing for PHP's most popular frameworks: + +**Laravel Automations** +```yaml [compose.yml] +services: + php: + image: serversideup/php:8.3-fpm-nginx + environment: + # Run migrations, storage link, caching, and more + AUTORUN_ENABLED: "true" +``` + +:u-button{to="/docs/framework-guides/laravel/automations" label="Learn More About Laravel Automations" aria-label="Learn More About Laravel Automations" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +**WordPress Optimizations** +- Pre-installed PHP extensions WordPress needs +- Optimized PHP settings for WordPress performance +- Support for popular WordPress hosting patterns + +**All Frameworks Welcome** + +While we optimize for Laravel and WordPress, our images work great with: +- Symfony +- CodeIgniter +- Drupal +- Joomla +- Custom PHP applications + +### 🛠️ Batteries Included + +Unlike the official PHP images, we include tools you'll actually use: + +**Composer** +- Pre-installed and ready to use +- No need to install it in every Dockerfile +- Supports Composer v2 for lightning-fast installs + +**install-php-extensions** +- The popular [mlocati/docker-php-extension-installer](https://github.com/mlocati/docker-php-extension-installer) included +- Install any PHP extension with a single command +- Handles all dependencies automatically + +```dockerfile [Dockerfile] +FROM serversideup/php:8.4-cli + +# Switch to root to install extensions +USER root + +# Install any PHP extension easily +RUN install-php-extensions redis imagick mongodb + +# Switch back to unprivileged user +USER www-data +``` + +**Modern Init System** +- S6 Overlay for our FPM-Apache and FPM-NGINX variations +- Proper process supervision in containers +- Better than Supervisor for containerized workloads +- Graceful handling of signals for zero-downtime deployments + +**Multiple Variations** +Choose the right tool for your use case: +- [`cli`](/docs/image-variations/cli) - Command-line scripts, Composer, CI/CD +- [`fpm`](/docs/image-variations/fpm) - Just PHP-FPM (bring your own web server) +- [`fpm-nginx`](/docs/image-variations/fpm-nginx) - PHP-FPM + NGINX (most popular) +- [`fpm-apache`](/docs/image-variations/fpm-apache) - PHP-FPM + Apache +- [`frankenphp`](/docs/image-variations/frankenphp) - Modern, incredibly fast (2-3x FPM performance) +- [`unit`](/docs/image-variations/unit) - NGINX Unit for high-performance applications (deprecated) + +## Real-World Impact + +Don't just take our word for it. Here's what developers are experiencing: + +::note +**By the Numbers** +- **1,000,000+** Docker image pulls +- **2,000+** GitHub stars +- **Active Community** with regular updates and contributions +- **Production-Proven** across startups to enterprise applications :: -Our design philosophy is built all around simplicity. The process of customizing the behavior of PHP is as simple as setting an environment variable. We took every common configuration option and set it up so you can change these values in a simple method, defaulting every single option to production-ready values. +**Time Savings** +- ⏱️ **Minutes vs Hours** - Go from zero to production-ready PHP in minutes, not hours of server configuration +- 🔄 **Consistent Deployments** - Eliminate "works on my machine" debugging sessions +- 📦 **Pre-configured** - Stop researching optimal PHP settings for Laravel + +**Better Security** +- 🛡️ **Hardened by Default** - Security best practices built-in, not bolted on +- 🔒 **Regular Updates** - Based on official PHP images with security patches +- 📋 **Audit Trail** - Infrastructure as code means every change is tracked + +**Happier Developers** +- 😊 **Simple Configuration** - Environment variables instead of config file archaeology +- 🚀 **Modern Tools** - FrankenPHP, native health checks, and container-native features +- 🤝 **Community Support** - Active community and comprehensive documentation + +## Making the Switch + +### From Traditional Servers (LAMP/LEMP) + +If you're currently managing PHP on traditional servers, the switch to containers might seem daunting, but it's easier than you think: + +**Benefits You'll Gain** +- Identical environments from development to production +- Scale horizontally by adding more containers +- Roll back bad deployments in seconds +- Version control your entire infrastructure + +See our quick start guide to run your first PHP container. +:u-button{to="/docs/getting-started/installation" label="Quick Start Guide" aria-label="Quick Start Guide" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} -:u-button{to="/docs/reference/environment-variable-specification" label="Learn more about environment variables" aria-label="Learn more about environment variables" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} +### From Official PHP Images -## Optimized for Laravel & WordPress -We did the hard work of customizing the official PHP image and giving you everything to get started with running PHP's most popular frameworks in seconds. We also hardened the images to help protect your PHP application from common attacks. +Already using Docker with official PHP images? Switching is trivially easy. -## Production-ready -We believe images should be ready for production and able to live in the open and wild Internet. We took our years of experience and tweaked these images to be performant and secure as possible. Having these values published online allows others to review, contribute, and improve the images -- giving you the best security and performance possible. +:u-button{to="/docs/guide/migrating-from-official-php-images" label="Official PHP Migration Guide" aria-label="Official PHP Migration Guide" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### From Other Docker Images + +Switching from Bitnami, custom images, or other PHP Docker images is straightforward: + +**Key Differences to Note** +- We run as `www-data` (UID 33) by default, not root +- Configuration via environment variables, not config file mounts +- Web root is `/var/www/html` by default +- All variations expose port `8080` (unprivileged port) + +**Migration Strategy** +1. Review our [environment variable specification](/docs/reference/environment-variable-specification) +2. Map your current config to environment variables +3. Test in development first +4. Switch image tag in production -## What is "S6 Overlay?" ::warning -Using **supervisor** to monitor multiple processes in a single container may lead to unintended consequences. This is why we use [S6 Overlay](/docs/guide/using-s6-overlay) to monitor multiple processes in a single container. +Always test in a development or staging environment first to ensure your application works correctly with the new image. +:: + +## Ready to Get Started? + +You're just minutes away from a better PHP deployment experience. + +::steps + +### Choose Your Path + +**Quick Start (New Projects)** +Follow our installation guide to create your first PHP app with Docker in under 5 minutes. + +:u-button{to="/docs/getting-started/installation" label="Quick Start Guide" aria-label="Quick Start Guide" size="md" color="primary" variant="solid" trailing-icon="i-lucide-rocket" class="font-bold"} + +**Migration Guide (Existing Apps)** +Already have a PHP application? Learn how to containerize it with serversideup/php. + +:u-button{to="/docs/guide/migrating-from-official-php-images" label="Migration Guides" aria-label="Migration Guides" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### Choose an Image Variation + +Not sure which image variation is right for you? We have a guide for that. + +:u-button{to="/docs/getting-started/choosing-an-image" label="Choosing an Image" aria-label="Choosing an Image" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + :: -[S6 Overlay](https://github.com/just-containers/s6-overlay){target="_blank"} is a process supervisor designed for containerization from the ground up. It's a modern alternative to [Supervisor (aka Supervisord)](https://supervisord.org/){target="_blank"}. We only use this in our `serversideup/php:*-fpm-apache` and `serversideup/php:*-fpm-nginx` images, as they require two processes to run a service. +## Questions? + +We're here to help! Check out these resources: -:u-button{to="/docs/guide/using-s6-overlay" label="Learn more about S6 Overlay" aria-label="Learn more about S6 Overlay" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file +- 📚 [Full Documentation](/docs) - Comprehensive guides and references +- 💬 [GitHub Discussions](https://github.com/serversideup/docker-php/discussions) - Ask questions and share experiences +- 🐛 [GitHub Issues](https://github.com/serversideup/docker-php/issues) - Report bugs or request features +- 🗣️ [Discord](https://serversideup.net/discord) - Join our community and get help from the team and other developers. + +::tip +Join our community! Star us on [GitHub](https://github.com/serversideup/docker-php) and follow updates. +:: \ No newline at end of file From 4c922f2b00261c1a83203399bb6277d578a0a709 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 09:34:05 -0500 Subject: [PATCH 264/304] Cleaned up title --- docs/content/docs/1.getting-started/2.these-images-vs-others.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/1.getting-started/2.these-images-vs-others.md b/docs/content/docs/1.getting-started/2.these-images-vs-others.md index 652d1c945..d3f44097d 100644 --- a/docs/content/docs/1.getting-started/2.these-images-vs-others.md +++ b/docs/content/docs/1.getting-started/2.these-images-vs-others.md @@ -177,7 +177,7 @@ While we optimize for Laravel and WordPress, our images work great with: - Joomla - Custom PHP applications -### 🛠️ Batteries Included +### Batteries Included Unlike the official PHP images, we include tools you'll actually use: From ab7363cff9f99c3939e909f6a947b8cba10029fa Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 09:56:37 -0500 Subject: [PATCH 265/304] Add 'Getting Help' documentation to provide users with resources for community and professional support options. The guide includes sections on GitHub Discussions, Discord Community, GitHub Issues, and professional consulting services, aimed at helping users troubleshoot and seek assistance effectively. --- .../docs/7.troubleshooting/2.getting-help.md | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 docs/content/docs/7.troubleshooting/2.getting-help.md diff --git a/docs/content/docs/7.troubleshooting/2.getting-help.md b/docs/content/docs/7.troubleshooting/2.getting-help.md new file mode 100644 index 000000000..adaa0b78a --- /dev/null +++ b/docs/content/docs/7.troubleshooting/2.getting-help.md @@ -0,0 +1,123 @@ +--- +title: Getting Help +description: 'Find the support you need - from free community resources to professional consulting services.' +--- + +## You're Not Alone + +Getting stuck is a normal part of development. Whether you're troubleshooting a configuration issue, planning a complex deployment, or need help with a custom implementation, there are support options available to help you succeed. + +This guide will help you find the right resource to get unblocked quickly. + +## Free Community Support +::note +Want guaranteed response times from the maintainers? View our [Professional Support](#professional-support) options. +:: +Our community is active, helpful, and welcoming. Most questions can be answered through these free resources: + +### GitHub Discussions + +**Best for:** General questions, feature requests, and community discussions + +Our GitHub Discussions is the most popular place to get help from both the maintainers and the community. It's searchable, so your question might help others in the future. + +:u-button{to="https://github.com/serversideup/docker-php/discussions" target="_blank" label="Browse GitHub Discussions" aria-label="Browse GitHub Discussions" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +::tip +Before posting, search existing discussions - your question might already be answered! +:: + +### Discord Community + +**Best for:** Quick questions, real-time chat, and community interaction + +Join over 3,000+ developers in our Discord server. It's a great place for quick questions, real-time troubleshooting, and connecting with other developers using serversideup/php. + +:u-button{to="https://serversideup.net/discord" label="Join Our Discord" aria-label="Join Our Discord" size="md" color="primary" variant="outline" trailing-icon="i-lucide-message-circle" class="font-bold" target="_blank"} + +::note +Discord is great for quick questions, but GitHub Discussions is better for complex issues that need detailed troubleshooting or permanent documentation. +:: + +### GitHub Issues + +**Best for:** Bug reports and specific technical issues with the images + +Found a bug or experiencing unexpected behavior? Open a GitHub issue with detailed reproduction steps. + +:u-button{to="https://github.com/serversideup/docker-php/issues/new" label="Report an Issue" aria-label="Report an Issue" size="md" color="primary" variant="outline" trailing-icon="i-lucide-bug" class="font-bold" target="_blank"} + +**When to use GitHub Issues:** +- The Docker images aren't behaving as documented +- You've found a security vulnerability +- You have a specific, reproducible bug + +::warning +Please don't use GitHub Issues for general questions or support requests. Use GitHub Discussions or Discord instead. +:: + + +## How to Ask for Help Effectively + +We've compile and entire guide on how to get answers quickly from the community. + +:u-button{to="https://serversideup.net/ask-for-help/" label="How to Ask for Help Effectively" aria-label="How to Ask for Help Effectively" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold" target="_blank"} + +## Professional Support + +Need guaranteed response times, direct access to the maintainers, or help with your specific implementation? We offer professional support options. + +### One-Time Questions + +**Perfect for:** Specific technical questions or getting unstuck on a particular issue + +Schedule a focused session to get expert help with: +- Docker-specific implementation questions +- Complex deployment scenarios +- Performance optimization guidance +- Security configuration review +- Custom implementation planning + +:u-button{to="https://schedule.serversideup.net/team/serversideup/quick-chat-with-jay" label="Schedule a Session" aria-label="Schedule a Session" size="md" color="primary" variant="solid" trailing-icon="i-lucide-calendar" class="font-bold" target="_blank"} + +**What you get:** +- Direct access to serversideup/php maintainers +- 1-hour focused troubleshooting or consulting +- Screen sharing and hands-on help +- Clear next steps and recommendations + +### Ongoing Development & Support + +**Perfect for:** Teams needing continuous development support or complex projects + +Get dedicated access to senior engineers for ongoing development, deployment assistance, and technical support. + +:u-button{to="https://serversideup.net/hire-us/" label="Learn About Retainer Services" aria-label="Learn About Retainer Services" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold" target="_blank"} + +**What's included:** +- 🎨 **Full-Stack Development** - Frontend, backend, and everything in between +- 🏗️ **Application Architecture** - Design scalable, maintainable solutions +- 🎯 **UI/UX Design** - Beautiful, functional interfaces using Figma +- 🐳 **Docker & DevOps** - Container orchestration, CI/CD pipelines, and automation +- 🖥️ **Managed Hosting** - Deploy anywhere (self-hosted or cloud) +- 📊 **Database Design** - Optimize for performance and scale +- 🔒 **Security Hardening** - Production-ready security configurations +- 📱 **Mobile App Development** - iOS & Android using modern frameworks + +**Key advantages:** +- **Run Anywhere** - We specialize in both self-hosted infrastructure and cloud deployments (AWS, Google Cloud, DigitalOcean, etc.) +- **No Vendor Lock-In** - Your infrastructure, your control +- **Fixed Monthly Pricing** - Predictable costs, no hourly billing surprises +- **Development-First** - 90%+ of time spent on actual development, not meetings +- **Complete Stack** - One team handling design, development, deployment, and maintenance + +::note +**Why choose us?** We built serversideup/php and maintain it in production for our own clients. We know these images inside and out because we use them every day for real-world applications. +:: + +## We're Here to Help + +Whether you choose free community support or professional services, we're committed to your success with serversideup/php. Don't hesitate to reach out - we were all beginners once, and there's no such thing as a "stupid question." + +Happy coding! 🚀 + From 25d10e67389c64dd4a26bde72e07be27f6189ded Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:09:34 -0500 Subject: [PATCH 266/304] Update installation documentation to clarify definitions of 'Image' and 'Tag' with specific examples, enhancing user understanding of container terminology. --- docs/content/docs/1.getting-started/3.installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/1.getting-started/3.installation.md b/docs/content/docs/1.getting-started/3.installation.md index 566549504..e45ed6dac 100644 --- a/docs/content/docs/1.getting-started/3.installation.md +++ b/docs/content/docs/1.getting-started/3.installation.md @@ -28,8 +28,8 @@ There are some important terms to understand when working with containers: | Term | Definition | |------|------------| | Container | A running instance of an image. | -| Image | A template for a container to start with (like serversideup/php). | -| Tag | A specific version of an image. | +| Image | A template for a container to start with (ie. `serversideup/php:8.4-frankenphp`). | +| Tag | A specific version of an image (ie. `8.4-frankenphp`). | | Registry | A repository of images. This is where users can pull images from to start a container. This can be places like [Docker Hub](https://hub.docker.com/r/serversideup/php) or [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php). | If you'd like to learn more about containers, you can learn more following the guide below. From 15f68b4b07009a1d5a7ba3f637403bd7e7351a93 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:12:10 -0500 Subject: [PATCH 267/304] Add target="_blank" attribute to external link in app.vue for improved user experience. --- docs/app/app.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/app/app.vue b/docs/app/app.vue index 1f09b7b3b..4e643de29 100644 --- a/docs/app/app.vue +++ b/docs/app/app.vue @@ -10,6 +10,7 @@ to="https://getspin.pro/?ref=docker-php" color="primary" class="text-white" + target="_blank" /> From c56a4e4712b1d10acf723361b85fd73d1b958d42 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:14:48 -0500 Subject: [PATCH 268/304] Update code block syntax in documentation from 'yaml' to 'yml' for consistency across various Docker Compose examples. --- .../docs/1.getting-started/2.these-images-vs-others.md | 2 +- docs/content/docs/2.image-variations/fpm.md | 6 +++--- .../docs/3.framework-guides/1.laravel/2.task-scheduler.md | 2 +- docs/content/docs/3.framework-guides/1.laravel/3.queue.md | 2 +- docs/content/docs/3.framework-guides/1.laravel/4.horizon.md | 2 +- docs/content/docs/3.framework-guides/1.laravel/4.reverb.md | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/content/docs/1.getting-started/2.these-images-vs-others.md b/docs/content/docs/1.getting-started/2.these-images-vs-others.md index d3f44097d..672bb5afa 100644 --- a/docs/content/docs/1.getting-started/2.these-images-vs-others.md +++ b/docs/content/docs/1.getting-started/2.these-images-vs-others.md @@ -152,7 +152,7 @@ Our images are built for the wild internet: We've done the hard work of optimizing for PHP's most popular frameworks: **Laravel Automations** -```yaml [compose.yml] +```yml [compose.yml] services: php: image: serversideup/php:8.3-fpm-nginx diff --git a/docs/content/docs/2.image-variations/fpm.md b/docs/content/docs/2.image-variations/fpm.md index 74e6989b4..7fbd392c9 100644 --- a/docs/content/docs/2.image-variations/fpm.md +++ b/docs/content/docs/2.image-variations/fpm.md @@ -157,7 +157,7 @@ spec: ### Health Check The FPM variation includes [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){target="_blank"}, a POSIX-compliant script that monitors PHP-FPM's `/status` endpoint to verify the service is healthy. -```yaml [compose.yml]{7-10} +```yml [compose.yml]{7-10} services: php: image: serversideup/php:8.4-fpm @@ -196,7 +196,7 @@ For a complete list of available environment variables, see the [Environment Var The FPM variation gives you fine-grained control over PHP process management. Here are some tuning tips: ### For High-Traffic Applications -```yaml [compose.yml] +```yml [compose.yml] services: php: image: serversideup/php:8.4-fpm @@ -207,7 +207,7 @@ services: ``` ### For Low-Memory Environments -```yaml [compose.yml] +```yml [compose.yml] services: php: image: serversideup/php:8.4-fpm diff --git a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md index 7c36a08ea..0fa52d62c 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md +++ b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md @@ -31,7 +31,7 @@ Notice we're calling the artisan command explicitly with the full path (`/var/ww :: #### Example & Simplified Docker Compose File -```yaml [docker-compose.yml] +```yml [docker-compose.yml] services: php: image: my/laravel-app diff --git a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md index 26548b114..5067c5de9 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md +++ b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md @@ -28,7 +28,7 @@ Notice we're calling the artisan command explicitly with the full path (`/var/ww :: #### Example & Simplified Docker Compose File -```yaml [docker-compose.yml] +```yml [docker-compose.yml] services: php: image: my/laravel-app diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md index 1cd773482..3a8140b42 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md +++ b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md @@ -29,7 +29,7 @@ Notice we're calling the artisan command explicitly with the full path (`/var/ww :: #### Example & Simplified Docker Compose File -```yaml [docker-compose.yml] +```yml [docker-compose.yml] services: php: image: my/laravel-app diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md index 1c8613928..b11a52fe6 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md +++ b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md @@ -29,7 +29,7 @@ Notice Laravel Reverb is running on port `8000`, where as Laravel is running on :: #### Example & Simplified Docker Compose File -```yaml [docker-compose.yml] +```yml [docker-compose.yml] services: php: image: my/laravel-app From c1399e4fc78ce1b46edffda6022c6c335709884d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:26:34 -0500 Subject: [PATCH 269/304] Add common issues troubleshooting guide for Docker images, covering permission issues, port conflicts, and support resources. This documentation aims to assist users in resolving frequent problems encountered while using serversideup/php images. --- .../docs/7.troubleshooting/1.common-issues.md | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/content/docs/7.troubleshooting/1.common-issues.md diff --git a/docs/content/docs/7.troubleshooting/1.common-issues.md b/docs/content/docs/7.troubleshooting/1.common-issues.md new file mode 100644 index 000000000..b6e9002c3 --- /dev/null +++ b/docs/content/docs/7.troubleshooting/1.common-issues.md @@ -0,0 +1,88 @@ +--- +title: Common Issues +description: 'Solutions to frequently encountered problems when using serversideup/php Docker images.' +--- + +## Overview + +This guide covers the most common issues users encounter and their solutions. If you don't find your issue here, check out our [Getting Help](/docs/troubleshooting/getting-help) guide for community and professional support options. + +## Permission Issues + +Permission problems are one of the most common issues when working with Docker containers. They typically manifest as "Permission denied" errors when trying to write files. + +### Understanding the Problem + +By default, these images run as a non-root user (`www-data`) for security. When your host machine uses a different user ID (UID) or group ID (GID), file permission conflicts can occur. + +If your command is failing during build, then you likely need to switch to root to perform root tasks. + +```dockerfile [Dockerfile] +FROM serversideup/php:8.4-fpm-nginx + +USER root + +# Install system packages +RUN install-php-extensions intl bcmath + +# Switch back to www-data +USER www-data +``` + +::warning +Always switch back to a non-root user after completing privileged operations. Running containers as root is a security risk. +:: + +If your container is failing to run during runtime, then you may have a more advanced permissions issue. See our guide on understanding file permissions. + +:u-button{to="/docs/guide/understanding-file-permissions" label="Understanding File Permissions" aria-label="Understanding File Permissions" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Port Already in Use + +### Error Message + +``` +Error starting userland proxy: listen tcp 0.0.0.0:80: bind: address already in use +``` + +### Solution + +Another process is using the port. Find and stop it, or use a different port: + +**Find what's using the port:** + +```bash +# On Linux/macOS +sudo lsof -i :80 + +# On Windows +netstat -ano | findstr :80 +``` + +**If you must use a different port:** + +```yaml [compose.yml] {4-6} +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - "8080:8080" # Use port 8080 instead + - "8443:8443" +``` + +Access your application at `http://localhost:8080`. + +## Getting More Help + +If your issue isn't covered here: + +1. **Search GitHub Discussions** - Someone may have encountered the same problem +2. **Check the Documentation** - Review guides specific to your setup +3. **Ask the Community** - Post in GitHub Discussions or Discord +4. **Review Container Logs** - Most issues show helpful error messages + +:u-button{to="/docs/troubleshooting/getting-help" label="View All Support Options" aria-label="View All Support Options" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +::tip +When asking for help, always include your image version, `compose.yml`, relevant error messages, and what you've already tried. This helps others help you faster! +:: From 8c59bb8ab02151a0f8267b9baa4cefd43d9f914a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:26:53 -0500 Subject: [PATCH 270/304] Update PHP image version in documentation from 8.3 to 8.4 for Docker Compose example, ensuring users have the latest configuration details. --- docs/content/docs/1.getting-started/2.these-images-vs-others.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/1.getting-started/2.these-images-vs-others.md b/docs/content/docs/1.getting-started/2.these-images-vs-others.md index 672bb5afa..f1d42394e 100644 --- a/docs/content/docs/1.getting-started/2.these-images-vs-others.md +++ b/docs/content/docs/1.getting-started/2.these-images-vs-others.md @@ -155,7 +155,7 @@ We've done the hard work of optimizing for PHP's most popular frameworks: ```yml [compose.yml] services: php: - image: serversideup/php:8.3-fpm-nginx + image: serversideup/php:8.4-fpm-nginx environment: # Run migrations, storage link, caching, and more AUTORUN_ENABLED: "true" From ebe579f4b1a24ff3b5713943c8b62489da043811 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:28:29 -0500 Subject: [PATCH 271/304] Add documentation for using S6 Overlay with Docker PHP images, explaining its benefits, process management, and comparison with Supervisor. The guide covers initialization, health reporting, and customization options to enhance user understanding of multi-process containerization. --- .../docs/5.guide/2.using-s6-overlay.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/content/docs/5.guide/2.using-s6-overlay.md diff --git a/docs/content/docs/5.guide/2.using-s6-overlay.md b/docs/content/docs/5.guide/2.using-s6-overlay.md new file mode 100644 index 000000000..b14a1f35b --- /dev/null +++ b/docs/content/docs/5.guide/2.using-s6-overlay.md @@ -0,0 +1,96 @@ +--- +head.title: 'Using S6 Overlay - Docker PHP - Server Side Up' +description: 'Learn about S6 Overlay and how it is used in this project.' +layout: docs +--- + +## What's S6 Overlay? +[S6 Overlay](https://github.com/just-containers/s6-overlay) is a process supervisor designed for containerization from the ground up. It's a modern alternative to [Supervisor (aka Supervisord)](https://supervisord.org/). + +S6 Overlay is a perfect match for running PHP because it usually requires running multiple processes together. + +## What images do you use S6 Overlay with? +We only use S6 Overlay for images that need two processes to run a service. This includes: +- `serversideup/php:*-fpm-apache` +- `serversideup/php:*-fpm-nginx` + +## Why does PHP need multiple processes? +Serving a PHP application can be broken down into two different components: +1. The PHP Application Itself +2. The static files that go with it (JavaScript, images, etc) + +There are ways to have these components served together with Apache modules like "mod_php". As time moved on, we found this to be very resource inefficient. + +Running "mod_php" with Apache meant that even when a JavaScript file needs to be loaded, Apache would load PHP to serve that file. This caused a lot of unnecessary memory and CPU usage for files that didn't need PHP to be served. + +### PHP-FPM +PHP-FPM was the answer to make this serving a PHP application more efficient. This allowed PHP apps to be served with a lot less memory and CPU overhead. + +Although this method was more efficient, it meant we still need something to serve our static content. This is where we turned to "reverse proxies". + +### Reverse proxy +Reverse proxies are servers that route traffic based on the request. To make things more confusing, you can have a web server be a reverse proxy and a web server at the same time (this is how we run NGINX). You can even configure Apache to run as a web server and a reverse proxy too (we run our `php:*-fpm-apache` images like this). + +![Reverse Proxy Diagram](images/docs/reverse-proxy.svg) + +In the example above, you can see the web request coming in from the top. NGINX is our first stop where it inspects the request. + +#### Static Files +If the request is for a static file (jpeg, js, png, etc), NGINX will also serve that file for us without loading PHP-FPM. This makes the request very fast and efficient. + +#### PHP Files +If the request ends in `.php`, then it will send the request over to PHP-FPM. PHP-FPM will then execute the PHP file and return a response to the original client, all passed through NGINX. + +## Shouldn't containers only have one process? +In a perfect world, that would be ideal -- but this isn't always realistic. You can see in the example above we need two things running: +1. NGINX: To serve static files +1. PHP-FPM: To serve PHP application + +If you want to replicate your application without the added complexity of multiple physical servers, etc -- you need something like S6 Overlay to properly bring up the processes and ensure your application service health is accurately reported. + +[S6 Overlay's philosophy](https://github.com/just-containers/s6-overlay#the-docker-way){target="_blank"} is a perfect match when it comes to running PHP: + +- A container should do ***one thing*** (which may contain multiple processes). When that one thing stops, the container should also stop. + +## The advantages of S6 Overlay +When we configure PHP to run with the S6 Overlay system, we get a number of advantages: + +- ✅ S6 Overlay was designed from the ground up to run within containers +- ✅ We get explicit control to run small scripts or configurations before/after the main processes start +- ✅ We get a better confidence on answering "Is my container actually healthy?" + +## This disadvantage of S6 Overlay + +- ❌ S6 Overlay may not be compatible with all PaaS providers, depending on how they run their containers ([see this comment from the S6 Overlay creator](https://github.com/just-containers/s6-overlay/issues/535#issuecomment-1597680218){target="_blank"}) + +## S6 Overlay vs. Supervisor +Many people flock to Supervisor, which was a very popular option before containerization. Here's some examples why you may want to trade Supervisor for S6 Overlay: + +### How Supervisor reports container health +![Supervisor Container Health Example](images/docs/supervisor-container.svg) + +When you bring up Supervisord within a container, it will be assigned `PID 1`. Then Supervisor will bring up child processes with it. + +During a failure, Supervisor can be configured to restart the child process to attempt recovery, but the container orchestrator thinks the container is still healthy because `supervisord` is occupying `PID 1` which is still healthy. + +**👉 This design can lead to inaccurate container health statuses during a failure.** + +### How S6 Overlay reports container health +![S6 Overlay Container Health Example](images/docs/s6-overlay-container.svg) +S6 Overlay was designed to be run in containers from the ground up. S6 Overlay can also attempt recovery, but it is more accurate on determining container health compared to Supervisor. + +**👍 By design, S6 Overlay can accurately detect a failure and exit (which is what we want when our app fails).** + +## Customizing the initialization process +![Container Initialization Example with S6 Overlay](images/docs/container-init.svg) +Since S6 Overlay was designed around the idea of containerization, there are also a number of other advantages to properly time your customizations during container startup. + +S6 Overlay has a number of options to [write our own service script](https://github.com/just-containers/s6-overlay/tree/master#writing-a-service-script){target="_blank"} and properly time everything. + +In the example above, you can see we have a `runas-user` script which helps us customize and set custom UIDs and GIDs for our file permissions. At the same time, `laravel-automations` executes to see if there are any automated migrations to run. + +Both scripts must finish successfully before S6 Overlay starts our main `php-fpm` process, which has both scripts listed as a dependency. + +As you can see this structure can be very powerful in making your own customizations. This is great for giving you full control of how you'd like your application to behave. + +:u-button{to="/docs/customizing-the-image/changing-common-php-settings" label="Changing Common PHP Settings" aria-label="Changing Common PHP Settings" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From 41537a0720c1d5b9f38dfab9b882a17e1bb6fb72 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:33:03 -0500 Subject: [PATCH 272/304] Add documentation for native Docker health checks in Laravel, detailing health check commands, default settings, and usage examples for Laravel services like Horizon, Reverb, Scheduler, and Queue. This guide aims to enhance application reliability and facilitate zero-downtime deployments. --- .../2.using-healthchecks-with-laravel.md | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md diff --git a/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md b/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md new file mode 100644 index 000000000..15b7b8d68 --- /dev/null +++ b/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md @@ -0,0 +1,111 @@ +--- +head.title: 'Native Docker Health Checks for Laravel - Docker PHP - Server Side Up' +description: 'Stop guessing if Laravel is up and running. Our health checks have you covered to ensure Laravel is running properly. It supports HTTP checks, Laravel Horizon, Reverb, Scheduler, and Queue.' +layout: docs +--- + +## Native Docker Health Checks for Laravel +::lead-p +Dialing in health checks are very important for ensuring your application is running smoothly and that you're able to deploy updates without any downtime. This guide will explain how our health checks work and how you can use them to your advantage. +:: + +:iframe{src="https://www.youtube-nocookie.com/embed/cuYIB5VrH1Q?si=75VBCKAe5x2Hmckd" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9;"} + +## What are Health Checks? + +Health checks are a way to check the status of your application. Whenever a container is started, a health check is performed. If the health check fails, the container will be restarted or marked as unhealthy. Health checks are very important for rolling updates and ensuring your application can start up in order if you have services that depend on each other. + +## Our Health Checks +We offer a number of health check commands, specifically for Laravel. You can find these commands are prefixed with `healthcheck-` and are located in [`/usr/local/bin`](https://github.com/serversideup/docker-php/tree/main/src/common/usr/local/bin){target="_blank"}. The examples below show how to use these health checks in your `docker-compose.yml` file, but you can also use them in other environments. + +## Default Health Check Settings +By default, our Dockerfiles ship with the following health check commands: + +| Variation | Health Check Command | +| --------- | -------------------- | +| `cli` | (none) | +| `fpm` | [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){target="_blank"} | +| `fpm-apache` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | +| `fpm-nginx` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | +| `frankenphp` | `curl --insecure --silent --location --show-error --fail http://localhost:8080$HEALTHCHECK_PATH` | + +::note +Notice how `fpm-apache`, `fpm-nginx`, and `frankenphp` have a `$HEALTHCHECK_PATH` variable? This is because you can specify a path for the health check to validate. +:: + +## Changing the Health Check Path +By default, you can see our [Environment Variable Specification](/docs/reference/environment-variable-specification) shows the `HEALTHCHECK_PATH` variable is set to `/healthcheck`. + +::tip +This only validates that FPM-NGINX or FPM-APACHE are running and ready to accept connections. It does not validate that Laravel is running or healthy. +:: + +If you are using Laravel, [modern versions of Laravel will ship with a `/up` route](https://laravel.com/docs/12.x/deployment#the-health-route){target="_blank"} that you can use to validate that Laravel is running and healthy. + +You can even create your own custom path in your application if you want. As long as the path returns a 200 status code, the health check will be successful. + +## Advanced Laravel Services +Since it is good practice to use the same Docker image for all our services, we also make additional health checks for Laravel's advanced services. If you're looking for rolling updates with zero downtime, it's very important to use these health checks. + +### Laravel Horizon +We utilize the `artisan horizon:status` command to check the status of Laravel Horizon. This is a command native to Laravel Horizon and is used to determine if the Horizon process is running. + +To run this command automatically, you can call our health check command in your `docker-compose.yml` file. + +```yml +healthcheck: + test: ["CMD", "healthcheck-horizon"] +``` + +[See a full example of configuring Laravel Horizon →](/docs/framework-guides/laravel/horizon) + +### Laravel Reverb +We use `pgrep` to check if the `reverb:start` command is running. This ensures the Reverb process is running and ready to accept connections. + +To run this command automatically, you can call our health check command in your `docker-compose.yml` file. + +::code-panel +--- +label: Using Healthcheck with Laravel Reverb +--- +```yml +healthcheck: + test: ["CMD", "healthcheck-reverb"] +``` +:: + +[See a full example of configuring Laravel Reverb →](/docs/framework-guides/laravel/reverb) + +### Laravel Scheduler +We use `pgrep` to check if the `schedule:work` command is running. This ensures the scheduler process is running. + +To run this command automatically, you can call our health check command in your `docker-compose.yml` file. + +::code-panel +--- +label: Using Healthcheck with Laravel Scheduler +--- +```yml +healthcheck: + test: ["CMD", "healthcheck-schedule"] +``` +:: + +[See a full example of configuring Laravel Scheduler →](/docs/framework-guides/laravel/task-scheduler) + +### Laravel Queue +We use `pgrep` to check if the `queue:work` command is running. This ensures the queue process is running. + +To run this command automatically, you can call our health check command in your `docker-compose.yml` file. + +::code-panel +--- +label: Using Healthcheck with Laravel Queue +--- +```yml +healthcheck: + test: ["CMD", "healthcheck-queue"] +``` +:: + +[See a full example of configuring Laravel Queue →](/docs/framework-guides/laravel/queue) From 3bd3cf7dcd630ace2545cd125d726ed402204d45 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:35:32 -0500 Subject: [PATCH 273/304] Add migration guide from v2 to v3 for serversideup/php images, detailing new features, breaking changes, and a comprehensive migration checklist. This documentation aims to assist users in transitioning smoothly to the latest version while highlighting important updates and configuration adjustments. --- .../5.guide/99.migrating-from-v2-to-v3.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md diff --git a/docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md b/docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md new file mode 100644 index 000000000..60d1a559f --- /dev/null +++ b/docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md @@ -0,0 +1,170 @@ +--- +head.title: 'Migrating from v2 - Server Side Up' +description: 'Learn how to migrate from serversideup/php v2 images to v3.' +layout: docs +title: Migrating from v2 +--- +::lead-p +If you're moving from v2 of serversideup/php to the latest version, there are a number of changes you should be aware of. We've tried to keep these to a minimum, but some of these changes were necessary to make the project more maintainable and easier to use. +:: + +## Preparing for the migration +If you're an existing user of our v2 images, be sure that your current configurations are NOT set to use the latest images. To do this, you can lock your images into the `v2.2.1` tag. This will ensure that you're not automatically upgraded to the v3 images. + +For example, if you are using `8.2-fpm-nginx`, you would change your `docker-compose.yml` file to use the [`v2.2.1`](https://hub.docker.com/r/serversideup/php/tags?page=1&name=2.2.1){target="_blank"} tag: + +```yml [docker-compose.yml] {3} +services: + php: + image: serversideup/php:8.2-fpm-nginx + ports: + - 80:80 + volumes: + - .:/var/www/html +``` + +```yml [docker-compose.yml] {3} +services: + php: + image: serversideup/php:8.2-fpm-nginx-v2.2.1 + ports: + - 80:80 + volumes: + - .:/var/www/html +``` + +All you need to do is add `-v2.2.1` to the end of the image tag. This will ensure that you're not automatically upgraded to the v3 images. + +## New Features +We've been busy overhauling our PHP Docker Images to make them more production-ready and easier to use. Here are some of the new features we've added: +- **Based on official PHP Images** - We're now building an improved developer experience on top of the official PHP Docker images. +- **Unprivileged by default** - We're now running our images as an unprivileged user by default. This is a huge step forward in security and compatibility. +- **PHP 8.4 support** - We're now shipping the latest and greatest. +- **Pin to the exact minor version** - Pin your app to the exact minor version of PHP that you want to use. This means you can pin to `8.2.12` instead of `8.2`. +- **Easier start up script customization** - We now have a folder called `/etc/entrypoint.d` that allows you to easily customize your container with scripts. Just put them in numerical order and we'll execute any shell script you want. No S6 Overlay knowledge required. +- **Expanded Laravel Automations** - We added automations to run `config:cache`, `route:cache`, `view:cache`, `event:cache`, `migrate --force --isolated`, and `storage:link` +- **NGINX Unit Support** - We're offering NGINX Unit as a variation as an alternative to PHP-FPM. This allows you to run PHP applications without the need for a webserver like NGINX or Apache to run with PHP-FPM. +- **Available on GitHub Packages** - We're now publishing our images to GitHub Packages. This means you can use our images without needing to authenticate with Docker Hub. + +## Breaking changes +::caution +The following changes are considered to be "breaking changes" and will require you to make changes to your application. +:: + +### Ubuntu is no longer used as a base image +We now use Debian or Alpine as our base OS (because we're using the official PHP images as a base). This is a huge change, but we're confident this will be the best direction moving forward. + +### `ppa:ondrej/php` is no longer used +Since we're using PHP.net as the "official source of truth" for getting our PHP versions, this means we're also dropping support for the `ppa:ondrej/php` repository. If you're using things like `apt-get install php-redis` you will need to change your method of installing PHP extensions. + +[Learn how to install your own PHP extension →](/docs/customizing-the-image/installing-additional-php-extensions) + +### `webuser` is no longer being used +We used to add a user called `webuser` with the UID of `9999` with shell permissions. To increase security, we're now using the `www-data` user and group that is built into the official PHP images. If you have mounted volumes, you will need to `chown` the files to match the ID of the `www-data` user and groups. For Debian, this is `33:33` and for Alpine, this is `82:82`. + +### NGINX and Apache listen on 8080 (HTTP) and 8443 (HTTPS) by default +Our images are now unprivileged by default. This is a major step forward in security and compatibility. Since we are unprivileged by default, we lose the ability to mount on ports less than 1024. If you're using NGINX or Apache, you will need to update your port mappings to use `8080` and `8443` instead of `80` and `443`. + +[Learn more about this change →](/docs/getting-started/default-configurations#unprivileged-by-default) + +### S6 Overlay is only used in `*-fpm-apache` and `*-fpm-nginx` images +Due to compatibility issues, we only use S6 Overlay in our `*-fpm-apache` and `*-fpm-nginx` images. If you were using S6 Overlay for our other variations (cli, fpm, etc), you will need to migrate your scripts to use the new `/etc/entrypoint.d` folder. + +### `SSL_MODE` is now set to `off` by default (HTTP only) +Running end-to-end SSL by default created more problems than good. By default, we're now shipping HTTP-only by default with the option for people to turn this on. + +### `AUTORUN_ENABLED` is now set to `false` by default. +Having this set to "true" by default also created more problems than good. If you want to use any of the Laravel Automation Scripts, be sure to set this to `true`. + +### MSMTP is no longer included in the images +For security and image size reasons, we removed MSMTP from the images. If you need to send emails, use an external SMTP service like Postmark/Sendgrid/Mailgun. You can also extend the image yourself to include MSMTP specifically for your use case. + +### Variable deprecations +- `WEB_APP_DIRECTORY` has now been renamed to `APP_BASE_DIR` +- `DEBUG_OUTPUT` has been removed for in favor of `LOG_OUTPUT_LEVEL=debug` +- `PUID` & `PGID` are no longer used because it requires root privileges. See the [new way to set the UID and GID →](/docs/guide/understanding-file-permissions) +- `MSMTP_RELAY_SERVER_HOSTNAME` & `MSMTP_RELAY_SERVER_PORT` are no longer used because MSMTP is no longer included in the images. +- `PHP_POOL_NAME` has been renamed to `PHP_FPM_POOL_NAME` + +## Migration Checklist +Here is a good list to perform the migration + +#### Repository +- Ensure you're committing to a test environment + +#### Docker Compose +- Update the image name (if applicable) +- Check each environment variable exists and is set to a proper value [See the full list of environment variables →](/docs/reference/environment-variable-specification) +- Ensure you updated the ports to `8080` and `8443` for NGINX, Apache, and Unit +- Consider adding `PHP_OPCACHE_ENABLE=1` to your production environment for increased performance + +#### Dockerfile +- Update the base image name (if applicable) +- Remove any `ppa:ondrej/php` references +- Remove any Ubuntu specific commands +- Ensure all extensions are installed with the `install-php-extensions` command [Learn how to install your own PHP extension →](/docs/customizing-the-image/installing-additional-php-extensions) +- Ensure your `COPY` commands are copying with the correct permissions (i.e. `--chown=www-data:www-data`) + +#### CI/CD +If you're running `fpm-nginx` (or similar) on a runner that's running as your builds as `root`, you may need to add `user = www-data` and `group = www-data` to your `php-fpm.conf` file so you can bring FPM up correctly. + +If you have to run things as root in CI, you can do this with a multi stage build and set the targets: + + +```dockerfile [Dockerfile] +############################################ +# Base Image +############################################ + +# Learn more about the Server Side Up PHP Docker Images at: +# https://serversideup.net/open-source/docker-php/ +FROM serversideup/php:8.4-fpm-nginx AS base + +## Uncomment if you need to install additional PHP extensions +# USER root +# RUN install-php-extensions bcmath gd + +############################################ +# Development Image +############################################ +FROM base AS development + +# We can pass USER_ID and GROUP_ID as build arguments +# to ensure the www-data user has the same UID and GID +# as the user running Docker. +ARG USER_ID +ARG GROUP_ID + +# Switch to root so we can set the user ID and group ID +USER root +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID +USER www-data + +############################################ +# CI image +############################################ +FROM base AS ci + +# Sometimes CI images need to run as root +# so we set the ROOT user and configure +# the PHP-FPM pool to run as www-data +USER root +RUN echo "user = www-data" >> /usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf && \ + echo "group = www-data" >> /usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf + +############################################ +# Production Image +############################################ +FROM base AS deploy +COPY --chown=www-data:www-data . /var/www/html +USER www-data +``` + +#### Production/Staging Servers +- Update all host volume file permissions to match the `www-data` UID/GID (`33:33` for Debian, `82:82` for Alpine) [Learn how to manage file permissions](/docs/guide/understanding-file-permissions) +- If you're running Docker Swarm with host volume mounts, we created a script that could potentially help ([change-volume-permissions.sh](https://github.com/serversideup/docker-volume-change-permission-script)) + +#### Deployment +- CI/CD with valid tests is always encouraged +- After completing all steps above, you're now ready to deploy the new images From 7fdf21b0e62ccb84345e8f06790abc599f891b07 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 10:39:14 -0500 Subject: [PATCH 274/304] Refactor health check documentation for Laravel in Docker Compose examples, enhancing clarity by adding service configuration comments and standardizing code block syntax. --- .../2.using-healthchecks-with-laravel.md | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md b/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md index 15b7b8d68..9645f4a03 100644 --- a/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md +++ b/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md @@ -52,7 +52,8 @@ We utilize the `artisan horizon:status` command to check the status of Laravel H To run this command automatically, you can call our health check command in your `docker-compose.yml` file. -```yml +```yml [docker-compose.yml] +## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-horizon"] ``` @@ -64,15 +65,11 @@ We use `pgrep` to check if the `reverb:start` command is running. This ensures t To run this command automatically, you can call our health check command in your `docker-compose.yml` file. -::code-panel ---- -label: Using Healthcheck with Laravel Reverb ---- -```yml +```yml [docker-compose.yml] +## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-reverb"] ``` -:: [See a full example of configuring Laravel Reverb →](/docs/framework-guides/laravel/reverb) @@ -81,15 +78,11 @@ We use `pgrep` to check if the `schedule:work` command is running. This ensures To run this command automatically, you can call our health check command in your `docker-compose.yml` file. -::code-panel ---- -label: Using Healthcheck with Laravel Scheduler ---- -```yml +```yml [docker-compose.yml] +## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-schedule"] ``` -:: [See a full example of configuring Laravel Scheduler →](/docs/framework-guides/laravel/task-scheduler) @@ -98,14 +91,10 @@ We use `pgrep` to check if the `queue:work` command is running. This ensures the To run this command automatically, you can call our health check command in your `docker-compose.yml` file. -::code-panel ---- -label: Using Healthcheck with Laravel Queue ---- -```yml +```yml [docker-compose.yml] +## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-queue"] ``` -:: [See a full example of configuring Laravel Queue →](/docs/framework-guides/laravel/queue) From 170de94c31aaa1f74729c0103b8e5b785647ea11 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 14:04:06 -0500 Subject: [PATCH 275/304] Add comprehensive documentation on container fundamentals, including concepts of images vs containers, image layers, volumes, ports, environment variables, and the container lifecycle. This update enhances user understanding of Docker and prepares them for deploying applications from development to production. --- .../1.container-basics.md | 127 ++++++++++++++++-- 1 file changed, 119 insertions(+), 8 deletions(-) diff --git a/docs/content/docs/4.deployment-and-production/1.container-basics.md b/docs/content/docs/4.deployment-and-production/1.container-basics.md index a7c72d0cb..14b9956b3 100644 --- a/docs/content/docs/4.deployment-and-production/1.container-basics.md +++ b/docs/content/docs/4.deployment-and-production/1.container-basics.md @@ -31,18 +31,129 @@ Although you may see us reference things as "Docker containers", we're actually So although we're going to show you best practices with Docker, this means you'll maintain your freedom and flexibility to choose how you want to run your containers. -## How to get started with Docker -At first, Docker may seem a little intimidating, but don't let that stop you. Docker is one of the most powerful skill-sets that we've learned in our career. +## How containers work +If you followed our [installation guide](/docs/getting-started/installation), you've already run containers and seen them in action. Let's break down what actually happened when you ran `docker compose up`. -Although teaching you how to use Docker is beyond the scope of serversideup/php, we've actually created another open source project called [Spin](https://serversideup.net/open-source/spin/) that dramatically reduces the learning curve for getting started and self-hosting with Docker on any VPS provider. +### Images vs Containers +Think of an **image** as a blueprint and a **container** as the actual running instance of that blueprint. When you specified `image: serversideup/php:8.3-fpm-nginx` in your `compose.yml`, you told Docker to: -::callout -These images work with any Docker setup — Docker Compose, Docker Swarm, Kubernetes, and more. In the demo below, we're using Spin to quickly demonstrate running Docker from development to production on any VPS provider. **Spin is NOT required** to use these images, but it's a great tool to help you get started with Docker. +1. Download the `serversideup/php:8.3-fpm-nginx` image (the blueprint) +2. Create a container from that image (the running instance) +3. Start the container with your specified configuration + +You can create multiple containers from the same image — just like you can build multiple houses from the same blueprint. Each container runs independently with its own isolated filesystem and processes. + +### Understanding image layers +Container images are built in layers, like a stack of pancakes. Each layer adds something new: the operating system, PHP, web servers, and configurations. When you pull an image, Docker downloads only the layers you don't already have, making updates incredibly efficient. + +This is why switching from PHP 8.3 to 8.4 in the installation guide was so fast — most of the layers were already on your machine, and Docker only downloaded the differences. + +::tip +Image tags like `8.3-fpm-nginx` and `8.4-frankenphp` aren't just version numbers — they describe the entire stack that's included in that image. The tag tells you the PHP version and which variation (web server stack) you're getting. +:: + +## Key container concepts + +### Volumes: Sharing files with containers +When you added this to your `compose.yml`: + +```yml +volumes: + - ./:/var/www/html +``` + +You created a **bind mount** that connects your local project directory to the container's `/var/www/html` directory. This means: + +- Changes you make on your computer are instantly visible inside the container +- The container serves your actual project files, not a copy +- When the container stops, your files remain on your computer + +This is perfect for development because you can edit files with your favorite editor and see changes immediately without rebuilding the container. + +::note +In production, you'll typically build your application files directly into the image instead of using volumes. We cover this in our [packaging guide](/docs/deployment-and-production/packaging-your-app-for-deployment). :: +### Ports: Accessing your container +The `ports` configuration maps ports between your computer and the container: + +```yml +ports: + - 80:8080 +``` + +This means "take port 8080 inside the container and make it available on port 80 on my computer." When you visited `http://localhost` in your browser, you were actually connecting to port 80 on your computer, which Docker forwarded to port 8080 inside the container where NGINX or FrankenPHP was listening. + +::warning +Only one service can use a port at a time. If you get a "port already in use" error, another service on your computer is already using that port. Try using a different port like `8000:8080`. +:: + +### Environment variables: Configuring your container +Environment variables let you configure your container without modifying the image. When you set: + +```yml +environment: + PHP_UPLOAD_MAX_FILE_SIZE: "500M" + PHP_OPCACHE_ENABLE: "1" +``` -:iframe{src="https://www.youtube-nocookie.com/embed/5z2JoEt5XIk?si=u5v-bDN-cMv0OE-C" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9; width: 100%;"} +You're telling our PHP images to adjust PHP's configuration. Our images read these variables at startup and automatically configure PHP accordingly. This means the same image can be configured differently for development, staging, and production — just by changing environment variables. + +::tip +Check out our [Environment Variable Specification](/docs/reference/environment-variable-specification) to see all available configuration options. +:: + +## The container lifecycle +Understanding the lifecycle helps you work more effectively with containers: + +1. **Create & Start** - `docker compose up` creates and starts containers +2. **Running** - Your application is active and serving requests +3. **Stop** - `docker compose down` stops containers (but keeps configurations) +4. **Remove** - Containers are deleted (but images remain for faster restarts) + +Important to know: +- Stopping a container doesn't delete it — it's just paused +- Restarting is fast because the image is already downloaded +- Any data stored inside the container (not in volumes) is lost when the container is removed +- Your images remain on your machine until you explicitly remove them + +## Common Docker commands +Here are the essential commands you'll use regularly: + +```bash [Terminal] +# Start containers (creates if they don't exist) +docker compose up + +# Start in background (detached mode) +docker compose up -d + +# Stop and remove containers +docker compose down + +# View running containers +docker compose ps + +# View container logs +docker compose logs + +# Follow logs in real-time +docker compose logs -f + +# Execute a command in a running container +docker compose exec php php -v + +# Rebuild containers after image changes +docker compose up --build + +# Remove images to force fresh download +docker compose down --rmi all +``` + +::tip +The `-f` flag in `docker compose logs -f` means "follow" — it keeps showing new log entries as they happen. Press :kbd{value="ctrl"} + :kbd{value="C"} to stop following. +:: -Spin is free and open source, and it's a great way to get started with Docker if you're looking for a free method to deploy to any VPS provider. +## What's next? +Now that you understand the fundamentals of containers, you're ready to learn how to take your application from development to production. Our next guide walks you through the entire journey, showing you how to package your application properly and deploy it with confidence. -:u-button{to="https://serversideup.net/open-source/spin/" label="Learn more about Spin" aria-label="Learn more about Spin" target="_blank" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file +:u-button{to="/docs/deployment-and-production/development-to-production" label="Development to Production Guide" aria-label="Development to Production Guide" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} From cc5c1ea3643908d6f5cf460b0aec0e995d645780 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 14:22:34 -0500 Subject: [PATCH 276/304] Update container basics documentation to reflect the change from PHP image version 8.3 to 8.4, and clarify the container stopping behavior. Additionally, enhance the environment variable tip with a direct link to the specification for better user guidance. --- .../docs/4.deployment-and-production/1.container-basics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/4.deployment-and-production/1.container-basics.md b/docs/content/docs/4.deployment-and-production/1.container-basics.md index 14b9956b3..364b87024 100644 --- a/docs/content/docs/4.deployment-and-production/1.container-basics.md +++ b/docs/content/docs/4.deployment-and-production/1.container-basics.md @@ -37,7 +37,7 @@ If you followed our [installation guide](/docs/getting-started/installation), yo ### Images vs Containers Think of an **image** as a blueprint and a **container** as the actual running instance of that blueprint. When you specified `image: serversideup/php:8.3-fpm-nginx` in your `compose.yml`, you told Docker to: -1. Download the `serversideup/php:8.3-fpm-nginx` image (the blueprint) +1. Download the `serversideup/php:8.4-fpm-nginx` image (the blueprint) 2. Create a container from that image (the running instance) 3. Start the container with your specified configuration @@ -99,7 +99,7 @@ environment: You're telling our PHP images to adjust PHP's configuration. Our images read these variables at startup and automatically configure PHP accordingly. This means the same image can be configured differently for development, staging, and production — just by changing environment variables. -::tip +::tip{to="/docs/reference/environment-variable-specification"} Check out our [Environment Variable Specification](/docs/reference/environment-variable-specification) to see all available configuration options. :: @@ -112,7 +112,7 @@ Understanding the lifecycle helps you work more effectively with containers: 4. **Remove** - Containers are deleted (but images remain for faster restarts) Important to know: -- Stopping a container doesn't delete it — it's just paused +- Stopping a container doesn't always delete it - Restarting is fast because the image is already downloaded - Any data stored inside the container (not in volumes) is lost when the container is removed - Your images remain on your machine until you explicitly remove them From 4867bf767df55b0ff3f35dde6b54bc011210f869 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 14:30:57 -0500 Subject: [PATCH 277/304] Add documentation for packaging PHP applications for deployment, detailing the transition from development to production images, creating Dockerfiles, best practices, and image versioning strategies. This guide aims to enhance user understanding of building secure and reliable Docker images for production environments. --- .../3.packaging-your-app-for-deployment.md | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 docs/content/docs/4.deployment-and-production/3.packaging-your-app-for-deployment.md diff --git a/docs/content/docs/4.deployment-and-production/3.packaging-your-app-for-deployment.md b/docs/content/docs/4.deployment-and-production/3.packaging-your-app-for-deployment.md new file mode 100644 index 000000000..1c0d20b62 --- /dev/null +++ b/docs/content/docs/4.deployment-and-production/3.packaging-your-app-for-deployment.md @@ -0,0 +1,167 @@ +--- +title: Packaging Your App for Deployment +description: 'Learn how to properly package your PHP application into production-ready Docker images.' +layout: docs +--- + +::lead-p +In development, you mount your code as a volume for instant changes. In production, you build your code directly into the image for security, reliability, and portability. This guide shows you how to make that transition. +:: + +## Development vs production images +Let's revisit what you've been doing in development and why it changes for production. + +### What you've been doing (development) +In the [installation guide](/docs/getting-started/installation), your `compose.yml` looked like this: + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - 80:8080 + volumes: + - ./:/var/www/html # Your code mounted as a volume +``` + +This works great for development because: +- You edit files on your computer and see changes instantly +- No rebuild needed between changes +- Fast iteration and debugging + +### What you need for production +For production, you'll create a **custom image** that includes your application code: + +```dockerfile [Dockerfile] +FROM serversideup/php:8.4-fpm-nginx + +# Copy your application code into the image +COPY --chown=www-data:www-data . /var/www/html +``` + +This is better for production because: +- Your code can't be accidentally modified or deleted +- The image is completely portable and self-contained +- You can version and tag each release +- Deployments are atomic — the new version either works or it doesn't + +## Creating your first production Dockerfile +Let's create a proper production Dockerfile step by step. Start by creating a file called `Dockerfile` in your project root. + +::tip +The `Dockerfile` should live at the root of your project, in the same directory as your `compose.yml`. +:: + +### Basic PHP application +For a simple PHP application, your Dockerfile might look like this: + +```dockerfile [Dockerfile] +FROM serversideup/php:8.4-fpm-nginx + +# Switch to root to install dependencies and copy files +USER root + +# Install PHP dependencies +RUN install-php-extensions intl bcmath + +# Switch back to non-root user for security +USER www-data + +# Copy application files with correct ownership +COPY --chown=www-data:www-data . /var/www/html +``` + +That's it! This takes your application code and bakes it into the image. + +::note +Notice we use `--chown=www-data:www-data` when copying files. This ensures the web server can read your files. Learn more in our [file permissions guide](/docs/guide/understanding-file-permissions). +:: + +## Building your image +Once you have your Dockerfile, build your image with: + +```bash [Terminal] +docker build -t my-app:latest . +``` + +This creates an image tagged as `my-app:latest` that contains your application code. + +### Testing your production image +Before deploying, test your production image locally: + +```yml [compose.prod.yml] +services: + php: + # Use your custom image instead of the base image + image: my-app:latest + ports: + - 80:8080 +``` + +Then run: + +```bash [Terminal] +docker compose -f compose.prod.yml up +``` + +This lets you verify your production image works correctly before deploying to real servers. + +## Best practices for production images + +### 1.`Use specific image tags +Don't use `latest` tags in production. Use specific versions: + +```dockerfile +# Bad - version can change unexpectedly +FROM serversideup/php:latest + +# Good - explicitly versioned +FROM serversideup/php:8.4-fpm-nginx +``` + +### 2. Run as non-root user +Always run your application as a non-root user for security: + +```dockerfile +# Our images default to www-data, but explicitly switch back if you use root +USER www-data +``` + +All `serversideup/php` images default to running as the `www-data` user for security. + +### 3. Keep secrets out of images +Never bake secrets into your images: + +```dockerfile +# ❌ Never do this +ENV APP_KEY=base64:your-secret-key + +# ✅ Provide secrets at runtime via environment variables +``` + +Secrets should be provided when the container starts, not built into the image. + +## Image versioning strategy +When building images for deployment, tag them with meaningful versions: + +```bash [Terminal] +# Tag with git commit SHA +docker build -t my-app:$(git rev-parse --short HEAD) . + +# Tag with semantic version +docker build -t my-app:1.2.3 . + +# Tag with date and build number +docker build -t my-app:2024-10-31-build-42 . +``` + +Good versioning lets you: +- Track which code is running in each environment +- Roll back to previous versions quickly +- Debug issues by knowing exactly what's deployed + +## What's next? +Now that you know how to package your application, you're ready to learn about SSL configuration and choosing the right hosting provider for your containerized application. + +:u-button{to="/docs/deployment-and-production/configuring-ssl" label="Configure SSL for your application" aria-label="Configure SSL for your application" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + From dfea2cdd4d96826416da6d781d3ba67bc2927097 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 14:31:33 -0500 Subject: [PATCH 278/304] Update Docker Compose code block syntax in Laravel documentation to use 'compose.yml' for consistency across task scheduler, queue, horizon, and reverb guides. --- .../docs/3.framework-guides/1.laravel/2.task-scheduler.md | 2 +- docs/content/docs/3.framework-guides/1.laravel/3.queue.md | 2 +- docs/content/docs/3.framework-guides/1.laravel/4.horizon.md | 2 +- docs/content/docs/3.framework-guides/1.laravel/4.reverb.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md index 0fa52d62c..734a5ae98 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md +++ b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md @@ -31,7 +31,7 @@ Notice we're calling the artisan command explicitly with the full path (`/var/ww :: #### Example & Simplified Docker Compose File -```yml [docker-compose.yml] +```yml [compose.yml] services: php: image: my/laravel-app diff --git a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md index 5067c5de9..2907dfd0d 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md +++ b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md @@ -28,7 +28,7 @@ Notice we're calling the artisan command explicitly with the full path (`/var/ww :: #### Example & Simplified Docker Compose File -```yml [docker-compose.yml] +```yml [compose.yml] services: php: image: my/laravel-app diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md index 3a8140b42..502b9fb77 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md +++ b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md @@ -29,7 +29,7 @@ Notice we're calling the artisan command explicitly with the full path (`/var/ww :: #### Example & Simplified Docker Compose File -```yml [docker-compose.yml] +```yml [compose.yml] services: php: image: my/laravel-app diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md index b11a52fe6..b1bcc105c 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md +++ b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md @@ -29,7 +29,7 @@ Notice Laravel Reverb is running on port `8000`, where as Laravel is running on :: #### Example & Simplified Docker Compose File -```yml [docker-compose.yml] +```yml [compose.yml] services: php: image: my/laravel-app From 3652933a01177232ca2504258ab8ad548a5fad4a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 15:07:02 -0500 Subject: [PATCH 279/304] Update migration guide to reflect the change from 'docker-compose.yml' to 'compose.yml' for consistency in configuration examples. --- docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md b/docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md index 60d1a559f..c9abaa482 100644 --- a/docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md +++ b/docs/content/docs/5.guide/99.migrating-from-v2-to-v3.md @@ -11,9 +11,9 @@ If you're moving from v2 of serversideup/php to the latest version, there are a ## Preparing for the migration If you're an existing user of our v2 images, be sure that your current configurations are NOT set to use the latest images. To do this, you can lock your images into the `v2.2.1` tag. This will ensure that you're not automatically upgraded to the v3 images. -For example, if you are using `8.2-fpm-nginx`, you would change your `docker-compose.yml` file to use the [`v2.2.1`](https://hub.docker.com/r/serversideup/php/tags?page=1&name=2.2.1){target="_blank"} tag: +For example, if you are using `8.2-fpm-nginx`, you would change your `compose.yml` file to use the [`v2.2.1`](https://hub.docker.com/r/serversideup/php/tags?page=1&name=2.2.1){target="_blank"} tag: -```yml [docker-compose.yml] {3} +```yml [compose.yml] {3} services: php: image: serversideup/php:8.2-fpm-nginx @@ -23,7 +23,7 @@ services: - .:/var/www/html ``` -```yml [docker-compose.yml] {3} +```yml [compose.yml] {3} services: php: image: serversideup/php:8.2-fpm-nginx-v2.2.1 From 0085c82793cf702e72c134e072a531ac99ce5894 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 15:07:34 -0500 Subject: [PATCH 280/304] Update health check documentation for Laravel to consistently use 'compose.yml' instead of 'docker-compose.yml' across all examples. --- .../2.using-healthchecks-with-laravel.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md b/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md index 9645f4a03..245b50dfc 100644 --- a/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md +++ b/docs/content/docs/5.guide/2.using-healthchecks-with-laravel.md @@ -16,7 +16,7 @@ Dialing in health checks are very important for ensuring your application is run Health checks are a way to check the status of your application. Whenever a container is started, a health check is performed. If the health check fails, the container will be restarted or marked as unhealthy. Health checks are very important for rolling updates and ensuring your application can start up in order if you have services that depend on each other. ## Our Health Checks -We offer a number of health check commands, specifically for Laravel. You can find these commands are prefixed with `healthcheck-` and are located in [`/usr/local/bin`](https://github.com/serversideup/docker-php/tree/main/src/common/usr/local/bin){target="_blank"}. The examples below show how to use these health checks in your `docker-compose.yml` file, but you can also use them in other environments. +We offer a number of health check commands, specifically for Laravel. You can find these commands are prefixed with `healthcheck-` and are located in [`/usr/local/bin`](https://github.com/serversideup/docker-php/tree/main/src/common/usr/local/bin){target="_blank"}. The examples below show how to use these health checks in your `compose.yml` file, but you can also use them in other environments. ## Default Health Check Settings By default, our Dockerfiles ship with the following health check commands: @@ -50,9 +50,9 @@ Since it is good practice to use the same Docker image for all our services, we ### Laravel Horizon We utilize the `artisan horizon:status` command to check the status of Laravel Horizon. This is a command native to Laravel Horizon and is used to determine if the Horizon process is running. -To run this command automatically, you can call our health check command in your `docker-compose.yml` file. +To run this command automatically, you can call our health check command in your `compose.yml` file. -```yml [docker-compose.yml] +```yml [compose.yml] ## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-horizon"] @@ -63,9 +63,9 @@ healthcheck: ### Laravel Reverb We use `pgrep` to check if the `reverb:start` command is running. This ensures the Reverb process is running and ready to accept connections. -To run this command automatically, you can call our health check command in your `docker-compose.yml` file. +To run this command automatically, you can call our health check command in your `compose.yml` file. -```yml [docker-compose.yml] +```yml [compose.yml] ## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-reverb"] @@ -76,9 +76,9 @@ healthcheck: ### Laravel Scheduler We use `pgrep` to check if the `schedule:work` command is running. This ensures the scheduler process is running. -To run this command automatically, you can call our health check command in your `docker-compose.yml` file. +To run this command automatically, you can call our health check command in your `compose.yml` file. -```yml [docker-compose.yml] +```yml [compose.yml] ## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-schedule"] @@ -89,9 +89,9 @@ healthcheck: ### Laravel Queue We use `pgrep` to check if the `queue:work` command is running. This ensures the queue process is running. -To run this command automatically, you can call our health check command in your `docker-compose.yml` file. +To run this command automatically, you can call our health check command in your `compose.yml` file. -```yml [docker-compose.yml] +```yml [compose.yml] ## Rest of your service configuration... healthcheck: test: ["CMD", "healthcheck-queue"] From 2423c651d9953c98b93af93f206b27d20d11f80f Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 15:08:25 -0500 Subject: [PATCH 281/304] Add documentation on understanding file permissions in PHP + Docker, addressing common challenges and providing solutions for managing UID/GID alignment between host and container environments. This guide includes examples for development and production setups to enhance user experience and security. --- .../3.understanding-file-permissions.md | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 docs/content/docs/5.guide/3.understanding-file-permissions.md diff --git a/docs/content/docs/5.guide/3.understanding-file-permissions.md b/docs/content/docs/5.guide/3.understanding-file-permissions.md new file mode 100644 index 000000000..627c12e3d --- /dev/null +++ b/docs/content/docs/5.guide/3.understanding-file-permissions.md @@ -0,0 +1,107 @@ +--- +head.title: 'Understanding file permissions - Docker PHP - Server Side Up' +description: 'Eliminate the headache of file permissions when working with PHP + Docker.' +layout: docs +--- + +# Understanding File Permissions +::lead-p +Working with file permissions is one of the biggest headaches when working with PHP + Docker. This generally is because the PHP server also requires a web server to serve static files. By default, this means multiple users are created in the container, and permissions can get out of hand quickly. +:: + +![Traditional PHP File Permissions Configuration](/images/docs/permissions-privileged.png) + +## Even more frustrating: Development Environments +Even if someone configured a single user in the container to run both the PHP server and the web server, things get even more complicated in development environments. For example, if you have Alice running her Windows Machine with WSL2, she might have a user ID of `1001`. Then you have Bob running his Ubuntu workstation with a user ID of `1002`. Meanwhile, Charlie is running his Docker on his macOS machine (that runs a tiny VM) that has a totally different file permission experience compared to Windows and Linux because of the file system differences. + +If a volume is mounted from the container to the host, the container will write files to the host as `33:33`, which will require sudo/root permissions to edit and delete files. + +## Our industry attempted workarounds +We've seen experiences that allow users to provide an environment variable of `PUID` and `PGID`. Although this is a great user experience, it requires the container user to be privileged, which is a major "no-no" in the security world. It also had downstream file permission errors if the container failed on initialization where logs would be created by the root user and no longer writable by the `www-data` user. + +## Our solution +We focus on providing the tools to give sysadmins the ability to: +- Keep their containers unprivileged by default +- Allow the dynamic reconfiguration of the container user and group ID (at build time only) + +It's a bummer that we can only set the user and group ID at build time, but it's a small price to pay for the security benefits of running unprivileged containers. + +#### How it works +- By default, all our images run `www-data` as the user (`33:33` for Debian and `82:82` for Alpine) +- We provide a script that can be called at build time to change the UID and GID of `www-data` (called `docker-php-serversideup-set-id`) +- If you need to update permissions of service files (example: NGINX, Apache, Unit, etc), you can run the `docker-php-serversideup-set-file-permissions` at build. This will automatically detect the service and update the file permissions accordingly. +- We will use a multi-stage build to ensure that the `docker-php-serversideup-set-id` script is not executed in the construction of the final image + +## Example +Here's an example of ensuring our UID/GID of `www-data` will match the development UID/GID of the host machine, while preserving the default UID/GID of `33:33` for the final image: + + +```dockerfile [Dockerfile] +############################################ +# Base Image +############################################ +FROM serversideup/php:8.4-fpm-nginx-bookworm AS base + +############################################ +# Development Image +############################################ +FROM base AS development + +# Switch to root so we can do root things +USER root + +# Save the build arguments as a variable +ARG USER_ID +ARG GROUP_ID + +# Use the build arguments to change the UID +# and GID of www-data while also changing +# the file permissions for NGINX +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ + \ + # Update the file permissions to match the new UID/GID + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID + +# Drop back to our unprivileged user +USER www-data + +############################################ +# Production Image +############################################ + +# Since we're calling "base", production isn't +# calling any of that permission stuff +FROM base AS production + +# Copy our app files as www-data (33:33) +COPY --chown=www-data:www-data . /var/www/html +``` + +To show a simple Docker Compose file example for development, we could use: + +```yml [compose.yml] +services: + php: + build: + context: . + target: development + args: + # UID and GID must be set as environment variables on the host machine + USER_ID: $UID + GROUP_ID: $GID + ports: + - 80:8080 + volumes: + - .:/var/www/html +``` + +When we run `docker compose up`, our compose file directs us to build the `development` target. This target will run the `docker-php-serversideup-set-id` script to change the UID and GID of `www-data` to match the host machine (assuming `$UID` and `$GID` are set in a zsh/bash profile or something similar). This will allow us to run the container as an unprivileged user while still having the correct permissions to read and write files. + +The best thing is the user can delete files off of their machine without being prompted for sudo permissions. This is because we're aligning the UID/GID of the container with the host machine. + +When it comes to building our image for production, we just use the `production` target, which will copy the files as `www-data` with the default UID/GID of `33:33`. + +## An optimized experience from development to production +If you like the concepts above and you're looking for an optimized experience for developers (especially when it comes simplifying the setting of UID/GID), we recommend checking out our other open source project Spin. Spin is a lightweight wrapper for Docker Compose that allows you to manage your environment from development to production. + +:u-button{to="https://serversideup.net/open-source/spin/" label="Learn more about Spin" aria-label="Learn more about Spin" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From 2732741bb4b710d25ec83e51344769d8753a6e73 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 15:09:42 -0500 Subject: [PATCH 282/304] Refactor file permissions documentation for PHP + Docker, improving clarity on user and permission management in development environments. Updated image path syntax for consistency and enhanced user understanding of file permission challenges across different operating systems. --- docs/content/docs/5.guide/3.understanding-file-permissions.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/content/docs/5.guide/3.understanding-file-permissions.md b/docs/content/docs/5.guide/3.understanding-file-permissions.md index 627c12e3d..4f8fcbeec 100644 --- a/docs/content/docs/5.guide/3.understanding-file-permissions.md +++ b/docs/content/docs/5.guide/3.understanding-file-permissions.md @@ -3,13 +3,11 @@ head.title: 'Understanding file permissions - Docker PHP - Server Side Up' description: 'Eliminate the headache of file permissions when working with PHP + Docker.' layout: docs --- - -# Understanding File Permissions ::lead-p Working with file permissions is one of the biggest headaches when working with PHP + Docker. This generally is because the PHP server also requires a web server to serve static files. By default, this means multiple users are created in the container, and permissions can get out of hand quickly. :: -![Traditional PHP File Permissions Configuration](/images/docs/permissions-privileged.png) +![Traditional PHP File Permissions Configuration](images/docs/permissions-privileged.png){:zoom=false} ## Even more frustrating: Development Environments Even if someone configured a single user in the container to run both the PHP server and the web server, things get even more complicated in development environments. For example, if you have Alice running her Windows Machine with WSL2, she might have a user ID of `1001`. Then you have Bob running his Ubuntu workstation with a user ID of `1002`. Meanwhile, Charlie is running his Docker on his macOS machine (that runs a tiny VM) that has a totally different file permission experience compared to Windows and Linux because of the file system differences. From 2be9f012a6c3c6d60051f2dd3516421c8dcc183f Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 15:10:35 -0500 Subject: [PATCH 283/304] Update SSL configuration documentation to replace 'docker-compose.yml' with 'compose.yml' for consistency across examples. --- .../docs/4.deployment-and-production/4.configuring-ssl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/4.deployment-and-production/4.configuring-ssl.md b/docs/content/docs/4.deployment-and-production/4.configuring-ssl.md index 6252314aa..f402e45ae 100644 --- a/docs/content/docs/4.deployment-and-production/4.configuring-ssl.md +++ b/docs/content/docs/4.deployment-and-production/4.configuring-ssl.md @@ -225,7 +225,7 @@ Self-signed certificates will display warnings in the browser. While browsers will show warnings, self-signed certificates are useful for specific use cases, such as encrypting traffic between containers in a cluster. If you set `SSL_MODE` to `mixed` or `full` without providing a certificate at `$SSL_CERTIFICATE_FILE` and `$SSL_PRIVATE_KEY_FILE`, a self-signed certificate will be automatically generated. -```yml [docker-compose.yml]{7-9} +```yml [compose.yml]{7-9} services: php: image: serversideup/php:8.4-fpm-nginx From 2191083edf3ef17f8bd0264972972f379d25fdf7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 15:16:06 -0500 Subject: [PATCH 284/304] Add comprehensive guide on transitioning from development to production using Docker. Covers deployment challenges, Docker benefits, production image building, deployment strategies, automation, and introduces Spin for simplified workflows. Aims to enhance user confidence in deploying PHP applications. --- .../2.development-to-production.md | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 docs/content/docs/4.deployment-and-production/2.development-to-production.md diff --git a/docs/content/docs/4.deployment-and-production/2.development-to-production.md b/docs/content/docs/4.deployment-and-production/2.development-to-production.md new file mode 100644 index 000000000..3b341a475 --- /dev/null +++ b/docs/content/docs/4.deployment-and-production/2.development-to-production.md @@ -0,0 +1,153 @@ +--- +title: Development to Production +description: 'Learn how to take your PHP application from development to production with confidence using Docker.' +layout: docs +--- + +::lead-p +The real power of Docker isn't just running containers locally — it's running the **exact same environment** from your laptop to production. This guide shows you how to achieve 100% environment replication and deploy with confidence. +:: + +## The traditional deployment problem +If you've deployed applications before, you've probably experienced these frustrations: + +- **"It works on my machine"** - Something breaks in production that worked perfectly locally +- **Environment drift** - Your development, staging, and production environments slowly become different +- **Deployment anxiety** - Every deployment feels risky because you can't be sure what will happen +- **Vendor lock-in** - You're stuck with a PaaS provider because migrating is too painful +- **Downtime** - Users experience interruptions during deployments +- **Configuration chaos** - Infrastructure settings scattered across multiple places + +These problems share a common root cause: **your environments aren't the same**. + +## How Docker solves this +When you containerize your application properly, you package everything your app needs into a single, reproducible unit. This means: + +### 100% environment replication +The container that runs on your MacBook is **identical** to the one running in production. Same PHP version, same extensions, same web server, same configurations. No surprises. + +### Version-controlled infrastructure +Your `compose.yml` and Dockerfiles live in Git alongside your application code. Need to change PHP settings? Update a file, commit it, and deploy. Need to roll back? Just deploy the previous commit. Your infrastructure is now as manageable as your application code. + +### Freedom to choose your host +Because your application is containerized, you can run it anywhere that supports Docker — DigitalOcean, Hetzner, Vultr, AWS, your own hardware, anywhere. If a host raises prices or doesn't meet your needs anymore, migration becomes straightforward instead of a massive project. + +### Simplified scaling +Once you have one container running, scaling to multiple containers becomes much simpler. Add more resources by deploying more containers, not by re-configuring servers. + +## The development to production journey +Let's walk through what this journey actually looks like when you use containers properly. + +### 1. Development (where you are now) +You've already experienced this in our [installation guide](/docs/getting-started/installation). You're running containers locally with Docker Compose, your files are mounted as volumes, and changes appear instantly. This is perfect for development because: + +- You can edit files with your favorite tools +- Changes appear immediately without rebuilding +- You can experiment freely +- Multiple developers work with identical environments + +### 2. Building production images +For production, you'll create optimized images that have your application code **built into the image** instead of mounted as volumes. This ensures: + +- Your code can't be accidentally modified in production +- Images are portable and can run anywhere +- Deployments are atomic — either the new version runs or the old one does +- You can roll back by deploying a previous image + +We cover this in detail in our [packaging guide](/docs/deployment-and-production/packaging-your-app-for-deployment). + +### 3. Choosing a deployment strategy +You have several options for running containers in production: + +**Docker Compose** - Simple and effective for single-server deployments. Great for getting started. + +**Docker Swarm** - Built into Docker, provides zero-downtime deployments, automatic SSL with Let's Encrypt, load balancing, and easy scaling across multiple servers. + +**Kubernetes** - The most powerful option for large-scale deployments. More complex but extremely capable. + +Each option uses the same OCI-compliant container images, so you can start simple and graduate to more sophisticated setups as you grow. + +::tip +We recommend starting with Docker Compose for development environments and Docker Swarm for production. Swarm gives you zero-downtime deployments without the complexity of Kubernetes. +:: + +### 4. Automated deployments +The final piece is automation. Instead of manually SSHing into servers, you'll set up CI/CD pipelines (usually with GitHub Actions) that: + +1. Run your tests on every commit +2. Build production images +3. Deploy automatically to staging or production +4. Roll back automatically if health checks fail + +This means you can deploy with a simple `git push` and have confidence that your application will deploy safely. + +## Simplifying the journey with Spin +While everything described above is achievable with standard Docker tools, there's a learning curve. We've experienced these challenges ourselves, which is why we created [Spin](https://serversideup.net/open-source/spin/). + +::note +**Spin is NOT required** to use these Docker images. They work with any Docker setup. But if you're looking for a simpler path to production, Spin can help significantly. +:: + +### What is Spin? +Spin is a free and open source tool that simplifies Docker workflows from development to production. It helps you: + +- Set up local development environments with a single command +- Provision and deploy to any VPS provider (DigitalOcean, Hetzner, Vultr, etc.) +- Configure Docker Swarm for zero-downtime deployments +- Set up automated SSL with Let's Encrypt +- Deploy with GitHub Actions +- Manage multiple environments (dev, staging, production) + +Think of Spin as a helpful wrapper around Docker that handles the complex bits while teaching you Docker best practices. + +### See it in action +This video demonstrates the complete journey from creating a Laravel project to deploying it to production with zero-downtime deployments: + +:iframe{src="https://www.youtube-nocookie.com/embed/5z2JoEt5XIk?si=u5v-bDN-cMv0OE-C" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9; width: 100%;"} + +The best part? It uses these same `serversideup/php` images you're already familiar with. + +:u-button{to="https://serversideup.net/open-source/spin/" label="Learn more about Spin" aria-label="Learn more about Spin" target="_blank" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### Advanced Laravel features with Spin Pro +For teams running advanced Laravel features (Horizon, Reverb, scheduled tasks, multiple databases), we offer [Spin Pro](https://getspin.pro/) — a paid starter kit that includes pre-configured templates for these services. + +It's a one-time purchase that gives you: +- Project templates with Horizon, Reverb, queues, and more +- Pre-configured GitHub Actions workflows +- Zero-downtime deployment configurations +- Support for multiple database engines (MySQL, PostgreSQL, MariaDB, SQLite) + +Spin Pro exists because we've deployed dozens of Laravel applications ourselves and kept running into the same setup challenges. We built the solution we wished existed, and we're sharing it with the community. + +::note +All Spin Pro templates use the same open source `serversideup/php` images and Spin CLI you're already using. You're buying the templates and automation, not proprietary technology. +:: + +## Alternative approaches +Spin is one tool among many. Here are other common approaches: + +**Manual Docker Compose** - Use Docker Compose directly. More hands-on but gives you complete control. Great for learning. + +**Kubernetes** - Use tools like Helm or Kustomize to deploy to Kubernetes clusters. More complex but extremely powerful for large-scale applications. + +**PaaS Providers** - Services like Laravel Forge, Ploi, or platform providers can deploy containers for you. More expensive but less hands-on management. + +**CI/CD Tools** - GitLab CI, GitHub Actions, or Jenkins can orchestrate your deployments without additional tools. + +Choose whatever works best for your team's skills, budget, and requirements. The important part is that you're using containers, which means you maintain flexibility to change strategies later. + +## Key principles for success +Regardless of which tools you choose, follow these principles: + +1. **Keep development and production similar** - Use the same base images and configurations in both environments +2. **Store infrastructure in Git** - Version control your Docker configurations alongside your code +3. **Automate everything** - Manual deployments lead to mistakes and inconsistency +4. **Use health checks** - Let your orchestrator verify deployments succeed before switching traffic +5. **Practice deployments** - Deploy to staging frequently to catch issues before production +6. **Monitor your containers** - Use logging and monitoring to understand what's happening in production + +## What's next? +Now that you understand the journey from development to production, you're ready to learn how to properly package your application for deployment. + +:u-button{to="/docs/deployment-and-production/packaging-your-app-for-deployment" label="Learn how to package your app for deployment" aria-label="Learn how to package your app for deployment" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From a97c210f42cd4a10931abf4b0579c9ab61d15d3b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 15:19:01 -0500 Subject: [PATCH 285/304] Update migration guide to replace 'docker-compose.yml' with 'compose.yml' for consistency in configuration examples and improve clarity in Docker setup instructions. --- .../docs/5.guide/1.migrating-from-official-php-images.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/5.guide/1.migrating-from-official-php-images.md b/docs/content/docs/5.guide/1.migrating-from-official-php-images.md index ce8a897ae..d7b91c2b7 100644 --- a/docs/content/docs/5.guide/1.migrating-from-official-php-images.md +++ b/docs/content/docs/5.guide/1.migrating-from-official-php-images.md @@ -43,7 +43,7 @@ Making the change will literally take you two seconds. #### Figure out which image you'd like to use Review our [choosing an image](/docs/getting-started/choosing-an-image) guide to help you decide which image you'd like to use. Also, make sure our [default configurations](/docs/getting-started/default-configurations) satisfy your requirements. -#### Update your `Dockerfile` or `docker-compose.yml` file +#### Update your `Dockerfile` or `compose.yml` file :::tip We simply change `php:8.4-apache` to `serversideup/php:8.4-fpm-apache` ::: @@ -61,9 +61,9 @@ FROM serversideup/php:8.4-fpm-apache ``` ::: -**docker-compose.yml** +**compose.yml** :::code-group -```yml [ORIGINAL: docker-compose.yml]{3,5-6} +```yml [ORIGINAL: compose.yml]{3,5-6} services: php: image: php:8.4-apache @@ -71,7 +71,7 @@ services: - 80:80 - 443:443 ``` -```yml [UPDATED: docker-compose.yml]{3,5-6} +```yml [UPDATED: compose.yml]{3,5-6} services: php: image: serversideup/php:8.4-fpm-apache From bd65f8bff1cdfcada7fb7f377341768e493d1d7d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 31 Oct 2025 15:20:09 -0500 Subject: [PATCH 286/304] Fix formatting in packaging documentation by adding a space after the section title for better readability. --- .../3.packaging-your-app-for-deployment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/4.deployment-and-production/3.packaging-your-app-for-deployment.md b/docs/content/docs/4.deployment-and-production/3.packaging-your-app-for-deployment.md index 1c0d20b62..800f8cff0 100644 --- a/docs/content/docs/4.deployment-and-production/3.packaging-your-app-for-deployment.md +++ b/docs/content/docs/4.deployment-and-production/3.packaging-your-app-for-deployment.md @@ -108,7 +108,7 @@ This lets you verify your production image works correctly before deploying to r ## Best practices for production images -### 1.`Use specific image tags +### 1. Use specific image tags Don't use `latest` tags in production. Use specific versions: ```dockerfile From 464c25ff0b26b195c4617688a936238ea8629683 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 3 Nov 2025 13:39:08 -0600 Subject: [PATCH 287/304] Add documentation on adding custom start-up scripts in Docker PHP. Includes requirements, execution order, and examples for integrating scripts into the entrypoint.d directory, as well as guidance on using S6 Overlay for long-running services. --- .../3.adding-your-own-start-up-scripts.md | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/content/docs/6.customizing-the-image/3.adding-your-own-start-up-scripts.md diff --git a/docs/content/docs/6.customizing-the-image/3.adding-your-own-start-up-scripts.md b/docs/content/docs/6.customizing-the-image/3.adding-your-own-start-up-scripts.md new file mode 100644 index 000000000..e9fa7848e --- /dev/null +++ b/docs/content/docs/6.customizing-the-image/3.adding-your-own-start-up-scripts.md @@ -0,0 +1,130 @@ +--- +head.title: 'Adding your own start up scripts - Docker PHP - Server Side Up' +description: 'Learn how to use our entrypoint.d directory to customize your container start up experience.' +layout: docs +title: Adding Start Up Scripts +--- +::lead-p +We provide a few [default entrypoint scripts](/docs/getting-started/default-configurations#default-entrypoint-scripts) to get you going, but sometimes you want to just add your own. We've made it easy to do that with our `entrypoint.d` directory. +:: + +## Entrypoint Script Requirements +::note +Before you write your entry point script, be aware of the following requirements. Your script should: +- Be executable (755 permissions) +- Located in the `/etc/entrypoint.d` directory +- Have the file extension ending in `.sh` +:: +::tip +We recommend writing your script in `/bin/sh` for the best compatibility between Alpine and Debian. If you choose to use `/bin/bash`, your script will only be able to run on Debian-based images. +:: + +## Choose your execution order +Since [we provide default entrypoint scripts](/docs/getting-started/default-configurations#default-entrypoint-scripts), you may want to choose the order in which your scripts are executed. We've made it easy to do that by prefixing your script with a number. The lower the number, the earlier it will be executed. + +::tip +If you want to disable our entrypoint scripts, you can set `DISABLE_DEFAULT_CONFIG` to `true` in your environment variables. +:: + +## Long running services +::note +Don't use entrypoint scripts for long-running services. You want your services to be monitored and restarted if they crash. +:: + +Anything in the `/etc/entrypoint.d` directory are scripts that are intended to run quickly and then move on. If you run a service as an entrypoint script, that service may crash and not be restarted. + +Instead, learn about [using S6 overlay](/docs/guide/using-s6-overlay) so your services can be properly initialized and monitored. See the [S6 Overylay project](https://github.com/just-containers/s6-overlay) for more details on how to write your own S6 service. + +## Example: Create a custom entrypoint script +In this example, let's create a `99-my-script.sh` so it executes after all the other default scripts. + +::code-tree{defaultValue="Dockerfile"} + +```bash [entrypoint.d/99-my-script.sh] +#!/bin/sh +echo "👋 Hello, world!" +``` + +```dockerfile [Dockerfile] +FROM serversideup/php:8.4-fpm-nginx + +# Copy our scripts as executable +COPY --chmod=755 ./entrypoint.d/ /etc/entrypoint.d/ +``` + +```yml [compose.yml] +services: + php: + build: + context: . + dockerfile: Dockerfile + ports: + - 80:8080 + volumes: + - .:/var/www/html +``` + +```php [public/index.php] + +``` + +:: + + +In the example above, you can see in the `Dockerfile` we are copying our `entrypoint.d` directory to `/etc/entrypoint.d/` in the container. We're also setting the permissions to `755` so our scripts are executable. + +## Running our example +When we run `docker compose up`, we can confirm our script is executing by checking the logs: + +::code-panel +--- +label: "Output of \"docker compose up\"" +--- +```txt +php-1 | 👉 [NOTICE]: Improve PHP performance by setting PHP_OPCACHE_ENABLE=1 (recommended for production). +php-1 | (init-webserver-config): Processing /etc/nginx/nginx.conf.template → /etc/nginx/nginx.conf... +php-1 | (init-webserver-config): Processing /etc/nginx/site-opts.d/http.conf.template → /etc/nginx/site-opts.d/http.conf... +php-1 | (init-webserver-config): Processing /etc/nginx/site-opts.d/https.conf.template → /etc/nginx/site-opts.d/https.conf... +php-1 | ℹ️ NOTICE (init-webserver-config): Enabling NGINX site with SSL "off"... +php-1 | 👋 Hello, world! +php-1 | [03-Nov-2025 19:35:29] NOTICE: fpm is running, pid 93 +php-1 | [03-Nov-2025 19:35:29] NOTICE: ready to handle connections +php-1 | HTTP Status Code: 200 +php-1 | ✅ NGINX + PHP-FPM is running correctly. +``` +:: + +You can see our `👋 Hello, world!` is executing *after* the initialization of `10-init-unit.sh`. + +## Advanced Scenarios: S6 Overlay dependencies +If you want to customize an image that uses S6 Overlay (`fpm-nginx` or `fpm-apache`), you may have an advanced scenario where you have a custom S6 service that needs to be executed after one of our entrypoint scripts. In order to do this, you'll need to move all our scripts from the `/etc/entrypoint.d` directory to the `/etc/s6-overlay/scripts` directory. This would be a very time consuming scenario if you did this manually, but thankfully you can use our `docker-php-serversideup-s6-init` script to do this for you. + +```dockerfile [Dockerfile] +FROM serversideup/php:8.4-fpm-nginx + +# Set the user to root for our build steps +USER root + +# If you have your own one-shot scripts, copy them to the entrypoint.d directory +COPY --chmod=755 ./entrypoint.d/ /etc/entrypoint.d/ + +# Copy our entrypoint scripts into the S6 Overlay scripts directory +RUN docker-php-serversideup-s6-init + +# If you have your own long running services, copy them to the s6 directory +COPY --chmod=755 ./my-s6-service/ /etc/s6-overlay/s6-rc.d/my-s6-service/ + +# Drop back to the non-root user +USER www-data +``` + +In the above file, we're copying our "one-shot" scripts to the `/etc/entrypoint.d` directory and our long running services to the `/etc/s6-overlay` directory. One-shot scripts are scripts that are intended to run quickly and then move on. Long running services are services that are intended to run for a long time and need to be monitored and restarted if they crash. + +The magic happens when we run `docker-php-serversideup-s6-init`. This script will move all our scripts from the `/etc/entrypoint.d` directory to the `/etc/s6-overlay/scripts` directory and set the correct dependencies for our S6 services. + +You can now reference our script names as dependencies in your own S6 service. + +:u-button{to="https://github.com/just-containers/s6-overlay" label="Learn more about S6 Overlay" aria-label="Learn more about S6 Overlay" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold" target="_blank"} From c73cd4b7d9ca281b41175bb9a3a77bb8c9f2fc75 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Mon, 3 Nov 2025 13:40:47 -0600 Subject: [PATCH 288/304] Add documentation for changing common PHP settings in Docker. Includes examples for using environment variables and custom php.ini files, along with validation methods to ensure changes are applied correctly. --- .../1.changing-common-php-settings.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/content/docs/6.customizing-the-image/1.changing-common-php-settings.md diff --git a/docs/content/docs/6.customizing-the-image/1.changing-common-php-settings.md b/docs/content/docs/6.customizing-the-image/1.changing-common-php-settings.md new file mode 100644 index 000000000..7aa8cb08c --- /dev/null +++ b/docs/content/docs/6.customizing-the-image/1.changing-common-php-settings.md @@ -0,0 +1,71 @@ +--- +head.title: 'Changing common PHP settings - Docker PHP - Server Side Up' +description: 'Learn how to change common PHP settings with environment variables or your own php.ini file.' +layout: docs +title: Changing php.ini settings +--- + +::lead-p +Instead of going through the effort of writing custom scripts or mounting files to change PHP settings, have the power to change common settings with the simplicity of an environment variable. +:: + +## Common Examples +All our environment variables are documented and can be found in our environment variable specification documentation. + +:u-button{to="/docs/reference/environment-variable-specification" label="Environment Variable Specification" aria-label="Environment Variable Specification" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +Here are a few examples on how you can change common PHP settings. + +```yml [compose.yml] {4-6} +services: + php: + image: serversideup/php:8.2.12-unit-bookworm + environment: + PHP_POST_MAX_SIZE: "500M" + PHP_UPLOAD_MAX_FILE_SIZE: "500M" + SSL_MODE: "mixed" + ports: + - 80:8080 + - 443:8443 + volumes: + - .:/var/www/html/ +``` + +You can also adjust environment variables using the Docker CLI. + +```bash [Terminal] +docker run -d \ + -p 80:8080 \ + -v $(pwd):/var/www/html \ + -e PHP_DATE_TIMEZONE="America/New_York" \ + serversideup/php:8.2.12-fpm-nginx-bookworm +``` + +## Setting your own php.ini +PHP will read the `php.ini` file from the `/usr/local/etc/php/conf.d/` directory in alphabetical order. This means you can create your own `php.ini` file and mount it to the container to override the default settings. + +For example, we can create this file in our project directory: +```ini [zzz-custom-php.ini] +mysqli.max_persistent = 300 +opcache.max_file_size = 10M +opcache.log_verbosity_level = 3 +``` + +Then in our Dockerfile, we can copy this file to the `/usr/local/etc/php/conf.d/` directory: +```dockerfile [Dockerfile] +FROM serversideup/php:8.4-fpm-nginx-bookworm + +COPY zzz-custom-php.ini /usr/local/etc/php/conf.d/ +``` + +If you prefer to remove the default `php.ini` file, you can do so by adding the following line to your Dockerfile: + +```dockerfile [Dockerfile] +FROM serversideup/php:8.4-fpm-nginx-bookworm + +RUN rm /usr/local/etc/php/conf.d/serversideup-docker-php.ini +COPY zzz-custom-php.ini /usr/local/etc/php/conf.d/ +``` + +## Validating changes +It's always best to validate your changes by running `php -i` via the command line or using [`phpinfo()`](https://www.php.net/manual/en/function.phpinfo.php){target="_blank"}. \ No newline at end of file From 147c0eb49a1fcaa5a4b6e46a48aa14c0bc38f8f7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 09:16:42 -0600 Subject: [PATCH 289/304] Add new host icons and update Nuxt configuration to include them. Introduced icons for DigitalOcean, Hetzner, Vultr, and Sevalla, enhancing visual representation of hosting options in the application. --- .../icons/hosts/digitalocean-square.svg | 6 ++++++ docs/app/assets/icons/hosts/digitalocean.svg | 20 +++++++++++++++++++ .../app/assets/icons/hosts/hetzner-square.svg | 3 +++ docs/app/assets/icons/hosts/hetzner.svg | 9 +++++++++ .../app/assets/icons/hosts/sevalla-square.svg | 6 ++++++ docs/app/assets/icons/hosts/sevalla.svg | 8 ++++++++ docs/app/assets/icons/hosts/vultr-square.svg | 6 ++++++ docs/app/assets/icons/hosts/vultr.svg | 11 ++++++++++ docs/nuxt.config.ts | 4 ++++ 9 files changed, 73 insertions(+) create mode 100644 docs/app/assets/icons/hosts/digitalocean-square.svg create mode 100644 docs/app/assets/icons/hosts/digitalocean.svg create mode 100644 docs/app/assets/icons/hosts/hetzner-square.svg create mode 100644 docs/app/assets/icons/hosts/hetzner.svg create mode 100644 docs/app/assets/icons/hosts/sevalla-square.svg create mode 100644 docs/app/assets/icons/hosts/sevalla.svg create mode 100644 docs/app/assets/icons/hosts/vultr-square.svg create mode 100644 docs/app/assets/icons/hosts/vultr.svg diff --git a/docs/app/assets/icons/hosts/digitalocean-square.svg b/docs/app/assets/icons/hosts/digitalocean-square.svg new file mode 100644 index 000000000..ef70db6f7 --- /dev/null +++ b/docs/app/assets/icons/hosts/digitalocean-square.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/app/assets/icons/hosts/digitalocean.svg b/docs/app/assets/icons/hosts/digitalocean.svg new file mode 100644 index 000000000..79ba1c65b --- /dev/null +++ b/docs/app/assets/icons/hosts/digitalocean.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/app/assets/icons/hosts/hetzner-square.svg b/docs/app/assets/icons/hosts/hetzner-square.svg new file mode 100644 index 000000000..a91dce2f6 --- /dev/null +++ b/docs/app/assets/icons/hosts/hetzner-square.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/app/assets/icons/hosts/hetzner.svg b/docs/app/assets/icons/hosts/hetzner.svg new file mode 100644 index 000000000..f291a5f91 --- /dev/null +++ b/docs/app/assets/icons/hosts/hetzner.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/docs/app/assets/icons/hosts/sevalla-square.svg b/docs/app/assets/icons/hosts/sevalla-square.svg new file mode 100644 index 000000000..f431d5332 --- /dev/null +++ b/docs/app/assets/icons/hosts/sevalla-square.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/app/assets/icons/hosts/sevalla.svg b/docs/app/assets/icons/hosts/sevalla.svg new file mode 100644 index 000000000..618233fe3 --- /dev/null +++ b/docs/app/assets/icons/hosts/sevalla.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/app/assets/icons/hosts/vultr-square.svg b/docs/app/assets/icons/hosts/vultr-square.svg new file mode 100644 index 000000000..8f4866d9b --- /dev/null +++ b/docs/app/assets/icons/hosts/vultr-square.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/app/assets/icons/hosts/vultr.svg b/docs/app/assets/icons/hosts/vultr.svg new file mode 100644 index 000000000..ee7dda82a --- /dev/null +++ b/docs/app/assets/icons/hosts/vultr.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index 265d6043b..438972bea 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -68,6 +68,10 @@ export default defineNuxtConfig({ prefix: 'features', dir: './app/assets/icons/features' }, + { + prefix: 'hosts', + dir: './app/assets/icons/hosts' + }, { prefix: 'services', dir: './app/assets/icons/services' From ef0a0fba3d9b9acde8abcbbfa5cc9b156f73e3ed Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 09:26:09 -0600 Subject: [PATCH 290/304] Add comprehensive documentation on choosing a hosting provider for containerized PHP applications. The guide covers portability, hosting options, recommended hosts, and key considerations for selecting the right host, enhancing user understanding of deployment strategies. --- .../5.choosing-a-host.md | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 docs/content/docs/4.deployment-and-production/5.choosing-a-host.md diff --git a/docs/content/docs/4.deployment-and-production/5.choosing-a-host.md b/docs/content/docs/4.deployment-and-production/5.choosing-a-host.md new file mode 100644 index 000000000..9c9aa7c69 --- /dev/null +++ b/docs/content/docs/4.deployment-and-production/5.choosing-a-host.md @@ -0,0 +1,158 @@ +--- +title: Choosing a Host +description: 'Learn how to choose the right hosting provider for your containerized PHP application.' +layout: docs +--- + +::lead-p +One of the biggest advantages of containerizing your application is the ability to run your application anywhere. This gives you the freedom to choose the best host for your needs. Here's a guide to help you choose the right host for your needs. +:: + +## The beauty of portability +Because your application runs in a container, you're no longer tied to a specific hosting provider. The same container that runs on your laptop will run on: + +- Any VPS provider (DigitalOcean, Hetzner, Vultr, Linode, etc.) +- Cloud platforms (AWS, Google Cloud, Azure) +- Managed Kubernetes services +- Your own hardware in a data center +- Even a Raspberry Pi in your closet + +This portability means: +- **No vendor lock-in** - Switch hosts without rebuilding your application +- **Price shopping** - Move to cheaper providers if prices increase +- **Multi-cloud strategies** - Run on multiple providers for redundancy +- **Freedom to experiment** - Try different hosts without risk + +## Hosting options + +Choose your hosting based on your team's experience, budget, and application scale. Here's a quick comparison: + +| Hosting Type | Best For | Monthly Cost | Management Level | When to Choose | +|-------------|----------|--------------|------------------|----------------| +| **VPS** | Most PHP apps | $5-20 | Self-managed | You want control and best value | +| **Managed Kubernetes** | Enterprise/high-traffic | $70-100+ | Moderate | You need advanced orchestration | +| **Cloud Platforms** | Variable workloads | $20-50+ | Low | You want integrated cloud services | +| **PaaS** | Quick deployment | $25-100+ | Minimal | You want zero infrastructure hassle | + +::tip +**Our recommendation:** Start with a VPS unless you have specific needs. A $10/month VPS can handle what costs $50-100/month on PaaS, and you'll have full control over your infrastructure. +:: + +## Recommended Hosts +Below are our recommended hosts from our experience. Some links may contain referral codes, but these codes give you free credits to help you get started. These referral programs do not influence our recommendations. + +### VPS Hosts +::card-group + +::card +--- +title: Hetzner +icon: i-hosts-hetzner-square +to: https://hetzner.cloud/?ref=lhLUIrkdUPhl +target: _blank +--- +Hetzner is infamously known for its low prices and high performance. They are a great choice for small to medium-sized applications. +:: + +::card +--- +title: Vultr +icon: i-hosts-vultr-square +to: https://www.vultr.com/?ref=7093917 +target: _blank +--- +Vultr has great performance and and over 32 global data centers. +:: + +::card +--- +title: DigitalOcean +icon: i-hosts-digitalocean-square +to: https://m.do.co/c/f3bad4b927ca +target: _blank +--- +DigitalOcean is most known for it's simple user experience and vast support of many developer tools. +:: + +:: + +### PaaS Hosts +::card-group + +::card +--- +title: Sevalla +icon: i-hosts-sevalla-square +to: https://sevalla.com/ +target: _blank +--- +Sevalla is a modern PaaS with transparent usage-based pricing and no artificial limits. **Full disclosure:** Sevalla sponsors this project, but we genuinely recommend them for their refreshingly honest approach to PaaS pricing. +:: + +:: + +## Simplifying VPS setup with Spin +::tip{to="https://serversideup.net/open-source/spin/" target="_blank"} +[Spin](https://serversideup.net/open-source/spin/) is a free and open source tool that simplifies Docker workflows from development to production. +:: + + +Spin can automatically provision and configure servers on Hetzner, DigitalOcean, and Vultr, setting up Docker Swarm for zero-downtime deployments. It handles the infrastructure complexity while you maintain full control of your server. Here's a quick example with Laravel: + +:iframe{src="https://www.youtube-nocookie.com/embed/5z2JoEt5XIk?si=u5v-bDN-cMv0OE-C" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9; width: 100%;"} + +Spin is *not required* to run these Docker images, but it can help simplify the setup process. Spin was designed to have native integration with these images, but it can be used with any Docker setup. + +:u-button{to="https://serversideup.net/open-source/spin/" label="Learn more about Spin" aria-label="Learn more about Spin" target="_blank" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## What to consider when choosing a host + +- **Budget** - VPS offers best value ($10-20/mo), PaaS most expensive ($25-100+/mo) +- **Technical expertise** - VPS requires Linux knowledge, PaaS abstracts complexity +- **Scale** - Small/medium apps thrive on VPS, very large apps benefit from Kubernetes +- **Time investment** - PaaS saves time but costs more, VPS costs less but needs more hands-on +- **Data sovereignty** - Choose providers with data centers in your required regions +- **Growth trajectory** - VPS and Kubernetes scale cost-effectively, PaaS gets expensive + +#### Specifically for PaaS providers +- **Pricing** - Is the pricing transparent and upfront? Are there alerts if you're approaching your limits? +- **Debugging** - Are you able to access logs easily and debug issues? +- **Vendor lock-in** - Are you building around a proprietary platform, or can you easily migrate to another provider if needed? + +## Our recommendation for most projects + +If you're running a typical PHP application (Laravel, WordPress, custom app), here's our suggested progression: + +::steps{level="4"} + +#### Starting out: VPS provider like Hetzner or DigitalOcean ($5-10/month) +- Use Docker Swarm for single-server deployment +- Simple, affordable, and plenty of room to grow + +#### Growing: Performance tuning and hardware upgrades ($20-40/month) +- Focus on fine tuning your application for performance +- Upgrade your server hardware if needed (simple as a few clicks) + +#### At scale: Managed Kubernetes or multiple VPS servers (>$100/month) +- Move to Kubernetes when you need sophisticated orchestration +- Only consider if you *must* exceed [99.999% of uptime](https://uptime.is/99.999){target="_blank"} + +:: + +Most PHP applications never need to move beyond a well-configured VPS. A $20/month server can serve thousands of requests per second when properly optimized. + +::note +Don't let fancy infrastructure distract you from building your application. Pick what works for you. Start simple, prove your concept, then scale up as needed. +:: + +## Key requirements for any host +Regardless of which provider you choose, ensure they support: + +- **Docker installation** - Ability to install and run Docker (most Linux VPS hosts support this) +- **Adequate resources** - At minimum 1GB RAM, 1 CPU core (2GB+ recommended for most applications) +- **Security** - Strong security policies and practices to protect your application and data +- **Performance** - Good performance and quality hardware to handle your application's traffic +- **Network reliability** - Good uptime SLA and network performance +- **Responsive support team** - Quality and responsive support team to help you out when you need it +- **Data center location** - Close to your users for better performance +- **Backup options** - Either provided by host or easily implemented yourself (and we strongly recommend the [3-2-1 backup rule](https://en.wikipedia.org/wiki/Backup){target="_blank"}) From e3ccdb04acb8657b230dca59ece63bd68eb4b3d4 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 09:31:51 -0600 Subject: [PATCH 291/304] Simplified examples on FPM --- docs/content/docs/2.image-variations/fpm.md | 67 ++++----------------- 1 file changed, 11 insertions(+), 56 deletions(-) diff --git a/docs/content/docs/2.image-variations/fpm.md b/docs/content/docs/2.image-variations/fpm.md index 7fbd392c9..6896e6466 100644 --- a/docs/content/docs/2.image-variations/fpm.md +++ b/docs/content/docs/2.image-variations/fpm.md @@ -45,11 +45,13 @@ Use the FPM variation when you need to: Unlike variations that include a web server, the FPM variation only runs PHP-FPM, which listens on port 9000 for FastCGI requests. You'll need a separate web server (like NGINX, Apache, or Caddy) to: -1. Accept HTTP requests from clients -2. Serve static files directly (CSS, JavaScript, images) -3. Forward PHP requests to the FPM container on port 9000 -4. Return the PHP-FPM response back to the client +::steps{level="4"} +#### Accept HTTP requests from clients +#### Serve static files directly (CSS, JavaScript, images) +#### Forward PHP requests to the FPM container on port 9000 +#### Return the PHP-FPM response back to the client +:: This architecture gives you maximum flexibility but requires more configuration than the all-in-one variations. ::note @@ -100,10 +102,6 @@ server { fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } - - location ~ /\.(?!well-known).* { - deny all; - } } ``` @@ -111,52 +109,13 @@ server { Notice how the `fastcgi_pass` directive points to `php:9000`. This is the service name from your Docker Compose file. Docker's networking allows services to communicate using their service names. :: -### Kubernetes Example -The FPM variation is particularly well-suited for Kubernetes deployments where you might have separate containers in the same pod. - -```yml [deployment.yaml] -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-app -spec: - replicas: 3 - selector: - matchLabels: - app: my-app - template: - metadata: - labels: - app: my-app - spec: - containers: - - name: nginx - image: nginx:alpine - ports: - - containerPort: 80 - volumeMounts: - - name: app-code - mountPath: /var/www/html - - name: nginx-config - mountPath: /etc/nginx/conf.d - - - name: php-fpm - image: serversideup/php:8.4-fpm - volumeMounts: - - name: app-code - mountPath: /var/www/html - - volumes: - - name: app-code - emptyDir: {} - - name: nginx-config - configMap: - name: nginx-config -``` - ### Health Check The FPM variation includes [`php-fpm-healthcheck`](https://github.com/renatomefi/php-fpm-healthcheck){target="_blank"}, a POSIX-compliant script that monitors PHP-FPM's `/status` endpoint to verify the service is healthy. +::tip +The `php-fpm-healthcheck` script can also monitor specific metrics like accepted connections or queue length. For example, you could fail the health check if the listen queue exceeds 10 processes: `php-fpm-healthcheck --listen-queue=10` +:: + ```yml [compose.yml]{7-10} services: php: @@ -164,16 +123,12 @@ services: volumes: - ./:/var/www/html healthcheck: - test: ["CMD", "php-fpm-healthcheck"] + test: ["CMD", "php-fpm-healthcheck", "--listen-queue=10"] interval: 10s timeout: 3s retries: 3 ``` -::tip -The `php-fpm-healthcheck` script can also monitor specific metrics like accepted connections or queue length. For example, you could fail the health check if the listen queue exceeds 10 processes: `php-fpm-healthcheck --listen-queue=10` -:: - ## Environment Variables The FPM variation supports extensive customization through environment variables. Here are some common ones: From 8395ba3350ab0ec850e4c72d1a24b93136232b5a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 09:41:45 -0600 Subject: [PATCH 292/304] Add health check information for Laravel in FPM-Apache and FPM-NGINX documentation. Include a link to the guide on using healthchecks with Laravel for better user guidance. --- docs/content/docs/2.image-variations/fpm-apache.md | 7 +++++++ docs/content/docs/2.image-variations/fpm-nginx.md | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/docs/content/docs/2.image-variations/fpm-apache.md b/docs/content/docs/2.image-variations/fpm-apache.md index 07dfd4184..a39183fc8 100644 --- a/docs/content/docs/2.image-variations/fpm-apache.md +++ b/docs/content/docs/2.image-variations/fpm-apache.md @@ -149,7 +149,14 @@ The FPM-Apache variation includes a built-in health check that verifies Apache i The health check endpoint is configurable via the `HEALTHCHECK_PATH` environment variable, which defaults to `/healthcheck`. :: +If you are using Laravel, you can use the `/up` route to validate that Laravel is running and healthy. + +:u-button{to="/docs/guide/using-healthchecks-with-laravel" label="Learn more about using healthchecks with Laravel" aria-label="Learn more about using healthchecks with Laravel" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + ## SSL/TLS Support +::tip{to="/docs/deployment-and-production/configuring-ssl"} +For more information on SSL, see the [Configuring SSL](/docs/deployment-and-production/configuring-ssl) guide. +:: The FPM-Apache variation includes built-in SSL support with self-signed certificates for development. ### Enabling SSL diff --git a/docs/content/docs/2.image-variations/fpm-nginx.md b/docs/content/docs/2.image-variations/fpm-nginx.md index 6b5785f37..f4ace3da7 100644 --- a/docs/content/docs/2.image-variations/fpm-nginx.md +++ b/docs/content/docs/2.image-variations/fpm-nginx.md @@ -152,6 +152,10 @@ The FPM-NGINX variation includes a built-in health check that verifies NGINX is The health check endpoint is configurable via the `HEALTHCHECK_PATH` environment variable, which defaults to `/healthcheck`. :: +If you are using Laravel, you can use the `/up` route to validate that Laravel is running and healthy. + +:u-button{to="/docs/guide/using-healthchecks-with-laravel" label="Learn more about using healthchecks with Laravel" aria-label="Learn more about using healthchecks with Laravel" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + ## SSL/TLS Support The FPM-NGINX variation includes built-in SSL support with self-signed certificates for development. From d163b132726ba3761546d415d3618a3215c9e6c7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 10:27:54 -0600 Subject: [PATCH 293/304] Update environment variable documentation to replace `CADDY_APP_PUBLIC_PATH` with `CADDY_SERVER_ROOT` for clarity and consistency. Adjust Caddyfile configuration to reflect the new variable, ensuring accurate root directory settings for the Caddy server. --- .../docs/8.reference/1.environment-variable-specification.md | 2 +- src/variations/frankenphp/etc/frankenphp/Caddyfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index 760ecc17e..3fbbd126d 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -45,7 +45,6 @@ Setting environment variables all depends on what method you're using to run you `AUTORUN_LARAVEL_STORAGE_LINK`
*Default: "true"*|Automatically run "php artisan storage:link" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `AUTORUN_LARAVEL_VIEW_CACHE`
*Default: "true"*|Automatically run "php artisan view:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all `CADDY_ADMIN`
*Default: "off"*|Enable Caddy admin interface. (Official docs)|frankenphp -`CADDY_APP_PUBLIC_PATH`
*Default: "/var/www/html/public"*|The path to your public directory for your app. (Official docs)|frankenphp `CADDY_AUTO_HTTPS`
*Default: "off"*|Enable automatic HTTPS. (Official docs)|frankenphp `CADDY_GLOBAL_OPTIONS`
*Default: ""*|Set global options for the Caddy server. (Official docs)|frankenphp `CADDY_HTTP_PORT`
*Default: "8080"*|Set the port for HTTP. (Official docs)|frankenphp @@ -57,6 +56,7 @@ Setting environment variables all depends on what method you're using to run you `CADDY_LOG_OUTPUT`
*Default: "stdout"*|Set the output for the Caddy log. (Official docs)|frankenphp `CADDY_PHP_SERVER_OPTIONS`
*Default: ""*|Set PHP server options for the Caddy server. (Official docs)|frankenphp `CADDY_SERVER_EXTRA_DIRECTIVES`
*Default: ""*|Set extra directives for the Caddy server. (Official docs)|frankenphp +`CADDY_SERVER_ROOT`
*Default: "/var/www/html/public"*|Set the root directory for the Caddy server. (Official docs)|frankenphp `COMPOSER_ALLOW_SUPERUSER`
*Default: "1"*|Disable warning about running as super-user|all `COMPOSER_HOME`
*Default: "/composer"*|The COMPOSER_HOME variable allows you to change the Composer home directory. This is a hidden, global (per-user on the machine) directory that is shared between all projects.|all `COMPOSER_MAX_PARALLEL_HTTP`
*Default: "24"*|Set to an integer to configure how many files can be downloaded in parallel. Composer ships with 12 by default and must be between 1 and 50. If your proxy has issues with concurrency maybe you want to lower this. Increasing it should generally not result in performance gains.|all diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index a785add5f..566911126 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -71,7 +71,7 @@ fd00::/8 \ # Common app logic; reused across all modes (php-app-common) { - root * {$CADDY_APP_PUBLIC_PATH:/var/www/html/public} + root * {$CADDY_SERVER_ROOT:/var/www/html/public} encode zstd br gzip From 8a7603748f9b1cab2f35bac2482ab3ca580300c0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 10:28:25 -0600 Subject: [PATCH 294/304] Update Dockerfile to replace `CADDY_APP_PUBLIC_PATH` with `CADDY_SERVER_ROOT` for improved clarity in server configuration. --- src/variations/frankenphp/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index b5ed10167..05f6cb3e3 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -127,7 +127,6 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ ENV APP_BASE_DIR=/var/www/html \ CADDY_ADMIN="off" \ - CADDY_APP_PUBLIC_PATH="/var/www/html/public" \ CADDY_AUTO_HTTPS="off" \ CADDY_GLOBAL_OPTIONS="" \ CADDY_HTTP_PORT="8080" \ @@ -138,6 +137,7 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ CADDY_LOG_OUTPUT="stdout" \ CADDY_PHP_SERVER_OPTIONS="" \ CADDY_SERVER_EXTRA_DIRECTIVES="" \ + CADDY_SERVER_ROOT="/var/www/html/public" \ COMPOSER_ALLOW_SUPERUSER=1 \ COMPOSER_HOME=/composer \ COMPOSER_MAX_PARALLEL_HTTP=24 \ From 5151886812a73263aec7510b3b85a7aab0e696e7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 10:48:46 -0600 Subject: [PATCH 295/304] A-Z the caddy variables --- .../docs/8.reference/1.environment-variable-specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index 3fbbd126d..9587b4675 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -48,14 +48,14 @@ Setting environment variables all depends on what method you're using to run you `CADDY_AUTO_HTTPS`
*Default: "off"*|Enable automatic HTTPS. (Official docs)|frankenphp `CADDY_GLOBAL_OPTIONS`
*Default: ""*|Set global options for the Caddy server. (Official docs)|frankenphp `CADDY_HTTP_PORT`
*Default: "8080"*|Set the port for HTTP. (Official docs)|frankenphp -`CADDY_HTTPS_PORT`
*Default: "8443"*|Set the port for HTTPS. (Official docs)|frankenphp `CADDY_HTTP_SERVER_ADDRESS`
*Default: "http://"*|Set the server address for HTTP. (Official docs)|frankenphp +`CADDY_HTTPS_PORT`
*Default: "8443"*|Set the port for HTTPS. (Official docs)|frankenphp `CADDY_HTTPS_SERVER_ADDRESS`
*Default: "https://"*|Set the server address for HTTPS. (Official docs)|frankenphp `CADDY_LOG_FORMAT`
*Default: "console"*|Set the format for the Caddy log. (Official docs)|frankenphp -`CADDY_SERVER_LOG_LEVEL`
*Default: "warn"*|Set the server log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_SERVER_LOG_LEVEL` takes precedence. (Official docs)|frankenphp `CADDY_LOG_OUTPUT`
*Default: "stdout"*|Set the output for the Caddy log. (Official docs)|frankenphp `CADDY_PHP_SERVER_OPTIONS`
*Default: ""*|Set PHP server options for the Caddy server. (Official docs)|frankenphp `CADDY_SERVER_EXTRA_DIRECTIVES`
*Default: ""*|Set extra directives for the Caddy server. (Official docs)|frankenphp +`CADDY_SERVER_LOG_LEVEL`
*Default: "warn"*|Set the server log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_SERVER_LOG_LEVEL` takes precedence. (Official docs)|frankenphp `CADDY_SERVER_ROOT`
*Default: "/var/www/html/public"*|Set the root directory for the Caddy server. (Official docs)|frankenphp `COMPOSER_ALLOW_SUPERUSER`
*Default: "1"*|Disable warning about running as super-user|all `COMPOSER_HOME`
*Default: "/composer"*|The COMPOSER_HOME variable allows you to change the Composer home directory. This is a hidden, global (per-user on the machine) directory that is shared between all projects.|all From 60d4a790e4d5ebc89edd851bfb46efe13dee2361 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 10:49:00 -0600 Subject: [PATCH 296/304] Add comprehensive documentation for the FrankenPHP variation of the serversideup/php image. This includes usage guidelines, feature comparisons, setup instructions, and environment variable configurations, aimed at enhancing user understanding and adoption of this modern PHP application server. --- .../docs/2.image-variations/frankenphp.md | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 docs/content/docs/2.image-variations/frankenphp.md diff --git a/docs/content/docs/2.image-variations/frankenphp.md b/docs/content/docs/2.image-variations/frankenphp.md new file mode 100644 index 000000000..2a7a414c7 --- /dev/null +++ b/docs/content/docs/2.image-variations/frankenphp.md @@ -0,0 +1,308 @@ +--- +title: FrankenPHP +description: 'Learn how to use the FrankenPHP variation of the serversideup/php image.' +--- + +::lead-p +The FrankenPHP variation is a modern application server built on top of the Caddy web server. It runs PHP and the web server in a single process, eliminating the complexity of managing PHP-FPM and a separate web server. + +This is the cutting-edge variation that offers worker mode, automatic HTTPS, and modern protocols like HTTP/2 and HTTP/3. It's the recommended variation for new Laravel projects seeking maximum performance. +:: + +## When to Use FrankenPHP +Use the FrankenPHP variation when you need to: + +- Run Laravel Octane with maximum performance +- Use worker mode to keep your application in memory +- Get automatic HTTPS with zero configuration +- Support modern protocols like HTTP/2 and HTTP/3 +- Simplify your container architecture (single process) +- Deploy Symfony applications with the Runtime component + +#### Perfect for +- Laravel Octane applications +- Symfony applications using the Runtime component +- Modern PHP applications that can benefit from worker mode +- Projects requiring automatic HTTPS +- High-performance APIs that benefit from persistent connections +- Teams wanting the latest and greatest in PHP application servers +- Apps that need PHP 8.3 or newer + +## Comparing FrankenPHP to Other Variations + +| Feature | FrankenPHP | FPM-NGINX | FPM-Apache | +|---------|-----------|-----------|------------| +| Performance | ⚡️ Excellent (worker mode) | ✅ Very Good | ✅ Good | +| Setup Complexity | ✅ Simple | ✅ Simple | ✅ Simple | +| Worker Mode | ✅ Yes | ❌ No | ❌ No | +| Automatic HTTPS | ✅ Yes | ❌ No | ❌ No | +| HTTP/3 Support | ✅ Yes | ❌ No | ❌ No | +| Laravel Octane | ✅ Native support | ⚠️ Use Swoole | ⚠️ Use Swoole | +| .htaccess Support | ❌ No | ❌ No | ✅ Yes | +| Maturity | ⚠️ New | ✅ Mature | ✅ Mature | + +::tip +FrankenPHP is the newest variation and represents the future of PHP application servers. If you're starting a new project and can commit to modern practices, this is the variation to choose. +:: + + +#### Known Issues +::warning{to="https://frankenphp.dev/docs/known-issues/#standalone-binary-and-alpine-based-docker-images" target="_blank"} +Some people are reporting performance issues on the `alpine` version of FrankenPHP. If you're experiencing this, consider using the `debian` version. +:: + +FrankenPHP is cutting edge and is a very active project. Be sure to understand FrankenPHP's known issues before using it in production. If you're looking for better compatibility, consider using the [FPM-NGINX](/docs/image-variations/fpm-nginx) image. + +:u-button{to="https://frankenphp.dev/docs/known-issues/" target="_blank" label="View FrankenPHP's known issues" aria-label="FrankenPHP known issues" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +#### What's Inside + +| Item | Status | +|------|--------| +| FrankenPHP application server | ✅ | +| Caddy web server | ✅ (built-in) | +| PHP CLI binary | ✅ | +| Common PHP extensions | ✅ | +| `composer` executable | ✅ | +| `install-php-extensions` script | ✅ | +| Essential system utilities | ✅ | +| Worker mode support | ✅ | +| Automatic HTTPS | ✅ | +| HTTP/2 support | ✅ | +| HTTP/3 support | ✅ | +| Mercure (real-time) | ✅ | +| Native health checks | ✅ (via HTTP endpoint) | +| SSL/TLS support | ✅ (automatic + self-signed) | +| Process management | Single process (no supervisor needed) | +| Exposed Ports | `8080` (HTTP), `8443` (HTTPS + HTTP/3), `2019` (Caddy admin) | +| Stop Signal | `SIGTERM` | + +## Classic Mode vs Worker Mode +Unlike traditional setups that require a separate web server and PHP-FPM, FrankenPHP runs everything in a single process. It also operates in two modes: + +#### Classic Mode (Default) +- FrankenPHP functions like a traditional PHP server (similar to PHP-FPM) +- Each request bootstraps your application fresh +- No additional configuration needed +- Safe for any existing PHP applications + +#### Worker Mode (Advanced) +Worker mode is FrankenPHP's killer feature. Instead of bootstrapping your application for every request, it stays loaded in memory: + +- **Traditional**: Bootstrap app → Handle request → Teardown → Repeat +- **Worker Mode**: Bootstrap app once → Handle requests indefinitely + +This can result in dramatic performance improvements for Laravel applications. + +::tip +Worker mode is perfect for Laravel Octane. Your application boots once and handles thousands of requests without reloading, dramatically improving response times. +:: + +## How FrankenPHP Works + +::steps{level="4"} + +#### Client sends request +The client sends an HTTP request to port 8080 (or 8443 for HTTPS). + +#### FrankenPHP receives and processes the request +FrankenPHP receives and processes the request directly in a single process. This includes: + +1. Static files +2. PHP requests + +#### Send response back to client +The response is sent back to the client. + +:: + +## Quick Start +Here are a few examples to help you get started with the FrankenPHP variation. + +### Docker CLI + +```bash [Terminal] +docker run -p 80:8080 -v $(pwd):/var/www/html/public serversideup/php:8.4-frankenphp +``` + +Your application will be available at `http://localhost`. The default webroot is `/var/www/html/public`. + +### Docker Compose +Here's a basic example getting FrankenPHP up and running with Docker Compose. + +::warning +Don't forget to create a `public` directory and put your PHP code in there. +:: + +::code-tree{defaultValue="compose.yml"} + +```yml [compose.yml] +services: + php: + # Choose our PHP version and variation + image: serversideup/php:8.4-frankenphp + # Expose and map HTTP and HTTPS ports + ports: + - 80:8080 + - 443:8443 + # Mount current directory to /var/www/html + volumes: + - ./:/var/www/html + # Support both HTTP and HTTPS + environment: + SSL_MODE: mixed +``` + +```php [public/index.php] + +``` +:: + +::tip +The FrankenPHP variation uses ports 8080 and 8443 (instead of 80 and 443) to allow the container to run as a non-root user for better security. +:: + +### Laravel Octane +Laravel Octane natively supports FrankenPHP. Use our guide below to learn more. + +:u-button{to="/docs/framework-guides/laravel/octane" label="Learn more about Laravel Octane" aria-label="Learn more about Laravel Octane" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +### Health Check +The FrankenPHP variation includes a built-in health check that verifies the server is responding: + +::note +The health check endpoint is configurable via the `HEALTHCHECK_PATH` environment variable, which defaults to `/healthcheck`. +:: + +If you are using Laravel, you can use the `/up` route to validate that Laravel is running and healthy. + +:u-button{to="/docs/guide/using-healthchecks-with-laravel" label="Learn more about using healthchecks with Laravel" aria-label="Learn more about using healthchecks with Laravel" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Automatic HTTPS +One of FrankenPHP's standout features is automatic HTTPS powered by Caddy. It can automatically obtain and renew SSL certificates from Let's Encrypt. + +::tip{to="/docs/deployment-and-production/configuring-ssl"} +See our [Configuring SSL](/docs/deployment-and-production/configuring-ssl) guide for more information on the best strategies for running SSL in production. +:: + +### Enabling Automatic HTTPS +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-frankenphp + ports: + - "80:8080" + - "443:8443" + volumes: + - ./:/var/www/html + environment: + CADDY_AUTO_HTTPS: "on" + # Your domain for automatic certificate + SERVER_NAME: "example.com" + SSL_MODE: "full" +``` + +::warning +Automatic HTTPS requires a public domain name and ports 80/443 accessible from the internet for Let's Encrypt validation. For local development, use self-signed certificates with `SSL_MODE`. +:: + +### SSL Modes for Development +For local development, use the `SSL_MODE` environment variable: + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-frankenphp + ports: + - "80:8080" + - "443:8443" + volumes: + - ./:/var/www/html + environment: + SSL_MODE: "full" +``` + +Available SSL modes: +- `off` - SSL disabled (default) +- `mixed` - Both HTTP (8080) and HTTPS (8443) enabled +- `full` - HTTPS only on port 8443 + +Learn more about SSL modes in the [Configuring SSL](/docs/deployment-and-production/configuring-ssl) guide. + +:u-button{to="/docs/deployment-and-production/configuring-ssl" label="Learn more about SSL modes" aria-label="Learn more about SSL modes" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Environment Variables +The FrankenPHP variation supports extensive customization through environment variables. + +### FrankenPHP/Caddy Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `FRANKENPHP_CONFIG` | `""` | FrankenPHP-specific configuration (e.g., worker mode) | +| `CADDY_SERVER_ROOT` | `/var/www/html/public` | Document root for the application | +| `CADDY_AUTO_HTTPS` | `off` | Enable automatic HTTPS (`on`/`off`) | +| `CADDY_HTTP_PORT` | `8080` | HTTP port | +| `CADDY_HTTPS_PORT` | `8443` | HTTPS port | +| `CADDY_ADMIN` | `off` | Caddy admin API endpoint | +| `CADDY_LOG_FORMAT` | `console` | Log format (`console`/`json`) | +| `CADDY_LOG_OUTPUT` | `stdout` | Log output destination | +| `CADDY_GLOBAL_OPTIONS` | `""` | Additional Caddy global options | +| `CADDY_SERVER_EXTRA_DIRECTIVES` | `""` | Additional Caddy server directives | +| `SSL_MODE` | `off` | SSL mode: `off`, `mixed`, or `full` | +| `SSL_CERTIFICATE_FILE` | `/etc/ssl/private/self-signed-web.crt` | Path to SSL certificate | +| `SSL_PRIVATE_KEY_FILE` | `/etc/ssl/private/self-signed-web.key` | Path to SSL private key | +| `HEALTHCHECK_PATH` | `/healthcheck` | Path for health check endpoint | +| `SERVER_NAME` | `""` | Domain name for automatic HTTPS | + +::tip{to="/docs/reference/environment-variable-specification"} +For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). +:: + +### PHP Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | +| `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | +| `PHP_UPLOAD_MAX_FILE_SIZE` | `100M` | Maximum upload file size | +| `PHP_POST_MAX_SIZE` | `100M` | Maximum POST request size | +| `PHP_OPCACHE_ENABLE` | `0` | Enable OPcache (`0`/`1`) | +| `PHP_OPCACHE_REVALIDATE_FREQ` | `2` | How often to check for file changes (seconds) | +| `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `1` | Whether to validate timestamps (`0`/`1`) | + +## Caddy Configuration +FrankenPHP uses Caddy's configuration format (Caddyfile) instead of NGINX configuration. + +### Adding Custom Options +There are a few areas where you can use environment variables to customize your Caddy configuration: + +| Variable | Description | Official Documentation | +|----------|-------------|-------------| +| `CADDY_GLOBAL_OPTIONS` | Global Caddy options | [Caddy Global Options](https://caddyserver.com/docs/caddyfile/options){target="_blank"} | +| `CADDY_SERVER_EXTRA_DIRECTIVES` | Server-specific Caddy directives | [Caddy Server Directives](https://caddyserver.com/docs/caddyfile/directives){target="_blank"} | +| `CADDY_PHP_SERVER_OPTIONS` | PHP-specific Caddy directives (site-specific) | [FrankenPHP PHP Server Options](https://frankenphp.dev/docs/config/#caddyfile-config){target="_blank"} | +| `FRANKENPHP_CONFIG` | FrankenPHP-specific configuration (global) | [FrankenPHP Configuration](https://frankenphp.dev/docs/config/#caddyfile-config){target="_blank"} | + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-frankenphp + environment: + CADDY_SERVER_EXTRA_DIRECTIVES: | + # Add custom headers + header { + X-Custom-Header "My Value" + -Server + } +``` + +## Further Customization +If you need to customize the container further, reference the docs below: + +- [Environment Variable Specification](/docs/reference/environment-variable-specification) - See which environment variables are available to customize PHP and Caddy settings. +- [Command Reference](/docs/reference/command-reference) - See which commands are available to run inside the container. +- [FrankenPHP Documentation](https://frankenphp.dev/){target="_blank"} - Official FrankenPHP documentation for advanced features. +- [Caddy Documentation](https://caddyserver.com/docs/){target="_blank"} - Official Caddy documentation for web server configuration. \ No newline at end of file From 833a8671bbc86f73c7e645f5cb55552213aee8b0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 11:55:31 -0600 Subject: [PATCH 297/304] Add documentation for configuring Laravel Octane with Docker, including setup instructions, health check implementation, and performance considerations. Introduce a health check script for Octane to ensure proper application status monitoring. --- .../3.framework-guides/1.laravel/octane.md | 101 ++++++++++++++++++ src/common/usr/local/bin/healthcheck-octane | 2 + 2 files changed, 103 insertions(+) create mode 100644 docs/content/docs/3.framework-guides/1.laravel/octane.md create mode 100644 src/common/usr/local/bin/healthcheck-octane diff --git a/docs/content/docs/3.framework-guides/1.laravel/octane.md b/docs/content/docs/3.framework-guides/1.laravel/octane.md new file mode 100644 index 000000000..41e83f063 --- /dev/null +++ b/docs/content/docs/3.framework-guides/1.laravel/octane.md @@ -0,0 +1,101 @@ +--- +head.title: 'Laravel Octane with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure Laravel Octane with Docker.' +layout: docs +title: Octane +--- + +::lead-p +Laravel Octane supercharges your application's performance by keeping it loaded in memory and serving requests at incredible speeds. The FrankenPHP variation of our images provides native Octane support with worker mode built-in. +:: + +## What is Laravel Octane? + +Laravel Octane boots your Laravel application once and keeps it in memory, then processes thousands of requests without reloading. This dramatically improves performance compared to traditional PHP execution. + +**Traditional PHP:** +Bootstrap → Handle Request → Teardown → Repeat for every request + +**With Octane:** +Bootstrap once → Handle unlimited requests + +::tip +FrankenPHP is Laravel's recommended application server for Octane and is included natively in our images. No additional installation required. +:: + +## Quick Start +Let's use this example project to get started. + +::warning{to="https://serversideup.net/open-source/spin/docs" target="_blank"} +This example assumes you already have a Laravel application installed. If you need help installing a new Laravel project with Docker, check out [Spin](https://serversideup.net/open-source/spin/docs){target="_blank"} for a simple way to get started. +:: + +### Classic Mode +By default, FrankenPHP runs in classic mode. Your compose file might look something like this: + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-frankenphp + ports: + - "80:8080" + volumes: + - .:/var/www/html/ +``` + +We'll expand upon this classic mode file and modify it to run Laravel Octane (which uses FrankenPHP's worker mode). + +### Install Laravel Octane + +First, install Octane in your Laravel application: + +```bash [Terminal] +docker compose run php composer require laravel/octane +``` + +### Configure Worker Mode +We now want to update the compose file to run Laravel Octane and use proper health checks. + + +```yml [compose.yml]{8-13} +services: + php: + image: serversideup/php:8.4-frankenphp + ports: + - "80:8080" + volumes: + - .:/var/www/html/ + # Start Octane in worker mode + command: ["php", "artisan", "octane:start", "--server=frankenphp", "--port=8080"] + # Set healthcheck to use our native healthcheck script for Octane + healthcheck: + test: ["CMD", "healthcheck-octane"] + start_period: 10s +``` +We make two major changes here: +1. **Set the command to start Octane in worker mode** - Instead of starting FrankenPHP in classic mode, we start Octane in worker mode. +2. **Set the healthcheck to use our native healthcheck script for Octane** - This is important to ensure that Octane is running and healthy. + +### Testing Locally + +Run your application locally to test Octane: + +```bash [Terminal] +docker compose up +``` + +Your Laravel application will be available at `http://localhost` with Octane running in worker mode. + +## Things to Watch Out For +Since Octane is a whole different way of running Laravel compared to traditional PHP-FPM, there are a few things to watch out for. + +### Dependency Injection +Be careful with how you inject dependencies into long-lived objects. Injecting the wrong things into constructors can cause requests to "leak" between users. Review Laravel's [Dependency Injection and Octane](https://laravel.com/docs/12.x/octane#dependency-injection-and-octane) documentation for details. + +### Memory Leaks +Review Laravel's [Octane documentation on memory leaks](https://laravel.com/docs/12.x/octane#managing-memory-leaks) to understand what to avoid. + +## Learn More +- [FrankenPHP Variation Documentation](/docs/image-variations/frankenphp) +- [Laravel Octane Documentation](https://laravel.com/docs/12.x/octane) +- [FrankenPHP Documentation](https://frankenphp.dev/docs/) \ No newline at end of file diff --git a/src/common/usr/local/bin/healthcheck-octane b/src/common/usr/local/bin/healthcheck-octane new file mode 100644 index 000000000..07130fc50 --- /dev/null +++ b/src/common/usr/local/bin/healthcheck-octane @@ -0,0 +1,2 @@ +#!/bin/sh +php "${APP_BASE_DIR}/artisan" octane:status \ No newline at end of file From 3b6733155333962f1feb0a6ada421b515f865e42 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 12:06:33 -0600 Subject: [PATCH 298/304] Update links in image variations documentation for consistency and improved navigation. Adjusted paths to ensure they are correctly formatted for internal referencing. --- docs/content/docs/2.image-variations/cli.md | 4 ++-- docs/content/docs/2.image-variations/fpm-nginx.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/2.image-variations/cli.md b/docs/content/docs/2.image-variations/cli.md index ce863bfe3..85d525189 100644 --- a/docs/content/docs/2.image-variations/cli.md +++ b/docs/content/docs/2.image-variations/cli.md @@ -74,5 +74,5 @@ docker compose run php php my-script.php ### Further Customization If you need to customize the container further, reference the docs below: -- [Environment Variable Specifications](docs/reference/environment-variable-specification) - See which environment variables are available to customize common PHP settings. -- [Command Reference](docs/reference/command-reference) - See which commands are available to run inside the container. +- [Environment Variable Specifications](/docs/reference/environment-variable-specification) - See which environment variables are available to customize common PHP settings. +- [Command Reference](/docs/reference/command-reference) - See which commands are available to run inside the container. diff --git a/docs/content/docs/2.image-variations/fpm-nginx.md b/docs/content/docs/2.image-variations/fpm-nginx.md index f4ace3da7..9b6e72e1f 100644 --- a/docs/content/docs/2.image-variations/fpm-nginx.md +++ b/docs/content/docs/2.image-variations/fpm-nginx.md @@ -242,7 +242,7 @@ For a complete list of available environment variables, see the [Environment Var Here are some tuning recommendations for different scenarios: ### For Production (low memory environments) -::note{to="docs/deployment-and-production/packaging-your-app-for-deployment"} +::note{to="/docs/deployment-and-production/packaging-your-app-for-deployment"} If you're running an application in production, you'll likely want to package your application inside an image for deployment. Click here to learn more. :: ```yml [compose.yml] From bc4211dc8f882d29189d7028fcedf7dec78b5e67 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 12:37:49 -0600 Subject: [PATCH 299/304] Reintroduce redirect logic for page navigation in [...slug].vue to ensure users are properly redirected when a page has a redirect property. This change enhances user experience by maintaining expected navigation behavior. --- docs/app/pages/[...slug].vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/app/pages/[...slug].vue b/docs/app/pages/[...slug].vue index d4937684f..bbb43b41c 100644 --- a/docs/app/pages/[...slug].vue +++ b/docs/app/pages/[...slug].vue @@ -77,10 +77,6 @@ if (!page.value) { throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true }) } -if (page.value?.redirect) { - navigateTo(page.value?.redirect) -} - const { data: surround } = await useAsyncData(`${route.path}-surround`, () => { return queryCollectionItemSurroundings('docs', route.path, { fields: ['description'] @@ -103,6 +99,10 @@ defineOgImageComponent('Docs', { headline: headline.value }) +if (page.value?.redirect) { + navigateTo(page.value?.redirect) +} + const links = computed(() => { const links = [] if (toc?.bottom?.edit) { From 01c54668187aa37a84eda24064fad5d4f4721fc2 Mon Sep 17 00:00:00 2001 From: Dan Pastori Date: Tue, 4 Nov 2025 13:48:33 -0600 Subject: [PATCH 300/304] Only navigate after mount. This allows us to build the site --- docs/app/pages/[...slug].vue | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/app/pages/[...slug].vue b/docs/app/pages/[...slug].vue index bbb43b41c..97f5aa86a 100644 --- a/docs/app/pages/[...slug].vue +++ b/docs/app/pages/[...slug].vue @@ -99,10 +99,6 @@ defineOgImageComponent('Docs', { headline: headline.value }) -if (page.value?.redirect) { - navigateTo(page.value?.redirect) -} - const links = computed(() => { const links = [] if (toc?.bottom?.edit) { @@ -116,4 +112,10 @@ const links = computed(() => { return [...links, ...(toc?.bottom?.links || [])].filter(Boolean) }) + +onMounted(() => { + if (page.value?.redirect) { + navigateTo(page.value?.redirect) + } +}) \ No newline at end of file From bfd396bf8e7940a6423c644d07ce266fcd6705fa Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 14:42:13 -0600 Subject: [PATCH 301/304] Update environment variable documentation to include `frankenphp` in the `SSL_PRIVATE_KEY_FILE` entry, enhancing clarity on supported configurations for HTTPS. --- .../docs/8.reference/1.environment-variable-specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index 9587b4675..90ac8db3b 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -116,7 +116,7 @@ Setting environment variables all depends on what method you're using to run you `SHOW_WELCOME_MESSAGE`
*Default: "true"*|Show a helpful welcome message showing container information when the container starts.|all `SSL_CERTIFICATE_FILE`
*Default: "/etc/ssl/private/self-signed-web.crt"*|Path to public certificate file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you.|fpm-nginx,
fpm-apache `SSL_MODE`
*Default: "off"*|Configure how you would like to handle SSL. This can be "off" (HTTP only), "mixed" (HTTP + HTTPS), or "full" (HTTPS only). If you use HTTP, you may need to also change `PHP_SESSION_COOKIE_SECURE`.|fpm-nginx,
fpm-apache,
unit -`SSL_PRIVATE_KEY_FILE`
*Default: "/etc/ssl/private/self-signed-web.key"*|Path to private key file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you.|fpm-nginx,
fpm-apache +`SSL_PRIVATE_KEY_FILE`
*Default: "/etc/ssl/private/self-signed-web.key"*|Path to private key file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you.|fpm-nginx,
fpm-apache,
frankenphp `UNIT_CERTIFICATE_NAME`
*Default: "self-signed-web-bundle"*| Name of your certificate bundle. This is used to configure HTTPS. (Official Docs)| unit `UNIT_CONFIG_DIRECTORY`
*Default: "/etc/unit/config.d"*|Path to the Unit configuration directory. Any *.json, *.js, and *.pem files will be loaded into Unit on initialization.| unit `UNIT_CONFIG_FILE`
*Default: "/etc/unit/config.d/config.json"*|Path to the Unit configuration file. One will be generated automatically by default. (Official Docs)| unit From eae3adde0f66cb0cc9c8640861316d0a9297da67 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 15:19:30 -0600 Subject: [PATCH 302/304] Set default log level to info for FrankenPHP (ref #573) --- .../docs/8.reference/1.environment-variable-specification.md | 2 +- src/variations/frankenphp/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index 90ac8db3b..5f1fc9e18 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -65,7 +65,7 @@ Setting environment variables all depends on what method you're using to run you `HEALTHCHECK_PATH`
*Default: "/healthcheck"*|Set the path for the health check endpoint. (Official docs)|all (except `cli` and `frankenphp`) `HEALTHCHECK_SSL_CERTIFICATE_FILE`
*Default: "/etc/ssl/healthcheck/localhost.crt"*|Set the path to the SSL certificate for the health check endpoint.| fpm-apache, fpm-nginx, frankenphp `HEALTHCHECK_SSL_PRIVATE_KEY_FILE`
*Default: "/etc/ssl/healthcheck/localhost.key"*|Set the path to the SSL private key for the health check endpoint.| fpm-apache, fpm-nginx, frankenphp -`LOG_OUTPUT_LEVEL`
*Default: "warn"*|Set your container output different verbosity levels: debug, info, off |all +`LOG_OUTPUT_LEVEL`
*Default:*
*"warn" (for all)*
*"info" (for frankenphp)*|Set your container output different verbosity levels: debug, warn, info, off |all `NGINX_ACCESS_LOG`
*Default: "/dev/stdout"*|Set the default output stream for access log.|fpm-nginx `NGINX_ERROR_LOG`
*Default: "/dev/stderr"*|Set the default output stream for error log.|fpm-nginx `NGINX_FASTCGI_BUFFERS`
*Default: "8 8k"*|Sets the number and size of the buffers used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 05f6cb3e3..62839849a 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -143,7 +143,7 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ COMPOSER_MAX_PARALLEL_HTTP=24 \ DISABLE_DEFAULT_CONFIG=false \ FRANKEN_PHP_CONFIG="" \ - LOG_OUTPUT_LEVEL=warn \ + LOG_OUTPUT_LEVEL=info \ HEALTHCHECK_PATH="/healthcheck" \ PHP_DATE_TIMEZONE="UTC" \ PHP_DISPLAY_ERRORS=Off \ From 1873a1ea142a7b03b5e47d4ca0214d58fda89119 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 15:31:12 -0600 Subject: [PATCH 303/304] Clarified port message --- docs/content/docs/7.troubleshooting/1.common-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/7.troubleshooting/1.common-issues.md b/docs/content/docs/7.troubleshooting/1.common-issues.md index b6e9002c3..15b2107e9 100644 --- a/docs/content/docs/7.troubleshooting/1.common-issues.md +++ b/docs/content/docs/7.troubleshooting/1.common-issues.md @@ -42,7 +42,7 @@ If your container is failing to run during runtime, then you may have a more adv ### Error Message ``` -Error starting userland proxy: listen tcp 0.0.0.0:80: bind: address already in use +Error starting xyz service: listen tcp 0.0.0.0:80: bind: address already in use ``` ### Solution From ebb82bdb4a2987837a8fa58ba8ca04db4f066aa4 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 4 Nov 2025 16:06:32 -0600 Subject: [PATCH 304/304] Remove --show-error to clean up start up output --- .../fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check | 2 +- .../fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check index f2c6b6fc4..58e6090a0 100644 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check +++ b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check @@ -1,5 +1,5 @@ #!/command/with-contenv sh -curl_options="--fail --location --insecure --silent --show-error --output /dev/null" +curl_options="--fail --location --insecure --silent --output /dev/null" healthcheck_url="http://localhost:${APACHE_HTTP_PORT}${HEALTHCHECK_PATH}" is_online() { diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index 28d87220e..bce9d0ced 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -1,5 +1,5 @@ #!/command/with-contenv sh -curl_options="--fail --location --insecure --silent --show-error --output /dev/null" +curl_options="--fail --location --insecure --silent --output /dev/null" healthcheck_url="http://localhost:${NGINX_HTTP_PORT}${HEALTHCHECK_PATH}" is_online() {

9JlI=s?lQTc~6*0#kt|i$~ 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 U0f$p%|mAmH+?% literal 0 HcmV?d00001 diff --git a/docs/public/images/docs/reverse-proxy.svg b/docs/public/images/docs/reverse-proxy.svg new file mode 100644 index 000000000..0a20f0489 --- /dev/null +++ b/docs/public/images/docs/reverse-proxy.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/docs/s6-overlay-container.svg b/docs/public/images/docs/s6-overlay-container.svg new file mode 100644 index 000000000..2db7ca086 --- /dev/null +++ b/docs/public/images/docs/s6-overlay-container.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/docs/supervisor-container.svg b/docs/public/images/docs/supervisor-container.svg new file mode 100644 index 000000000..f23ae36ff --- /dev/null +++ b/docs/public/images/docs/supervisor-container.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 06154ee3d576f0200884f20282ab5802f562e385 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:40:10 -0500 Subject: [PATCH 208/304] Fix documentation links for S6 Overlay and health checks, ensuring accurate navigation for users seeking guidance on process supervision and health check implementation in PHP Docker environments. --- .../docs/1.getting-started/2.these-images-vs-others.md | 4 ++-- .../docs/1.getting-started/5.default-configurations.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/1.getting-started/2.these-images-vs-others.md b/docs/content/docs/1.getting-started/2.these-images-vs-others.md index 0fd424597..350476607 100644 --- a/docs/content/docs/1.getting-started/2.these-images-vs-others.md +++ b/docs/content/docs/1.getting-started/2.these-images-vs-others.md @@ -48,9 +48,9 @@ We believe images should be ready for production and able to live in the open an ## What is "S6 Overlay?" ::warning -Using **supervisor** to monitor multiple processes in a single container may lead to unintended consequences. This is why we use [S6 Overlay](/docs/guides/using-s6-overlay) to monitor multiple processes in a single container. +Using **supervisor** to monitor multiple processes in a single container may lead to unintended consequences. This is why we use [S6 Overlay](/docs/guide/using-s6-overlay) to monitor multiple processes in a single container. :: [S6 Overlay](https://github.com/just-containers/s6-overlay){target="_blank"} is a process supervisor designed for containerization from the ground up. It's a modern alternative to [Supervisor (aka Supervisord)](https://supervisord.org/){target="_blank"}. We only use this in our `serversideup/php:*-fpm-apache` and `serversideup/php:*-fpm-nginx` images, as they require two processes to run a service. -:u-button{to="/docs/guides/using-s6-overlay" label="Learn more about S6 Overlay" aria-label="Learn more about S6 Overlay" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file +:u-button{to="/docs/guide/using-s6-overlay" label="Learn more about S6 Overlay" aria-label="Learn more about S6 Overlay" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file diff --git a/docs/content/docs/1.getting-started/5.default-configurations.md b/docs/content/docs/1.getting-started/5.default-configurations.md index f75466930..2ff8a0953 100644 --- a/docs/content/docs/1.getting-started/5.default-configurations.md +++ b/docs/content/docs/1.getting-started/5.default-configurations.md @@ -104,7 +104,7 @@ For our `fpm` variation, we use the [`php-fpm-healthcheck`](https://github.com/r The `cli` variation does not have a health check because it doesn't really make sense to have one. Would love to discuss more if you feel different. -:u-button{to="/docs/guides/using-healthchecks-with-laravel" label="Learn more about health checks" aria-label="Learn more about health checks" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} +:u-button{to="/docs/guide/using-healthchecks-with-laravel" label="Learn more about health checks" aria-label="Learn more about health checks" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} ## Default Entrypoint Scripts From fe6f346ec82a338e8f05b7d2f5db7767b6c97b78 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:44:25 -0500 Subject: [PATCH 209/304] Add command reference documentation for Docker PHP image, detailing available commands to enhance developer experience, including installation scripts for Alpine and Debian, entrypoint usage, file permission management, and S6 Overlay integration. --- .../docs/8.reference/2.command-reference.md | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 docs/content/docs/8.reference/2.command-reference.md diff --git a/docs/content/docs/8.reference/2.command-reference.md b/docs/content/docs/8.reference/2.command-reference.md new file mode 100644 index 000000000..71a0cbed0 --- /dev/null +++ b/docs/content/docs/8.reference/2.command-reference.md @@ -0,0 +1,85 @@ +--- +head.title: 'Command reference - Docker PHP - Server Side Up' +description: 'Learn about all the commands available within the serversideup/php image to improve your developer experience.' +layout: docs +--- + +::lead-p +We included a few commands in the `/usr/local/bin` directory to help you with your development experience. These commands are available in the `serversideup/php` image. +:: + +::note +Since our images are unprivileged by default, you'll need to switch to the `root` user to run these commands. This is a security feature that ensures that your container is as secure as possible. +:: + +## Official PHP Docker Commands +Since our images are based off the official PHP Docker images, any commands from the Official PHP Docker images are available in our images. You can find the full list of commands on the [official PHP Docker images documentation](https://hub.docker.com/_/php). + +## docker-php-serversideup-dep-install-alpine +This command will detect the operating system and install packages if it is an Alpine based system. This is helpful if you're building multiple operating systems from the same Dockerfile. + + +```bash [Example - Installing the git package on Alpine] +# Usage: docker-php-serversideup-dep-install-alpine [alpine-packages] +docker-php-serversideup-dep-install-alpine git +``` + +## docker-php-serversideup-dep-install-debian +This command will detect the operating system and install packages if it is a Debian based system. This is helpful if you're building multiple operating systems from the same Dockerfile. + +```bash [Example - Installing the git package on Debian] +# Usage: docker-php-serversideup-dep-install-debian [debian-packages] +docker-php-serversideup-dep-install-debian git +``` + +## docker-php-serversideup-entrypoint +For our images that **DO NOT use S6 Overlay**, this is our default entrypoint script. + +## docker-php-serversideup-install-php-ext-installer +This is an internal helper script to shorten up the syntax for the installation of the PHP extension installer. + +```bash [Example - Installing the PHP extension installer] +# Usage: docker-php-serversideup-install-php-ext-installer [version] +docker-php-serversideup-install-php-ext-installer 2.2.0 +``` + +## docker-php-serversideup-set-file-permissions +This command is used to set the file permissions of a service in the container. This is useful for development environments where you want to match the user and group ID of the host machine. + +[Learn more about working with file permissions →](/docs/guide/understanding-file-permissions) + +```bash [Example - Setting the file permissions of the NGINX service] +# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP +docker-php-serversideup-set-file-permissions --owner 1000:1000 +``` + +The above command will automatically detect the service and update the file permissions accordingly. + +## docker-php-serversideup-set-id +This command is used to set the user and group ID of the `www-data` user in the container. This is useful for development environments where you want to match the user and group ID of the host machine. + +[Learn more about working with file permissions →](/docs/guide/understanding-file-permissions) + +```bash [Example - Setting the user and group ID of the www-data user] +# docker-php-serversideup-set-id [username] [uid]:[gid] +docker-php-serversideup-set-id www-data 1000:1000 +``` + +## docker-php-serversideup-s6-init +This command is used to copy our entrypoint scripts into the S6 Overlay scripts directory. This is useful if you're using S6 Overlay and want to ensure your scripts are executed in the correct order. + +[Learn more about using S6 Overlay dependencies →](/docs/customizing-the-image/adding-your-own-start-up-scripts#advanced-scenarios-s6-overlay-dependencies) + +## docker-php-serversideup-s6-install +This is a command used at build time to install a specific version of S6 Overlay. + +## install-php-extensions +This command is a wrapper around the `docker-php-ext-install` command that comes with the official PHP Docker images. This command allows you to install PHP extensions with a single command. + +[Check out the docker-php-extension-installer project on GitHub →](https://github.com/mlocati/docker-php-extension-installer){target="_blank"} + +Here's an example of how you can use this command: + +```bash [Example - Installing the "intl" extension] +install-php-extensions intl +``` \ No newline at end of file From fe4f6b3436c9865e21bea0440005dc1407df59d5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:44:30 -0500 Subject: [PATCH 210/304] Add environment variable specification documentation for Docker PHP images, detailing available variables and their usage across different configurations, enhancing customization options for users. --- .../1.environment-variable-specification.md | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 docs/content/docs/8.reference/1.environment-variable-specification.md diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md new file mode 100644 index 000000000..352fca842 --- /dev/null +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -0,0 +1,124 @@ +--- +head.title: 'Environment Variable Specifications - Docker PHP - Server Side Up' +description: 'View the complete reference for environment variables available in the Docker PHP images.' +layout: docs +--- + +::lead-p +Environment variables are a very powerful way to customize your container. Use the document below to see what options are available to customize without requiring you to mount any custom configuration files. +:: + +Setting environment variables all depends on what method you're using to run your container, but for most cases you might be using Docker Compose. + +:u-button{to="https://docs.docker.com/compose/environment-variables/#set-environment-variables-in-containers" target="_blank" label="Learn environment variables work with Docker Compose" aria-label="Learn how environment variables work with Docker Compose" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Available Environment Variables +**Variable Name**|**Description**|**Used in variation** +:-----:|:-----:|:-----: +`APACHE_DOCUMENT_ROOT`
*Default: "/var/www/html/public"*|Sets the directory from which Apache will serve files. (
Official docs)|fpm-apache +`APACHE_MAX_CONNECTIONS_PER_CHILD`
*Default: "0"*|Sets the limit on the number of connections that an individual child server process will handle.(Official docs)|fpm-apache +`APACHE_MAX_REQUEST_WORKERS`
*Default: "150"*|Sets the limit on the number of simultaneous requests that will be served. (Official docs)|fpm-apache +`APACHE_MAX_SPARE_THREADS`
*Default: "75"*|Maximum number of idle threads. (Official docs)|fpm-apache +`APACHE_MIN_SPARE_THREADS`
*Default: "10"*|Minimum number of idle threads to handle request spikes. (Official docs)|fpm-apache +`APACHE_RUN_GROUP`
*Default: "www-data"*|Set the username of what Apache should run as.|fpm-apache +`APACHE_RUN_USER`
*Default: "www-data"*|Set the username of what Apache should run as.|fpm-apache +`APACHE_START_SERVERS`
*Default: "2"*|Sets the number of child server processes created on startup.(Official docs)|fpm-apache +`APACHE_THREAD_LIMIT`
*Default: "64"*|Set the maximum configured value for ThreadsPerChild for the lifetime of the Apache httpd process. (Official docs)|fpm-apache +`APACHE_THREADS_PER_CHILD`
*Default: "25"*|This directive sets the number of threads created by each child process. (Official docs)|fpm-apache +`APP_BASE_DIR`
*Default: "/var/www/html"*|Change this only if you mount your application to a different directory within the container. ℹ️ Be sure to change `NGINX_WEBROOT`, `APACHE_DOCUMENT_ROOT`, `UNIT_WEBROOT`, etc if it applies to your use case as well.|all +`AUTORUN_DEBUG`
*Default: "false"*|Enable debug mode for the Laravel automations. | all +`AUTORUN_ENABLED`
*Default: "false"*|Enable or disable all automations. It's advised to set this to `false` in certain CI environments (especially during a composer install). If this is set to `false`, all `AUTORUN_*` behaviors will also be disabled.| all +`AUTORUN_LARAVEL_OPTIMIZE`
*Default: "true"*|Automatically run "php artisan optimize" on container, attempting to `--except` in Laravel > `v11.38.0` (Official docs)
ℹ️ Requires `AUTORUN_ENABLED = true` to run. | all +`AUTORUN_LARAVEL_CONFIG_CACHE`
*Default: "true"*|Automatically run "php artisan config:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_EVENT_CACHE`
*Default: "true"*|Automatically run "php artisan event:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_MIGRATION`
*Default: "true"*|Automatically run `php artisan migrate --force` on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_MIGRATION_DATABASE`
*Default: null*| Run migrations on a specific database. In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). If `null`, it will use the default database connection.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_MIGRATION_FORCE`
*Default: "true"*|Force migrations to run in production without confirmation. Set to `false` to disable the `--force` flag.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_MIGRATION_ISOLATION`
*Default: "false"*|Requires Laravel v9.38.0 or higher and a database that supports table locks. Automatically run `php artisan migrate --force --isolated` on container start.

ℹ️ Requires `AUTORUN_ENABLED = true` to run.
ℹ️ Does not work with SQLite.
ℹ️ Only works with `AUTORUN_LARAVEL_MIGRATION_MODE = default`.| all +`AUTORUN_LARAVEL_MIGRATION_MODE`
*Default: "default"*|Set the migration mode. Valid options: `default` (runs `php artisan migrate`), `fresh` (runs `php artisan migrate:fresh`), or `refresh` (runs `php artisan migrate:refresh`).
ℹ️ Requires `AUTORUN_ENABLED = true` to run.
⚠️ WARNING:`fresh` and `refresh` are destructive and will drop all tables. Only use these in development or testing environments.| all +`AUTORUN_LARAVEL_MIGRATION_SEED`
*Default: "false"*|Automatically seed the database after migrations by adding the `--seed` flag.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK`
*Default: "false"*|Skip the database connection check before running migrations.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_MIGRATION_TIMEOUT`
*Default: "30"*|The number of seconds to wait for the database to come online before attempting `php artisan migrate`..
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_ROUTE_CACHE`
*Default: "true"*|Automatically run "php artisan route:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_STORAGE_LINK`
*Default: "true"*|Automatically run "php artisan storage:link" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`AUTORUN_LARAVEL_VIEW_CACHE`
*Default: "true"*|Automatically run "php artisan view:cache" on container start.
ℹ️ Requires `AUTORUN_ENABLED = true` to run.| all +`CADDY_ADMIN`
*Default: "off"*|Enable Caddy admin interface. (Official docs)|frankenphp +`CADDY_APP_PUBLIC_PATH`
*Default: "/var/www/html/public"*|The path to your public directory for your app. (Official docs)|frankenphp +`CADDY_AUTO_HTTPS`
*Default: "off"*|Enable automatic HTTPS. (Official docs)|frankenphp +`CADDY_GLOBAL_OPTIONS`
*Default: ""*|Set global options for the Caddy server. (Official docs)|frankenphp +`CADDY_HTTP_PORT`
*Default: "8080"*|Set the port for HTTP. (Official docs)|frankenphp +`CADDY_HTTPS_PORT`
*Default: "8443"*|Set the port for HTTPS. (Official docs)|frankenphp +`CADDY_HTTP_SERVER_ADDRESS`
*Default: "http://"*|Set the server address for HTTP. (Official docs)|frankenphp +`CADDY_HTTPS_SERVER_ADDRESS`
*Default: "https://"*|Set the server address for HTTPS. (Official docs)|frankenphp +`CADDY_LOG_FORMAT`
*Default: "console"*|Set the format for the Caddy log. (Official docs)|frankenphp +`CADDY_GLOBAL_LOG_LEVEL`
*Default: "warn"*|Set the global log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_GLOBAL_LOG_LEVEL` takes precedence. (Official docs)|frankenphp +`CADDY_SERVER_LOG_LEVEL`
*Default: "warn"*|Set the server log level for the Caddy server. This can also be changed with `LOG_OUTPUT_LEVEL`, but `CADDY_SERVER_LOG_LEVEL` takes precedence. (Official docs)|frankenphp +`CADDY_LOG_OUTPUT`
*Default: "stdout"*|Set the output for the Caddy log. (Official docs)|frankenphp +`CADDY_PHP_SERVER_OPTIONS`
*Default: ""*|Set PHP server options for the Caddy server. (Official docs)|frankenphp +`CADDY_SERVER_EXTRA_DIRECTIVES`
*Default: ""*|Set extra directives for the Caddy server. (Official docs)|frankenphp +`COMPOSER_ALLOW_SUPERUSER`
*Default: "1"*|Disable warning about running as super-user|all +`COMPOSER_HOME`
*Default: "/composer"*|The COMPOSER_HOME variable allows you to change the Composer home directory. This is a hidden, global (per-user on the machine) directory that is shared between all projects.|all +`COMPOSER_MAX_PARALLEL_HTTP`
*Default: "24"*|Set to an integer to configure how many files can be downloaded in parallel. Composer ships with 12 by default and must be between 1 and 50. If your proxy has issues with concurrency maybe you want to lower this. Increasing it should generally not result in performance gains.|all +`DISABLE_DEFAULT_CONFIG`
*Default: "false"*|Get full customization of the image and disable all default configurations and automations.| all +`FRANKENPHP_CONFIG`
*Default: ""*|Set the configuration for FrankenPHP. (Official docs)|frankenphp +`HEALTHCHECK_PATH`
*Default: "/healthcheck"*|Set the path for the health check endpoint. (Official docs)|all (except `cli` and `frankenphp`) +`LOG_OUTPUT_LEVEL`
*Default: "warn"*|Set your container output different verbosity levels: debug, info, off |all +`NGINX_ACCESS_LOG`
*Default: "/dev/stdout"*|Set the default output stream for access log.|fpm-nginx +`NGINX_ERROR_LOG`
*Default: "/dev/stderr"*|Set the default output stream for error log.|fpm-nginx +`NGINX_FASTCGI_BUFFERS`
*Default: "8 8k"*|Sets the number and size of the buffers used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx +`NGINX_FASTCGI_BUFFER_SIZE`
*Default: "8k"*|Sets the size of the buffer used for reading a response from a FastCGI server. (Official Docs)|fpm-nginx +`NGINX_LISTEN_IP_PROTOCOL`
*Default: "all"*|Set the IP protocol for NGINX to listen on. Valid values are "all", "ipv4", and "ipv6". (Official Docs)|fpm-nginx +`NGINX_SERVER_TOKENS`
*Default: "off"*|Display NGINX version in responses. (Official Docs)|fpm-nginx +`NGINX_WEBROOT`
*Default: "`/var/www/html/public"*|Sets the root directory for requests. (Official Docs)|fpm-nginx +`NGINX_CLIENT_MAX_BODY_SIZE`
*Default: "100M"*|Sets the max body size for requests. (Official Docs*Default: "UTC"*|Control your timezone. (Official Docs)|all +`PHP_DISPLAY_ERRORS`
*Default: Off*|Show PHP errors on screen. (Official docs)|all +`PHP_DISPLAY_STARTUP_ERRORS`
*Default: Off*|Even when display_errors is on, errors that occur during PHP's startup sequence are not displayed. (Official docs)| all +`PHP_ERROR_LOG`
*Default: "/dev/stderr"*|Name of the file where script errors should be logged. . (Official docs)|all +`PHP_ERROR_REPORTING`
*Default: "22527"*|Set PHP error reporting level. Must be a number. Use this tool for help. (Official docs)|all +`PHP_FPM_PM_CONTROL`
*Defaults:
fpm: dynamic
fpm-apache: ondemand
fpm-nginx: ondemand*|Choose how the process manager will control the number of child processes. (Official docs)|fpm* +`PHP_FPM_PM_MAX_CHILDREN`
*Default: "20"*|The number of child processes to be created when pm is set to static and the maximum number of child processes to be created when pm is set to dynamic. (Official docs)|fpm* +`PHP_FPM_PM_MAX_REQUESTS`
*Default: "0"*|The number of requests each child process should execute before respawning. This can be useful to work around memory leaks in 3rd party libraries. (Official docs)|fpm* +`PHP_FPM_PM_MAX_SPARE_SERVERS`
*Default: "3"*|The desired maximum number of idle server processes. Used only when pm is set to dynamic. (Official docs)|fpm* +`PHP_FPM_PM_MIN_SPARE_SERVERS`
*Default: "1"*|The desired minimum number of idle server processes. Used only when pm is set to dynamic. (Official docs)|fpm* +`PHP_FPM_PM_START_SERVERS`
*Default: "2"*|The number of child processes created on startup. Used only when pm is set to dynamic. (Official docs)|fpm* +`PHP_FPM_PM_STATUS_PATH`
*Default: ""*|The path to the PHP-FPM status page. (Official docs)|fpm* +`PHP_FPM_POOL_NAME`
*Default: "www"*|Set the name of your PHP-FPM pool (helpful when running multiple sites on a single server).|fpm* +`PHP_FPM_PROCESS_CONTROL_TIMEOUT`
*Default: "10s"*|Set the timeout for the process control commands. (Official docs)|fpm* +`PHP_MAX_EXECUTION_TIME`
*Default: "99"*|Set the maximum time in seconds a script is allowed to run before it is terminated by the parser. (Official docs)|all +`PHP_MAX_INPUT_TIME`
*Default: "-1"*|This sets the maximum time in seconds a script is allowed to parse input data, like POST and GET. Timing begins at the moment PHP is invoked at the server and ends when execution begins. The default setting is -1, which means that max_execution_time is used instead. Set to 0 to allow unlimited time. This directive is hardcoded to -1 for the CLI SAPI by PHP. (Official docs)|all +`PHP_MAX_INPUT_VARS`
*Default: "1000"*|Set the limits for number of input variables (e.g., POST, GET, or COOKIE variables) that PHP will process in a single request. (Official docs)|all +`PHP_MEMORY_LIMIT`
*Default: "256M"*|Set the maximum amount of memory in bytes that a script is allowed to allocate. (Official docs)|all +`PHP_OPCACHE_ENABLE`
*Default: "0" (to keep developers sane)*|Enable or disable OPcache. ⚠️ This will set **both values** for `opcache.enable` and `opcache.enable_cli`. (Official docs)|all +`PHP_OPCACHE_ENABLE_FILE_OVERRIDE`
*Default: "0"*|Enable or disable file existence override (file_exists, etc.). (Official docs)|all +`PHP_OPCACHE_FORCE_RESTART_TIMEOUT`
*Default: "180"*|The number of seconds to wait for a scheduled restart to begin if the cache isn't active, in seconds. If the timeout is hit, then OPcache assumes that something is wrong and will kill the processes holding locks on the cache to permit a restart. (Official docs)|all +`PHP_OPCACHE_INTERNED_STRINGS_BUFFER`
*Default: "8"*|The amount of memory used to store interned strings, in megabytes. (Official docs)|all +`PHP_OPCACHE_JIT`
*Default: "off"*|Enable or disable the JIT compiler. (Official docs)|all +`PHP_OPCACHE_JIT_BUFFER_SIZE`
*Default: "0"*|The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. (Official docs)|all +`PHP_OPCACHE_MAX_ACCELERATED_FILES`
*Default: "10000"*|The maximum number of keys (scripts) in the OPcache hash table. (Official docs)|all +`PHP_OPCACHE_MEMORY_CONSUMPTION`
*Default: "128"*|The amount of memory used by the OPcache engine, in megabytes. (Official docs)|all +`PHP_OPCACHE_REVALIDATE_FREQ`
*Default: "2"*|How often the OPcache checks for updates to cached files (in seconds). (Official docs)|all +`PHP_OPCACHE_SAVE_COMMENTS`
*Default: "1"*|Remove comments from OPcache to minify a bit further. Note: any code that depends on PHPDoc annotations can break from this. (Official docs)|all +`PHP_OPCACHE_VALIDATE_TIMESTAMPS`
*Default: "1"*|Whether OPcache checks for changes to files, or requires reload of PHP to revalidate OPcache. (Official docs)|all +`PHP_OPEN_BASEDIR`
*Default: "None"* |Limit the files that can be accessed by PHP to the specified directory-tree, including the file itself. `open_basedir` is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed. (Official docs)| all +`PHP_POST_MAX_SIZE`
*Default: "100M"*|Sets max size of post data allowed. (Official docs)|all +`PHP_REALPATH_CACHE_TTL`
*Default: "120"*|The duration of time, in seconds for which to cache realpath information for a given file or directory. (Official docs)|all +`PHP_SESSION_COOKIE_SECURE`
*Default: 1 (true)*|Specifies whether cookies should only be sent over secure connections. (Official docs)|all +`PHP_UPLOAD_MAX_FILE_SIZE`
*Default: "100M"*|The maximum size of an uploaded file. (Official docs)|all +`PHP_ZEND_DETECT_UNICODE`
*Default: ""*|Check for BOM (Byte Order Mark) and see if the file contains valid multibyte characters. This detection is performed before processing of __halt_compiler(). Available only in Zend Multibyte mode. (Official docs)|all +`PHP_ZEND_MULTIBYTE`
*Default: "Off"*|Enable or disable Zend Multibyte. (Official docs)|all +`S6_BEHAVIOUR_IF_STAGE2_FAILS`
*Default: "2" (stop the container)*|Determines what the container should do if one of the service scripts fails (Official docs)|fpm-nginx,
fpm-apache +`S6_CMD_WAIT_FOR_SERVICES_MAXTIME`
*Default: "0"*|The maximum time (in milliseconds) the services could take to bring up before proceeding to CMD executing (Official docs)|fpm-nginx,
fpm-apache +`S6_VERBOSITY`
*Default: "1"*|Set the verbosity of "S6 Overlay" (the init system these images are based on). The default is "1" (print warnings and errors). The scale goes from 1 to 5, but the output will quickly become very noisy. If you're having issues, start here. You can also customize many other variables. (Official docs)|fpm-nginx,
fpm-apache +`SHOW_WELCOME_MESSAGE`
*Default: "true"*|Show a helpful welcome message showing container information when the container starts.|all +`SSL_CERTIFICATE_FILE`
*Default: "/etc/ssl/private/self-signed-web.crt"*|Path to public certificate file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you.|fpm-nginx,
fpm-apache +`SSL_MODE`
*Default: "off"*|Configure how you would like to handle SSL. This can be "off" (HTTP only), "mixed" (HTTP + HTTPS), or "full" (HTTPS only). If you use HTTP, you may need to also change `PHP_SESSION_COOKIE_SECURE`.|fpm-nginx,
fpm-apache,
unit +`SSL_PRIVATE_KEY_FILE`
*Default: "/etc/ssl/private/self-signed-web.key"*|Path to private key file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you.|fpm-nginx,
fpm-apache +`UNIT_CERTIFICATE_NAME`
*Default: "self-signed-web-bundle"*| Name of your certificate bundle. This is used to configure HTTPS. (Official Docs)| unit +`UNIT_CONFIG_DIRECTORY`
*Default: "/etc/unit/config.d"*|Path to the Unit configuration directory. Any *.json, *.js, and *.pem files will be loaded into Unit on initialization.| unit +`UNIT_CONFIG_FILE`
*Default: "/etc/unit/config.d/config.json"*|Path to the Unit configuration file. One will be generated automatically by default. (Official Docs)| unit +`UNIT_PROCESSES_IDLE_TIMEOUT`
*Default: "30"*|The maximum time in seconds that an idle process will be kept alive. (Official Docs)| unit +`UNIT_PROCESSES_MAX`
*Default: "20"*|The maximum number of application processes that can be started. (Official Docs)| unit +`UNIT_PROCESSES_SPARE`
*Default: "2"*|Minimum number of idle processes that Unit tries to maintain for an app. (Official Docs)| unit +`UNIT_WEBROOT`
*Default: "/var/www/html/public"*|Base directory of the app’s file structure. All URI paths are relative to it. (Official Docs)| unit +`UNIT_MAX_BODY_SIZE`
*Default: "104857600"* (100MB) | Sets maximum number of bytes in the body of a client’s request. (Official docs) | unit From a83a85d9dda4b2687c770d16c8aa094c93277983 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:44:52 -0500 Subject: [PATCH 211/304] Add navigation configuration for Advanced Guides in documentation, enhancing structure and accessibility for users seeking advanced topics in PHP Docker environments. --- docs/content/docs/{5.guides => 5.guide}/.navigation.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/content/docs/{5.guides => 5.guide}/.navigation.yml (100%) diff --git a/docs/content/docs/5.guides/.navigation.yml b/docs/content/docs/5.guide/.navigation.yml similarity index 100% rename from docs/content/docs/5.guides/.navigation.yml rename to docs/content/docs/5.guide/.navigation.yml From a1fa608a77bf94cd6c5398364b903ee165870728 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:47:05 -0500 Subject: [PATCH 212/304] Add Laravel automation, task scheduler, queue, horizon, and reverb documentation for Docker PHP images, providing comprehensive guides on configuring and managing these features, enhancing developer experience and deployment practices. --- .../1.laravel/1.automations.md | 161 ++++++++++++++++++ .../1.laravel/2.task-scheduler.md | 113 ++++++++++++ .../3.framework-guides/1.laravel/3.queue.md | 46 +++++ .../3.framework-guides/1.laravel/4.horizon.md | 51 ++++++ .../3.framework-guides/1.laravel/4.reverb.md | 71 ++++++++ 5 files changed, 442 insertions(+) create mode 100644 docs/content/docs/3.framework-guides/1.laravel/1.automations.md create mode 100644 docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md create mode 100644 docs/content/docs/3.framework-guides/1.laravel/3.queue.md create mode 100644 docs/content/docs/3.framework-guides/1.laravel/4.horizon.md create mode 100644 docs/content/docs/3.framework-guides/1.laravel/4.reverb.md diff --git a/docs/content/docs/3.framework-guides/1.laravel/1.automations.md b/docs/content/docs/3.framework-guides/1.laravel/1.automations.md new file mode 100644 index 000000000..0dc20ed8a --- /dev/null +++ b/docs/content/docs/3.framework-guides/1.laravel/1.automations.md @@ -0,0 +1,161 @@ +--- +head.title: 'Laravel Automations Script - Docker PHP - Server Side Up' +description: 'Automate your deployments and minimize your efforts with Laravel.' +layout: docs +title: Automations +--- + +::lead-p +`serversideup/php` has a "Laravel Automations" script that helps automate common tasks to maintain your Laravel application and improve it's performance. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. +:: + +## What the script does + +::note +In order for this script to run,`AUTORUN_ENABLED` must be set to `true`. Once the main part of the script is enabled, you can control the individual tasks by setting the corresponding environment variables to `true` or `false`. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. +:: + +| Environment Variable | Default | Description | +| -------------------- | -------------- | ----------- | +| `AUTORUN_ENABLED` | `false` | Enables the Laravel Automations script.
**ℹ️ Note:** This must be set to `true` for the script to run. | +| `AUTORUN_DEBUG` | `false` | Enables a special debug mode, specifically for the Laravel Automations script. | +| `AUTORUN_LARAVEL_CONFIG_CACHE` | `true` | `php artisan config:cache`: Caches the configuration files into a single file. | +| `AUTORUN_LARAVEL_EVENT_CACHE` | `true` | `php artisan event:cache`: Creates a manifest of all your application's events and listeners. | +| `AUTORUN_LARAVEL_MIGRATION` | `true` | `php artisan migrate`: Runs migrations. | +| `AUTORUN_LARAVEL_MIGRATION_DATABASE` | `null` | Run migrations on a specific database. In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). If `null`, it will use the default database connection. | +| `AUTORUN_LARAVEL_MIGRATION_FORCE` | `true` | Force migrations to run in production without confirmation. Set to `false` to disable the `--force` flag. | +| `AUTORUN_LARAVEL_MIGRATION_ISOLATION` | `false` | Run your migrations with the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag.
**ℹ️ Note:** Requires Laravel v9.38.0+. Only works with `default` migration mode. | +| `AUTORUN_LARAVEL_MIGRATION_MODE` | `default` | Migration mode: `default`, `fresh`, or `refresh`.
**⚠️ Warning:** `fresh` and `refresh` drop all tables. | +| `AUTORUN_LARAVEL_MIGRATION_SEED` | `false` | Automatically seed the database after migrations using the `--seed` flag. | +| `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK` | `false` | Skip the database connection check before running migrations. | +| `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` | `30` | Number of seconds to wait for database connection before timing out during migrations. | +| `AUTORUN_LARAVEL_OPTIMIZE` | `true` | `php artisan optimize`: Optimizes the application. | +| `AUTORUN_LARAVEL_ROUTE_CACHE` | `true` | `php artisan route:cache`: Caches the routes. | +| `AUTORUN_LARAVEL_STORAGE_LINK` | `true` | `php artisan storage:link`: Creates a symbolic link from `public/storage` to `storage/app/public`. | +| `AUTORUN_LARAVEL_VIEW_CACHE` | `true` | `php artisan view:cache`: Caches the views. | + +## Database Connection Checks +Before running migrations, the automation script performs connection checks to ensure your database is ready. Understanding this process helps you configure timeouts and troubleshoot connection issues. + +::note +You can skip database connection checks entirely by setting `AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK=true`. This is useful when you're certain your database is ready or when using alternative connection verification methods. +:: + +::steps{level="3"} + +### Clear configuration cache +The script runs `php artisan config:clear` to ensure fresh database configuration is loaded before attempting any connections. + +### Attempt database connection +The script tests the database connection using a retry mechanism: +- Attempts to connect every second +- Continues for up to `AUTORUN_LARAVEL_MIGRATION_TIMEOUT` seconds (default: 30) +- Shows connection progress in real-time +- Logs detailed attempts every 5 seconds when `AUTORUN_DEBUG=true` + +### Verify each database connection +If you've specified multiple databases via `AUTORUN_LARAVEL_MIGRATION_DATABASE`, the script waits for each database connection individually before proceeding with migrations for that database. + +### Run migrations +Once the database connection is confirmed, the script executes the appropriate migration command based on your configuration. + +:: + +::tip +Set `AUTORUN_DEBUG=true` to see detailed connection attempt logs, which is helpful for troubleshooting connection issues. +:: + + +## php artisan storage:link +Creates a symbolic link from `public/storage` to `storage/app/public`. + +:u-button{to="https://laravel.com/docs/12.x/filesystem#the-public-disk" target="_blank" label="Read more about storage links" aria-label="Read more about storage links" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## php artisan migrate +Before running migrations, we ensure the database is online and ready to accept connections. By default, we will wait 30 seconds before timing out. + +### Migration Modes +You can control how migrations run using `AUTORUN_LARAVEL_MIGRATION_MODE`: + +| Mode | Description | +|------|-------------| +| `default` | Runs `php artisan migrate` - standard forward migrations | +| `fresh` | Runs `php artisan migrate:fresh` - drops all tables and re-runs migrations | +| `refresh` | Runs `php artisan migrate:refresh` - rolls back and re-runs migrations | + +::caution +Using `fresh` or `refresh` modes will **drop all tables** in your database. Only use these in development or testing environments. +:: + +### Force Flag +By default, migrations run with the `--force` flag to bypass production warnings. You can disable this by setting `AUTORUN_LARAVEL_MIGRATION_FORCE` to `false`. + +### Seeding +You can automatically seed your database after migrations by setting `AUTORUN_LARAVEL_MIGRATION_SEED` to `true`. This adds the `--seed` flag to your migration command. + +### Specific Database Migrations +If you need to specify the exact database connection to use for migrations, you can set `AUTORUN_LARAVEL_MIGRATION_DATABASE` to the name of the database connection you want to use. + +| Use case | Description | Value | +|----------|-------|-------------| +| Single database | Run migrations on the `mysql` database connection. | `AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql` | +| Multiple databases | In the rare case you need to use multiple databases, you can provide a comma-delimited list of connection names (e.g., "mysql,pgsql"). | `AUTORUN_LARAVEL_MIGRATION_DATABASE=mysql,pgsql` | + +### Isolated Migrations +You can enable the [`--isolated`](https://laravel.com/docs/12.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. + +**Special Notes for Isolated Migrations:** +- Requires Laravel v9.38.0+ +- Only works with `default` migration mode (not compatible with `fresh` or `refresh`) +- Your application must be using the memcached, redis, dynamodb, database, file, or array cache driver as your application's default cache driver. In addition, all servers must be communicating with the same central cache server. + +:u-button{to="https://laravel.com/docs/12.x/migrations#running-migrations" target="_blank" label="Read more about migrations" aria-label="Read more about migrations" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## php artisan optimize +Laravel comes with an artisan command called `optimize`, which will optimize the application by caching the configuration, routes, views, and events all in one command. + +You can disable any cache features by setting the corresponding environment variable to `false` (for example, `AUTORUN_LARAVEL_CONFIG_CACHE` would disable configuration caching). + +If your application is running Laravel v11.38.0 or higher, we will utilize the `optimize --except` parameter to exclude any cache features you have disabled. Otherwise, we will run the individual optimizations separately. + +It's possible to disable the `optimize` command by setting `AUTORUN_LARAVEL_OPTIMIZE` to `false`, but the major advantage of using the `optimize` command is other dependencies may hook into this action and run other commands. + +:u-button{to="https://laravel.com/docs/12.x/deployment#optimization" target="_blank" label="Read more about optimizing Laravel" aria-label="Read more about optimizing Laravel" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## php artisan config:cache +This command caches all configuration files into a single file, which can then be quickly loaded by Laravel. Once the configuration is cache, the `.env` file will no longer be loaded. + +:u-button{to="https://laravel.com/docs/12.x/configuration#configuration-caching" target="_blank" label="Read more about configuration caching" aria-label="Read more about configuration caching" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## php artisan route:cache +This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. + +:u-button{to="https://laravel.com/docs/12.x/routing#route-caching" target="_blank" label="Read more about route caching" aria-label="Read more about route caching" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## php artisan view:cache +This command caches all of the views in your application, which can greatly decrease the time it takes to render your views. + +:u-button{to="https://laravel.com/docs/12.x/views#optimizing-views" target="_blank" label="Read more about view caching" aria-label="Read more about view caching" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## php artisan event:cache +This command creates a manifest of all your application's events and listeners, which can greatly speed up the process of registering them with Laravel. + +:u-button{to="https://laravel.com/docs/12.x/events#event-discovery-in-production" target="_blank" label="Read more about event caching" aria-label="Read more about event caching" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + +## Debugging the AUTORUN script +It's very important to understand the nature of how containerized environments work when debugging the AUTORUN script. In some cases, some users may become frustrated when they push an update but their changes are never deployed. + +In most cases, this is due to a bug in their application code that causes a migration or some other process to fail. + +::note +If a failure occurs in the Laravel Automations script, it will exit with a non-zero exit code -- preventing the container from starting. +:: + +If you are experiencing issues, you can enable the `AUTORUN_DEBUG` environment variable to get more detailed ouput of what could be going wrong. + +If you need even more information, you can set `LOG_OUTPUT_LEVEL` to `debug` to get **A TON** of output of what's exactly happening. + +### Preventing issues with the AUTORUN script +- Ensure you are running the latest version of `serversideup/php` +- Ensure you have dependencies installed (ie. `composer install`) before calling this script +- Use automated testing to catch issues before deploying \ No newline at end of file diff --git a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md new file mode 100644 index 000000000..28b4cdedd --- /dev/null +++ b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md @@ -0,0 +1,113 @@ +--- +head.title: 'Laravel Task Scheduler with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure a Laravel Task Scheduler with Docker.' +layout: docs +title: Task Scheduler +--- + +## Laravel Task Scheduler with Docker +Running a Laravel task scheduler with Docker can be a little different from traditional methods. + +## Important concepts +1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) +1. Be sure to set the health check +1. We will **not** use `cron` to run the scheduler +1. By default `schedule:work` checks every minute, so we will use that to run the system process +1. The actual time trigger itself is set within Laravel + +## More detail +We need to run the [schedule:work](https://laravel.com/docs/12.x/scheduling#running-the-scheduler-locally){target="_blank"} command from Laravel. Although the docs say "Running the scheduler locally", this is what we want in production. It will run the scheduler in the foreground and execute it every minute. You can configure your Laravel app for the exact time that a command should run through a [scheduled task](https://laravel.com/docs/12.x/scheduling#scheduling-artisan-commands){target="_blank"}. + + +## Examples +Here is a simplified example of how you can achieve this with Docker Compose: + +::note +Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. +:: + +#### Example & Simplified Docker Compose File +```yaml [docker-compose.yml] +services: + php: + image: my/laravel-app + + task: + image: my/laravel-app + command: ["php", "/var/www/html/artisan", "schedule:work"] + stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx + healthcheck: + # This is our native healthcheck script for the scheduler + test: ["CMD", "healthcheck-schedule"] + start_period: 10s +``` + + +#### Example in Laravel +::note +This example is for Laravel version <= 10. +:: + +This is an example how we would set the actual execution time within Laravel itself: +```php [app/Console/Kernel.php] +command('process:invoices')->daily()->at('02:00')->timezone('America/Chicago'); + $schedule->command('process:latefees')->daily()->at('04:00')->timezone('America/Chicago'); + } + + /** + * Register the commands for the application. + * + * @return void + */ + protected function commands() + { + $this->load(__DIR__.'/Commands'); + + require base_path('routes/console.php'); + } +} +``` + +```php [routes/console.php] +delete(); +})->daily()->at('04:00')->timezone('America/Chicago'); +``` + + + +## Get Up and Running The Easy Way +We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with the Task Scheduler on Spin Pro. + +:u-button{to="https://getspin.pro/docs/services/laravel-scheduler" target="_blank" label="Learn more about Laravel Task Scheduler + Spin Pro" aria-label="Learn more about Laravel Task Scheduler + Spin Pro" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} diff --git a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md new file mode 100644 index 000000000..0fe8837ec --- /dev/null +++ b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md @@ -0,0 +1,46 @@ +--- +head.title: 'Laravel Queue with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure a Laravel Queue with Docker.' +layout: docs +title: Queue +--- + +## Laravel Queue with Docker +All you need to do is pass the Laravel Queue command to the container and Laravel will start the queue worker. + +```sh +php artisan queue:work --tries=3 +``` + +## Important concepts +1. It's usually best to run the queue as a separate container (but using the same image) +1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) +1. Be sure to set the health check +1. Notice we're using the same `my/laravel-app` image for both the PHP and Queue services. This is a common practice to keep the image consistent. +1. If you need to run the queue in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process){target="_blank"} to manage and monitor multiple processes in one container. + +## Run it with Docker +::note +Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. +:: + +#### Example & Simplified Docker Compose File +```yaml [docker-compose.yml] +services: + php: + image: my/laravel-app + + queue: + image: my/laravel-app + command: ["php", "/var/www/html/artisan", "queue:work", "--tries=3"] + stop_signal: SIGTERM # Graceful shutdown for fpm-apache or fpm-nginx + healthcheck: + # This is our native healthcheck script for the queue + test: ["CMD", "healthcheck-queue"] + start_period: 10s +``` + +## Get Up and Running The Easy Way +We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Queues on Spin Pro. + +:u-button{to="https://getspin.pro/docs/services/laravel-queues" target="_blank" label="Learn more about Laravel Queues + Spin Pro" aria-label="Learn more about Laravel Queues + Spin Pro" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md new file mode 100644 index 000000000..fbb63d1df --- /dev/null +++ b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md @@ -0,0 +1,51 @@ +--- +head.title: 'Laravel Horizon with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure Laravel Horizon with Docker.' +layout: docs +title: Horizon +--- + +## Laravel Horizon with Docker +We simply pass the command to the Docker container and Laravel will start the Horizon process. + +```sh +php artisan horizon +``` + +## Important concepts +1. In most cases, you probably want to run this as a separate container from your web container +1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) +1. Be sure to set the health check +1. Ensure that you have your `.env` configured correctly to authenticate with Redis +1. Ensure Redis is running before you attempt to connect Horizon to Redis +1. If you need to run horizon in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process){target="_blank"} to manage and monitor multiple processes in one container. + +## Run it with Docker +::note +Notice we're calling the artisan command explicitly with the full path (`/var/www/html/artisan`). This is because we need to run the command from the context of the container. +:: + +#### Example & Simplified Docker Compose File +```yaml [docker-compose.yml] +services: + php: + image: my/laravel-app + + redis: + image: redis:6 + command: "redis-server --appendonly yes --requirepass redispassword" + + horizon: + image: my/laravel-app + command: ["php", "/var/www/html/artisan", "horizon"] + stop_signal: SIGTERM # Set this for graceful shutdown if you're using fpm-apache or fpm-nginx + healthcheck: + # This is our native healthcheck script for Horizon + test: ["CMD", "healthcheck-horizon"] + start_period: 10s +``` + +## Get Up and Running The Easy Way +We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Horizon on Spin Pro. + +:u-button{to="https://getspin.pro/docs/services/laravel-horizon" target="_blank" label="Learn more about Laravel Horizon + Spin Pro" aria-label="Learn more about Laravel Horizon + Spin Pro" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md new file mode 100644 index 000000000..7163ee115 --- /dev/null +++ b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md @@ -0,0 +1,71 @@ +--- +head.title: 'Laravel Reverb with Docker - Docker PHP - Server Side Up' +description: 'Learn how to configure Laravel Reverb with Docker.' +layout: docs +title: Reverb +--- + +## Laravel Reverb with Docker +We simply pass the command to the Docker container and Laravel will start the Reverb process. + +```sh +php artisan reverb:start +``` + +## Important concepts +1. You will need to follow the [Laravel Reverb setup instructions](https://laravel.com/docs/12.x/reverb) to install the Laravel Reverb package into your Laravel application. +1. In most cases, you probably want to run this as a separate container from your web container +1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) +1. Be sure to set the health check +1. You may need a proxy like Traefik to correctly route traffic to the right container +1. If you need to run Reverb in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process){target="_blank"} to manage and monitor multiple processes in one container. + +## Run it with Docker +::note +Notice Laravel Reverb is running on port `8000`, where as Laravel is running on port `8080`. You may need to set additional environment variables and configure a reverse proxy like Traefik to correctly route traffic to the right container. +:: + +#### Example & Simplified Docker Compose File +```yaml [docker-compose.yml] +services: + php: + image: my/laravel-app + labels: + - "traefik.enable=true" + - "traefik.http.routers.laravel.tls=true" + - "traefik.http.routers.laravel.entrypoints=websecure" + - "traefik.http.routers.laravel.rule=Host(`https://app.example.com`)" + - "traefik.http.services.laravel.loadbalancer.server.port=8080" + - "traefik.http.services.laravel.loadbalancer.server.scheme=http" + + reverb: + image: my/laravel-app + command: ["php", "/var/www/html/artisan", "--port=8000", "reverb:start"] + stop_signal: SIGTERM # Graceful shutdown for fpm-apache or fpm-nginx + healthcheck: + # This is our native healthcheck script for Reverb + test: ["CMD", "healthcheck-reverb"] + start_period: 10s + labels: + - "traefik.enable=true" + - "traefik.http.routers.reverb.tls=true" + - "traefik.http.routers.reverb.entrypoints=websecure" + - "traefik.http.routers.reverb.rule=Host(`https://reverb.example.com`)" + - "traefik.http.services.reverb.loadbalancer.server.port=8000" + - "traefik.http.services.reverb.loadbalancer.server.scheme=http" +``` + +## Laravel ENV updates +Reverb may require a few ENV variables to be set in your Laravel application. +| **Laravel ENV Variable** | **Description** | **Value if matching example above** | +| ------------------------- | --------- | --------- | +| `REVERB_HOST` | The hostname the **CLIENT** will connect to. | `reverb.example.com` | +| `REVERB_PORT` | The port the **CLIENT** will connect to. | `443` | +| `REVERB_SCHEME` | The scheme the **CLIENT** will connect to. | `https` | + +Be sure to not get `REVERB_HOST` or `REVERB_PORT` confused with `REVERB_SERVER_HOST` or `REVERB_SERVER_PORT`. The `_SERVER_` variables are for the **SERVER** (the Reverb daemon itself) and the others are for the **CLIENT** (people connecting to your application). + +## Get Up and Running The Easy Way +We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Reverb on Spin Pro. + +:u-button{to="https://getspin.pro/docs/services/laravel-reverb" target="_blank" label="Learn more about Laravel Reverb + Spin Pro" aria-label="Learn more about Laravel Reverb + Spin Pro" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file From df7ac7aac7c495c3ab143106646979f72d7d136e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:47:21 -0500 Subject: [PATCH 213/304] Add WordPress optimization documentation for Docker PHP images, detailing security practices, deployment strategies, and community considerations, enhancing guidance for users modernizing WordPress with Docker. --- .../4.using-wordpress-with-docker.md | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md diff --git a/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md b/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md new file mode 100644 index 000000000..3e933a656 --- /dev/null +++ b/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md @@ -0,0 +1,64 @@ +--- +head.title: 'WordPress Optimizations - Docker PHP - Server Side Up' +description: 'Learn how serversideup/php is optimized for running WordPress in production.' +layout: docs +title: Using Docker with WordPress +--- +::lead-p +Ready to take the dive and modernize WordPress with Docker? + +Long story short, it can be definitely be worth it depending on your use case. From our experience, you can save yourself hours, or really over-engineer and dig yourself a hole. +:: + +## WordPress & Security Optimizations +* Hardening of Apache & NGINX included +* Disabling of XML-RPC +* Preventative access to sensitive version control or CI files +* Protection against other common attacks + +See our [Apache security.conf](https://github.com/serversideup/docker-php/blob/main/src/variations/fpm-apache/etc/apache2/conf-available/security.conf) for more detail. + +## "The WordPress Way" +We're very grateful for WordPress, and it even runs our blog for [Server Side Up](https://serversideup.net/). The only downside with WordPress is it has a lot of history with it. That means the core was written with in a mindset before all of the fancy and flashy methods that we have today. + +The most important thing you need to remember is always try to do things _"The WordPress Way"_. + +The more you work with it, the less you are fighting the platform. + +### What do you mean by "fighting the platform"? +Introducing modern toolsets to WordPress may allow you to get WordPress up and running, but severely limit you to the rest of the community tools. + +This could be things like (but not limited to): +* Using NGINX as the web server +* Deploying updates by packaging WordPress into a Docker Image +* Using too new of a PHP version +* Using [Roots Bedrock](https://roots.io/){target="_blank"} to modernize your development flow + +We've tried all of these above and we learned that most of the plugins that we relied on had issues with this. + +## How does Server Side Up run WordPress? +We embrace these principals: +1. Always do things "The WordPress Way" +1. Keep plugin installation to a minimum +1. Always use Apache as the webserver +1. Only commit _your own_ source code (not WordPress core or another plugin) + +## The structure +We run WordPress in production on Docker Swarm using: +1. [Traefik](https://traefik.io/traefik/){target="_blank"} (handles SSL automatically with Let's Encrypt) +2. [Our PHP-Apache image](https://hub.docker.com/r/serversideup/php/tags?name=fpm-apache&page=1&ordering=-name){target="_blank"} (this is optimized for WordPress) +3. [MariaDB](https://hub.docker.com/_/mariadb){target="_blank"} (the "non-Oracle" alternative to MySQL) + +## Deploying updates +We attempted to deploy WordPress with rolling updates (like we do with Laravel) by packaging WordPress into a Docker image use the [Roots Bedrock](https://roots.io/){target="_blank"} framework, but we ran into a ton of issues with community plugins. + +Our most stable solution (following "The WordPress Way") is to: +1. Mount `/var/www/html` of the container to the host +2. At the host, run an SSH `git pull` from our CI system + +Ugly, but it works 🤷‍♂️ + +## Example Repository +We included an example repository that makes it easy for you to clone down and get started. + +:u-button{to="https://github.com/serversideup/docker-wordpress" target="_blank" label="View the Example WordPress + Docker Repository" aria-label="View the Example WordPress + Docker Repository" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} From 7fe6f023751f87fcb37005005cc20cb311006cde Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 15:49:25 -0500 Subject: [PATCH 214/304] Add section on migrating from official PHP images in installation documentation, providing guidance for users already utilizing Docker, enhancing the onboarding experience for transitioning to our PHP Docker images. --- docs/content/docs/1.getting-started/3.installation.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/content/docs/1.getting-started/3.installation.md b/docs/content/docs/1.getting-started/3.installation.md index 9b9c8282b..9a854f3fc 100644 --- a/docs/content/docs/1.getting-started/3.installation.md +++ b/docs/content/docs/1.getting-started/3.installation.md @@ -36,6 +36,11 @@ If you'd like to learn more about containers, you can learn more following the g :u-button{to="/docs/deployment-and-production/container-basics" label="Learn More About Containers" aria-label="Learn More About Containers" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} +## Already using Docker? +If you're already using the official PHP images, switching will literally take you seconds. + +:u-button{to="/docs/guide/migrating-from-official-php-images" label="Learn how to migrate from the official PHP images" aria-label="Learn how to migrate from the official PHP images" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} + ## Quick Start In order to run containers, we need a container engine installed such as Docker. You can follow [Docker's installation guide](https://docs.docker.com/get-started/get-docker/) to get started. Confirm Docker is working by running these commands in your terminal: ```bash [Terminal] From 9aff799a7a4b0bf5c2c927172b8a5e21502e4ec2 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 16:01:02 -0500 Subject: [PATCH 215/304] Enhance Laravel documentation by adding tips for using Spin Pro for Task Scheduler, Queues, Horizon, and Reverb, streamlining the setup process for users deploying on VPS with Docker and zero downtime. --- .../1.laravel/2.task-scheduler.md | 13 +++++-------- .../docs/3.framework-guides/1.laravel/3.queue.md | 8 +++----- .../docs/3.framework-guides/1.laravel/4.horizon.md | 10 ++++------ .../docs/3.framework-guides/1.laravel/4.reverb.md | 10 ++++------ 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md index 28b4cdedd..65c04d207 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md +++ b/docs/content/docs/3.framework-guides/1.laravel/2.task-scheduler.md @@ -9,6 +9,10 @@ title: Task Scheduler Running a Laravel task scheduler with Docker can be a little different from traditional methods. ## Important concepts +::tip{to="https://getspin.pro/docs/services/laravel-scheduler" target="_blank"} +If you want the easiest way to get up and running with Laravel Task Scheduler on a VPS using Docker and zero downtime deployments, we recommend using [Spin Pro](https://getspin.pro/docs/services/laravel-scheduler){target="_blank"}. +:: + 1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) 1. Be sure to set the health check 1. We will **not** use `cron` to run the scheduler @@ -103,11 +107,4 @@ use Illuminate\Support\Facades\Schedule; Schedule::call(function () { DB::table('recent_users')->delete(); })->daily()->at('04:00')->timezone('America/Chicago'); -``` - - - -## Get Up and Running The Easy Way -We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with the Task Scheduler on Spin Pro. - -:u-button{to="https://getspin.pro/docs/services/laravel-scheduler" target="_blank" label="Learn more about Laravel Task Scheduler + Spin Pro" aria-label="Learn more about Laravel Task Scheduler + Spin Pro" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} +``` \ No newline at end of file diff --git a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md index 0fe8837ec..d93de2674 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/3.queue.md +++ b/docs/content/docs/3.framework-guides/1.laravel/3.queue.md @@ -13,6 +13,9 @@ php artisan queue:work --tries=3 ``` ## Important concepts +::tip{to="https://getspin.pro/docs/services/laravel-queues" target="_blank"} +If you want the easiest way to get up and running with Laravel Queues on a VPS using Docker and zero downtime deployments, we recommend using [Spin Pro](https://getspin.pro/docs/services/laravel-queues){target="_blank"}. +:: 1. It's usually best to run the queue as a separate container (but using the same image) 1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) 1. Be sure to set the health check @@ -39,8 +42,3 @@ services: test: ["CMD", "healthcheck-queue"] start_period: 10s ``` - -## Get Up and Running The Easy Way -We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Queues on Spin Pro. - -:u-button{to="https://getspin.pro/docs/services/laravel-queues" target="_blank" label="Learn more about Laravel Queues + Spin Pro" aria-label="Learn more about Laravel Queues + Spin Pro" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md index fbb63d1df..c9d4c0a6d 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md +++ b/docs/content/docs/3.framework-guides/1.laravel/4.horizon.md @@ -13,6 +13,9 @@ php artisan horizon ``` ## Important concepts +::tip{to="https://getspin.pro/docs/services/laravel-horizon" target="_blank"} +If you want the easiest way to get up and running with Laravel Horizon on a VPS using Docker and zero downtime deployments, we recommend using [Spin Pro](https://getspin.pro/docs/services/laravel-horizon){target="_blank"}. +:: 1. In most cases, you probably want to run this as a separate container from your web container 1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) 1. Be sure to set the health check @@ -43,9 +46,4 @@ services: # This is our native healthcheck script for Horizon test: ["CMD", "healthcheck-horizon"] start_period: 10s -``` - -## Get Up and Running The Easy Way -We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Horizon on Spin Pro. - -:u-button{to="https://getspin.pro/docs/services/laravel-horizon" target="_blank" label="Learn more about Laravel Horizon + Spin Pro" aria-label="Learn more about Laravel Horizon + Spin Pro" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md index 7163ee115..6e104f873 100644 --- a/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md +++ b/docs/content/docs/3.framework-guides/1.laravel/4.reverb.md @@ -13,6 +13,9 @@ php artisan reverb:start ``` ## Important concepts +::tip{to="https://getspin.pro/docs/services/laravel-reverb" target="_blank"} +If you want the easiest way to get up and running with Laravel Reverb on a VPS using Docker and zero downtime deployments, we recommend using [Spin Pro](https://getspin.pro/docs/services/laravel-reverb){target="_blank"}. +:: 1. You will need to follow the [Laravel Reverb setup instructions](https://laravel.com/docs/12.x/reverb) to install the Laravel Reverb package into your Laravel application. 1. In most cases, you probably want to run this as a separate container from your web container 1. If you're using `fpm-apache` or `fpm-nginx`, might need to set the stop signal to `SIGTERM` for a graceful shutdown (see this [PR](https://github.com/serversideup/docker-php/pull/437){target="_blank"} for more details why) @@ -63,9 +66,4 @@ Reverb may require a few ENV variables to be set in your Laravel application. | `REVERB_PORT` | The port the **CLIENT** will connect to. | `443` | | `REVERB_SCHEME` | The scheme the **CLIENT** will connect to. | `https` | -Be sure to not get `REVERB_HOST` or `REVERB_PORT` confused with `REVERB_SERVER_HOST` or `REVERB_SERVER_PORT`. The `_SERVER_` variables are for the **SERVER** (the Reverb daemon itself) and the others are for the **CLIENT** (people connecting to your application). - -## Get Up and Running The Easy Way -We do all the heavy lifting for you with Spin Pro. It's as easy as selecting it in a menu and we'll configure everything else for you. Learn how easy it is to get up and running with Reverb on Spin Pro. - -:u-button{to="https://getspin.pro/docs/services/laravel-reverb" target="_blank" label="Learn more about Laravel Reverb + Spin Pro" aria-label="Learn more about Laravel Reverb + Spin Pro" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold"} \ No newline at end of file +Be sure to not get `REVERB_HOST` or `REVERB_PORT` confused with `REVERB_SERVER_HOST` or `REVERB_SERVER_PORT`. The `_SERVER_` variables are for the **SERVER** (the Reverb daemon itself) and the others are for the **CLIENT** (people connecting to your application). \ No newline at end of file From d8228eb09ab76b45c29c97e48608de10f8da9ae5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 22 Oct 2025 16:11:39 -0500 Subject: [PATCH 216/304] Add FPM-NGINX documentation, detailing usage, configuration, and performance tuning for PHP applications, enhancing guidance for users deploying Laravel and modern PHP frameworks with Docker. --- .../docs/2.image-variations/fpm-nginx.md | 333 ++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 docs/content/docs/2.image-variations/fpm-nginx.md diff --git a/docs/content/docs/2.image-variations/fpm-nginx.md b/docs/content/docs/2.image-variations/fpm-nginx.md new file mode 100644 index 000000000..c44bed551 --- /dev/null +++ b/docs/content/docs/2.image-variations/fpm-nginx.md @@ -0,0 +1,333 @@ +--- +title: FPM-NGINX +description: 'Learn how to use the FPM-NGINX variation of the serversideup/php image.' +--- + +::lead-p +The FPM-NGINX variation combines PHP-FPM with NGINX as a reverse proxy in a single container. This is the traditional setup widely adopted for many PHP applications and is currently is the best balance of performance, stability, and compatibility. If you want the latest and greatest, consider using the [FrankenPHP variation →](/docs/image-variations/frankenphp). +:: + +## When to Use FPM-NGINX +Use the FPM-NGINX variation when you need to: + +- Run Laravel applications with excellent performance and stability +- Want an all-in-one container with both web server and PHP processing +- Need a fast, lightweight web server with low resource consumption +- Serve static assets efficiently while processing PHP requests + +#### Perfect for +- Laravel applications (this is our most popular variation for Laravel) +- Modern PHP frameworks (Symfony, etc.) +- API-first applications +- Production deployments requiring high performance and stability + +#### What's Inside + +| Item | Status | +|------|--------| +| NGINX web server | ✅ | +| PHP-FPM process manager | ✅ | +| PHP CLI binary | ✅ | +| Common PHP extensions | ✅ | +| `composer` executable | ✅ | +| `install-php-extensions` script | ✅ | +| Essential system utilities | ✅ | +| S6 Overlay (process supervisor) | ✅ | +| Native health checks | ✅ (via HTTP endpoint) | +| SSL/TLS support | ✅ (self-signed certificates) | +| Process management | S6 Overlay supervising both NGINX and PHP-FPM | +| Exposed Ports | `8080` (HTTP), `8443` (HTTPS) | +| Stop Signal | `SIGQUIT` | + +## How FPM-NGINX Works +This variation runs both NGINX and PHP-FPM in a single container, managed by S6 Overlay. Here's how requests flow: + +::steps{level="4"} + +#### Client sends HTTP request +The container listens on port 8080 (or 8443 for HTTPS) for incoming HTTP requests. + +#### NGINX receives the request +NGINX receives the request and determines if it's a static file or PHP script. + +#### Check for static files +Static files (CSS, JavaScript, images) are served directly by NGINX. + +#### Forward PHP requests to PHP-FPM +PHP requests are forwarded to PHP-FPM via FastCGI protocol. + +#### Process PHP requests with PHP-FPM +PHP-FPM processes the PHP script and returns the result to NGINX. + +#### Send the response back to the client +NGINX sends the response back to the client. + +:: + +S6 Overlay ensures both NGINX and PHP-FPM are running and automatically restarts them if either process fails. + +::tip +This variation offers better performance than FPM-Apache for most modern PHP applications. NGINX is designed to handle high concurrency with lower resource consumption. +:: + +## Quick Start +Here are a few examples to help you get started with the FPM-NGINX variation. + +### Docker CLI +```bash [Terminal] +docker run -p 80:8080 -v $(pwd):/var/www/html/public serversideup/php:8.4-fpm-nginx +``` + +Your application will be available at `http://localhost`. The default webroot is `/var/www/html/public`. + +### Docker Compose + +::warning +Notice how we're mapping the current directory to `/var/www/html/`, but the actual default document root is `/var/www/html/public`. We're assuming you're creating the `public` directory and putting your PHP code in there. It's not best practice to expose your `compose.yml` file. See the [Installation guide](/docs/getting-started/installation) for a full example. +:: + +This is the recommended approach for local development and production deployments. + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - "80:8080" + volumes: + - ./:/var/www/html + environment: + PHP_OPCACHE_ENABLE: "1" +``` + +::tip +The FPM-NGINX variation uses ports 8080 and 8443 (instead of 80 and 443) to allow the container to run as a non-root user for better security. +:: + +### Laravel Example +The FPM-NGINX variation is perfectly suited for Laravel applications: + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - "80:8080" + - "443:8443" + volumes: + - .:/var/www/html + environment: + SSL_MODE: "full" + PHP_OPCACHE_ENABLE: "1" + depends_on: + - mariadb + - redis + + mariadb: + image: mariadb:11 + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: laravel + MYSQL_USER: laravel + MYSQL_PASSWORD: laravel + volumes: + - db_data:/var/lib/mysql + + redis: + image: redis:alpine + command: redis-server --appendonly yes + volumes: + - redis_data:/data + +volumes: + db_data: + redis_data: +``` + + +### Health Check +The FPM-NGINX variation includes a built-in health check that verifies NGINX is responding: + +::note +The health check endpoint is configurable via the `HEALTHCHECK_PATH` environment variable, which defaults to `/healthcheck`. +:: + +## SSL/TLS Support +The FPM-NGINX variation includes built-in SSL support with self-signed certificates for development. + +### Enabling SSL +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - "80:8080" + - "443:8443" + volumes: + - ./:/var/www/html + environment: + SSL_MODE: "full" +``` + +Available SSL modes: +- `off` - SSL disabled (default) +- `mixed` - Both HTTP (8080) and HTTPS (8443) enabled +- `full` - HTTPS only on port 8443 + +### Custom SSL Certificates +For production, use your own SSL certificates: + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - "443:8443" + volumes: + - ./:/var/www/html + - ./certs/server.crt:/etc/ssl/private/self-signed-web.crt:ro + - ./certs/server.key:/etc/ssl/private/self-signed-web.key:ro + environment: + SSL_MODE: "full" +``` + +::warning +For production deployments, consider using a reverse proxy like Traefik or Caddy to handle SSL termination instead of managing certificates in the container. +:: + +## Environment Variables +The FPM-NGINX variation supports extensive customization through environment variables. + +### NGINX Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `NGINX_WEBROOT` | `/var/www/html/public` | Document root for NGINX | +| `NGINX_ACCESS_LOG` | `/dev/stdout` | Path to access log file | +| `NGINX_ERROR_LOG` | `/dev/stderr` | Path to error log file | +| `NGINX_CLIENT_MAX_BODY_SIZE` | `100M` | Maximum upload/request body size | +| `NGINX_FASTCGI_BUFFERS` | `8 8k` | Number and size of FastCGI buffers | +| `NGINX_FASTCGI_BUFFER_SIZE` | `8k` | Size of the first FastCGI response buffer | +| `NGINX_SERVER_TOKENS` | `off` | Show NGINX version in headers (`on`/`off`) | +| `NGINX_LISTEN_IP_PROTOCOL` | `all` | IP protocol to listen on (`all`, `ipv4`, `ipv6`) | +| `SSL_MODE` | `off` | SSL mode: `off`, `mixed`, or `full` | +| `SSL_CERTIFICATE_FILE` | `/etc/ssl/private/self-signed-web.crt` | Path to SSL certificate | +| `SSL_PRIVATE_KEY_FILE` | `/etc/ssl/private/self-signed-web.key` | Path to SSL private key | +| `HEALTHCHECK_PATH` | `/healthcheck` | Path for health check endpoint | + +::tip +For a complete list of available environment variables, see the [Environment Variable Specification →](/docs/reference/environment-variable-specification). +:: + +### PHP-FPM Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `PHP_FPM_POOL_NAME` | `www` | Name of the PHP-FPM pool | +| `PHP_FPM_PM_CONTROL` | `dynamic` | Process manager control (`dynamic`, `static`, `ondemand`) | +| `PHP_FPM_PM_MAX_CHILDREN` | `20` | Maximum number of child processes | +| `PHP_FPM_PM_START_SERVERS` | `2` | Number of child processes created on startup | +| `PHP_FPM_PM_MIN_SPARE_SERVERS` | `1` | Minimum number of idle processes | +| `PHP_FPM_PM_MAX_SPARE_SERVERS` | `3` | Maximum number of idle processes | +| `PHP_MEMORY_LIMIT` | `256M` | Maximum memory a script can use | +| `PHP_MAX_EXECUTION_TIME` | `99` | Maximum time a script can run (seconds) | +| `PHP_UPLOAD_MAX_FILE_SIZE` | `100M` | Maximum upload file size | +| `PHP_POST_MAX_SIZE` | `100M` | Maximum POST request size | + +## Performance Tuning +Here are some tuning recommendations for different scenarios: + +### For Production (low memory environments) +::note{to="docs/deployment-and-production/packaging-your-app-for-deployment"} +If you're running an application in production, you'll likely want to package your application inside an image for deployment. Click here to learn more. +:: +```yml [compose.yml] +services: + php: + # You'll likely replace this with your own custom image name + image: serversideup/php:8.4-fpm-nginx + environment: + # Enable OPcache for production + PHP_OPCACHE_ENABLE: "1" + + # NGINX Settings (adjust as needed) + NGINX_FASTCGI_BUFFERS: "16 16k" + NGINX_FASTCGI_BUFFER_SIZE: "32k" +``` + +### For High-Traffic Applications +```yml [compose.yml] +services: + php: + # You'll likely replace this with your own custom image name + image: serversideup/php:8.4-fpm-nginx + environment: + # NGINX Settings + NGINX_CLIENT_MAX_BODY_SIZE: "200M" + NGINX_FASTCGI_BUFFERS: "32 32k" + + # PHP-FPM Settings (adjust as needed) + PHP_FPM_PM_CONTROL: "static" + PHP_FPM_PM_MAX_CHILDREN: "50" + PHP_MEMORY_LIMIT: "512M" + + # OPcache Settings + PHP_OPCACHE_ENABLE: "1" +``` + +::note +These are just examples. Review the [Environment Variable Specification](/docs/reference/environment-variable-specification) for a complete list of available environment variables to match your needs. +:: + +## NGINX Configuration +Unlike Apache's `.htaccess` files, NGINX uses configuration files. The FPM-NGINX variation comes pre-configured for Laravel and modern PHP applications. + +### Default Configuration +The default NGINX configuration includes: +- FastCGI caching headers +- Gzip compression +- Security headers +- Laravel-compatible URL rewriting +- Static file optimization + +### Custom NGINX Configuration +You can add custom NGINX server configuration by mounting files: + +```yml [compose.yml] +services: + php: + image: serversideup/php:8.4-fpm-nginx + ports: + - "80:8080" + volumes: + - ./:/var/www/html + - ./custom-nginx.conf:/etc/nginx/conf.d/custom.conf:ro +``` + +Example custom configuration: + +```nginx [custom-nginx.conf] +# Add custom headers +add_header X-Custom-Header "My Value" always; + +# Custom location block +location /api { + try_files $uri $uri/ /index.php?$query_string; + + # Additional settings for API endpoints + client_max_body_size 50M; +} + +# Rate limiting +limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; +location /api/ { + limit_req zone=api burst=20 nodelay; +} +``` + +## Further Customization +If you need to customize the container further, reference the docs below: + +- [Environment Variable Specification](/docs/reference/environment-variable-specification) - See which environment variables are available to customize PHP and NGINX settings. +- [Command Reference](/docs/reference/command-reference) - See which commands are available to run inside the container. + From fe997be04b89ee13e540d3095cf88f260532b800 Mon Sep 17 00:00:00 2001 From: Dan Pastori Date: Wed, 22 Oct 2025 16:13:58 -0500 Subject: [PATCH 217/304] Enhance documentation for PHP Docker images by adding optional 'redirect' field in content configuration, integrating '@vueuse/nuxt' in Nuxt configuration, and updating package dependencies. Introduced a new 'ServerSideUp' component for improved user interface and added redirection logic in page handling. Additionally, included a new logo for branding consistency. --- docs/app/app.vue | 2 + docs/app/components/ServerSideUp.vue | 237 ++++ docs/app/pages/[...slug].vue | 4 + docs/content.config.ts | 1 + docs/content/docs/index.md | 3 + docs/nuxt.config.ts | 1 + docs/package.json | 2 + .../logos/server-side-up-logo-horizontal.svg | 6 + docs/yarn.lock | 1103 +++++++++-------- 9 files changed, 831 insertions(+), 528 deletions(-) create mode 100644 docs/app/components/ServerSideUp.vue create mode 100644 docs/content/docs/index.md create mode 100644 docs/public/images/logos/server-side-up-logo-horizontal.svg diff --git a/docs/app/app.vue b/docs/app/app.vue index 20718807c..87830df57 100644 --- a/docs/app/app.vue +++ b/docs/app/app.vue @@ -2,6 +2,8 @@ + + +