diff --git a/frankenphp/.dockerignore b/frankenphp/.dockerignore new file mode 100644 index 0000000..dc5a875 --- /dev/null +++ b/frankenphp/.dockerignore @@ -0,0 +1,34 @@ +**/*.log +**/*.md +**/*.php~ +**/*.dist.php +**/*.dist +**/*.cache +**/._* +**/.dockerignore +**/.DS_Store +**/.git/ +**/.gitattributes +**/.gitignore +**/.gitmodules +**/compose.*.yaml +**/compose.*.yml +**/compose.yaml +**/compose.yml +**/docker-compose.*.yaml +**/docker-compose.*.yml +**/docker-compose.yaml +**/docker-compose.yml +**/Dockerfile +**/Thumbs.db +.github/ +docs/ +public/bundles/ +tests/ +var/ +vendor/ +.editorconfig +.env.*.local +.env.local +.env.local.php +.env.test diff --git a/frankenphp/.gitignore b/frankenphp/.gitignore new file mode 100644 index 0000000..c8ea302 --- /dev/null +++ b/frankenphp/.gitignore @@ -0,0 +1 @@ +*.dist** diff --git a/frankenphp/Dockerfile b/frankenphp/Dockerfile new file mode 100644 index 0000000..945a7a4 --- /dev/null +++ b/frankenphp/Dockerfile @@ -0,0 +1,43 @@ +#syntax=docker/dockerfile:1 + +# Base FrankenPHP image +FROM frankenphp_upstream AS frankenphp_base + +# defaults +ENV APP_ENV=dev XDEBUG_MODE=off + +WORKDIR /app + +VOLUME /app/var/ + +# persistent / runtime deps +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install -y --no-install-recommends \ + acl \ + file \ + gettext \ + git \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + install-php-extensions \ + @composer \ + apcu \ + intl \ + opcache \ + zip \ + ; + +# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser +ENV COMPOSER_ALLOW_SUPERUSER=1 +ENV PATH="${PATH}:/root/.composer/vendor/bin" +ENV PHP_INI_SCAN_DIR=":$PHP_INI_DIR/app.conf.d" + +COPY --link conf/php/ $PHP_INI_DIR/app.conf.d/ +COPY --link conf/caddy/ /etc/caddy/ +COPY --link --chmod=755 docker-entrypoint.sh /usr/local/bin/docker-entrypoint + +ENTRYPOINT ["docker-entrypoint"] + +HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1 +CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ] diff --git a/frankenphp/build.mk b/frankenphp/build.mk new file mode 100644 index 0000000..bbe0dac --- /dev/null +++ b/frankenphp/build.mk @@ -0,0 +1,29 @@ +BAKE_FLAGS := --pull --no-cache --push + +PHONY += frankenphp-bake-all +frankenphp-bake-all: ## Bake all FrankenPHP images + @PHP83_PATCH=$(call get_php_minor,8.3) PHP84_PATCH=$(call get_php_minor,8.4) \ + docker buildx bake -f frankenphp/docker-bake.hcl $(BAKE_FLAGS) + +PHONY += frankenphp-bake-print +frankenphp-bake-print: BAKE_FLAGS := --print +frankenphp-bake-print: frankenphp-bake-all ## Print bake plan for FrankenPHP images + +PHONY += frankenphp-bake-local +frankenphp-bake-local: BAKE_FLAGS := --pull --progress plain --no-cache --load --set *.platform=linux/$(CURRENT_ARCH) +frankenphp-bake-local: frankenphp-bake-all run-frankenphp-tests ## Bake all FrankenPHP images locally + +PHONY += frankenphp-bake-test +frankenphp-bake-test: BAKE_FLAGS := --pull --progress plain --no-cache +frankenphp-bake-test: frankenphp-bake-all run-frankenphp-tests ## CI test for FrankenPHP images + +PHONY += run-frankenphp-tests +run-frankenphp-tests: + $(call step,Run tests in druidfi/frankenphp:1.4.2-php8.3) + @docker run --rm -t -v $(CURDIR)/tests/scripts:/app/scripts druidfi/frankenphp:1.4.2-php8.3 /app/scripts/tests_symfony.sh + $(call step,Run tests in druidfi/frankenphp:1.4.2-php8.4) + @docker run --rm -t -v $(CURDIR)/tests/scripts:/app/scripts druidfi/frankenphp:1.4.2-php8.4 /app/scripts/tests_symfony.sh + +PHONY += frankenphp-update +frankenphp-update: + @frankenphp/update.sh diff --git a/frankenphp/conf/caddy/Caddyfile b/frankenphp/conf/caddy/Caddyfile new file mode 100644 index 0000000..b881367 --- /dev/null +++ b/frankenphp/conf/caddy/Caddyfile @@ -0,0 +1,59 @@ +{ + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + {$FRANKENPHP_CONFIG} + } +} + +{$CADDY_EXTRA_CONFIG} + +{$SERVER_NAME:localhost} { + log { + {$CADDY_SERVER_LOG_OPTIONS} + # Redact the authorization query parameter that can be set by Mercure + format filter { + request>uri query { + replace authorization REDACTED + } + } + } + + root /app/public + encode zstd br gzip + + 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} + + # Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics + header ?Permissions-Policy "browsing-topics=()" + + @phpRoute { + not path /.well-known/mercure* + not file {path} + } + rewrite @phpRoute index.php + + @frontController path index.php + php @frontController + + file_server { + hide *.php + } +} diff --git a/frankenphp/conf/caddy/worker.Caddyfile b/frankenphp/conf/caddy/worker.Caddyfile new file mode 100644 index 0000000..d384ae4 --- /dev/null +++ b/frankenphp/conf/caddy/worker.Caddyfile @@ -0,0 +1,4 @@ +worker { + file ./public/index.php + env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime +} diff --git a/frankenphp/conf/php/10-app.ini b/frankenphp/conf/php/10-app.ini new file mode 100644 index 0000000..79a17dd --- /dev/null +++ b/frankenphp/conf/php/10-app.ini @@ -0,0 +1,13 @@ +expose_php = 0 +date.timezone = UTC +apc.enable_cli = 1 +session.use_strict_mode = 1 +zend.detect_unicode = 0 + +; https://symfony.com/doc/current/performance.html +realpath_cache_size = 4096K +realpath_cache_ttl = 600 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 20000 +opcache.memory_consumption = 256 +opcache.enable_file_override = 1 diff --git a/frankenphp/conf/php/20-app.dev.ini b/frankenphp/conf/php/20-app.dev.ini new file mode 100644 index 0000000..e50f43d --- /dev/null +++ b/frankenphp/conf/php/20-app.dev.ini @@ -0,0 +1,5 @@ +; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host +; See https://github.com/docker/for-linux/issues/264 +; The `client_host` below may optionally be replaced with `discover_client_host=yes` +; Add `start_with_request=yes` to start debug session on each request +xdebug.client_host = host.docker.internal diff --git a/frankenphp/conf/php/20-app.prod.ini b/frankenphp/conf/php/20-app.prod.ini new file mode 100644 index 0000000..b716441 --- /dev/null +++ b/frankenphp/conf/php/20-app.prod.ini @@ -0,0 +1,5 @@ +; https://symfony.com/doc/current/performance.html#use-the-opcache-class-preloading +opcache.preload_user = root +opcache.preload = /app/config/preload.php +; https://symfony.com/doc/current/performance.html#don-t-check-php-files-timestamps +opcache.validate_timestamps = 0 diff --git a/frankenphp/docker-bake.hcl b/frankenphp/docker-bake.hcl new file mode 100644 index 0000000..638c00b --- /dev/null +++ b/frankenphp/docker-bake.hcl @@ -0,0 +1,66 @@ +variable "FRANKENPHP_VERSION" { + default = "1.4.2" +} + +variable "REPO_BASE" { + default = "druidfi/frankenphp" +} + +variable "PHP83_PATCH" {} +variable "PHP84_PATCH" {} + +group "default" { + targets = ["php-variants"] +} + +group "php-variants" { + targets = ["php-83", "php-84"] +} + +target "common" { + context = "./frankenphp" + platforms = ["linux/amd64", "linux/arm64"] + labels = { + "org.opencontainers.image.url" = "https://github.com/druidfi/docker-images" + "org.opencontainers.image.source" = "https://github.com/druidfi/docker-images" + "org.opencontainers.image.licenses" = "MIT" + "org.opencontainers.image.vendor" = "Druid Oy" + "org.opencontainers.image.created" = "${timestamp()}" + } +} + +# +# FRANKENPHP +# + +target "php-83" { + inherits = ["common"] + args = { + PHP_VERSION = "8.3" + PHP_SHORT_VERSION = "83" + } + contexts = { + frankenphp_upstream = "docker-image://dunglas/frankenphp:${FRANKENPHP_VERSION}-php${PHP83_PATCH}" + } + tags = [ + "${REPO_BASE}:${FRANKENPHP_VERSION}-php8.3", + "${REPO_BASE}:${FRANKENPHP_VERSION}-php${PHP83_PATCH}" + ] +} + +target "php-84" { + inherits = ["common"] + args = { + PHP_VERSION = "8.4" + PHP_SHORT_VERSION = "84" + } + contexts = { + frankenphp_upstream = "docker-image://dunglas/frankenphp:${FRANKENPHP_VERSION}-php${PHP84_PATCH}" + } + tags = [ + "${REPO_BASE}:${FRANKENPHP_VERSION}-php8", + "${REPO_BASE}:${FRANKENPHP_VERSION}-php8.4", + "${REPO_BASE}:${FRANKENPHP_VERSION}-php${PHP84_PATCH}", + "${REPO_BASE}:latest" + ] +} diff --git a/frankenphp/docker-entrypoint.dev.sh b/frankenphp/docker-entrypoint.dev.sh new file mode 100755 index 0000000..766e882 --- /dev/null +++ b/frankenphp/docker-entrypoint.dev.sh @@ -0,0 +1,42 @@ +#!/bin/sh +set -e + +if [ "$1" = 'conf' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then + if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then + composer install --prefer-dist --no-progress --no-interaction + fi + + # Display information about the current project + # Or about an error in project initialization + php bin/console -V + + if grep -q ^DATABASE_URL= .env; then + echo 'Waiting for database to be ready...' + ATTEMPTS_LEFT_TO_REACH_DATABASE=60 + until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do + if [ $? -eq 255 ]; then + # If the Doctrine command exits with 255, an unrecoverable error occurred + ATTEMPTS_LEFT_TO_REACH_DATABASE=0 + break + fi + sleep 1 + ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1)) + echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left." + done + + if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then + echo 'The database is not up or not reachable:' + echo "$DATABASE_ERROR" + exit 1 + else + echo 'The database is now ready and reachable' + fi + fi + + setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var + setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var + + echo 'PHP app development ready!' +fi + +exec docker-php-entrypoint "$@" diff --git a/frankenphp/docker-entrypoint.sh b/frankenphp/docker-entrypoint.sh new file mode 100755 index 0000000..4079ce0 --- /dev/null +++ b/frankenphp/docker-entrypoint.sh @@ -0,0 +1,46 @@ +#!/bin/sh +set -e + +if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then + if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then + composer install --prefer-dist --no-progress --no-interaction + fi + + # Display information about the current project + # Or about an error in project initialization + php bin/console -V + + if grep -q ^DATABASE_URL= .env; then + echo 'Waiting for database to be ready...' + ATTEMPTS_LEFT_TO_REACH_DATABASE=60 + until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do + if [ $? -eq 255 ]; then + # If the Doctrine command exits with 255, an unrecoverable error occurred + ATTEMPTS_LEFT_TO_REACH_DATABASE=0 + break + fi + sleep 1 + ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1)) + echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left." + done + + if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then + echo 'The database is not up or not reachable:' + echo "$DATABASE_ERROR" + exit 1 + else + echo 'The database is now ready and reachable' + fi + + if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then + php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing + fi + fi + + setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var + setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var + + echo 'PHP app ready!' +fi + +exec docker-php-entrypoint "$@" diff --git a/frankenphp/update.sh b/frankenphp/update.sh new file mode 100755 index 0000000..2cda0bd --- /dev/null +++ b/frankenphp/update.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# Update in some automation: +# sed -i '' "s/VERSION=.*/VERSION=1.2.3/g" update.sh +VERSION=1.0.0 +GITHUB=https://raw.githubusercontent.com +REPOSITORY=dunglas/symfony-docker +BRANCH=main +REPOSITORY_URL=https://github.com/${REPOSITORY} + +while true; do + case "$1" in + -b | --branch ) BRANCH="$2"; shift ;; + * ) break ;; + esac +done + +RED="[0;31m" +GREEN="[0;32m" +YELLOW="[0;33m" +NORMAL="[0m" + +declare -a files=( + ".dockerignore" + "Dockerfile" + "frankenphp/docker-entrypoint.sh" + "frankenphp/Caddyfile" + "frankenphp/worker.Caddyfile" + "frankenphp/conf.d/10-app.ini" + "frankenphp/conf.d/20-app.dev.ini" + "frankenphp/conf.d/20-app.prod.ini" +) + +declare -a targets=( + "frankenphp/.dockerignore" + "frankenphp/Dockerfile.dist" + "frankenphp/docker-entrypoint.dist.sh" + "frankenphp/conf/caddy/Caddyfile" + "frankenphp/conf/caddy/worker.Caddyfile" + "frankenphp/conf/php/10-app.ini" + "frankenphp/conf/php/20-app.dev.ini" + "frankenphp/conf/php/20-app.prod.ini" +) + +main() { + + printf "\n\e%s%s updater (version %s)\e%s\n\n" "${YELLOW}" "${REPOSITORY}" "${VERSION}" "${NORMAL}" + + info "Download following files from ${REPOSITORY_URL}:" + printf "\n" + printf '%s\n' "${files[@]}" + + for i in "${!files[@]}" + do + file=${files[i]} + timestamp=$(date +%s) + urls[${i}]="${GITHUB}/${REPOSITORY}/${BRANCH}/${file}?t=${timestamp}" + done + + for i in "${!files[@]}" + do + curl -LJs -o "${targets[i]}" "${urls[i]}" + done + + if [[ $? -eq 0 ]] + then + printf "\n\e%s[OK]\e%s Update complete!\e%s\n" "${GREEN}" "${YELLOW}" "${NORMAL}" + printf "\n" + info "Use git diff or your IDE diff tools to see the changes" + exit 0 + else + printf "\n\e%s[ERROR]\e%s Check if update.sh has correct settings\n" "${RED}" "${NORMAL}" + exit 1 + fi +} + +info() { + printf "\e%s[Info]\e%s %s\n" "${YELLOW}" "${NORMAL}" "${1}" +} + +main diff --git a/make/build_targets.mk b/make/build_targets.mk index 0efb9a4..9469ceb 100644 --- a/make/build_targets.mk +++ b/make/build_targets.mk @@ -1,5 +1,6 @@ BUILD_TARGETS := +include $(PROJECT_DIR)/frankenphp/build.mk include $(PROJECT_DIR)/php/build.mk include $(PROJECT_DIR)/nginx/build.mk include $(PROJECT_DIR)/db/build.mk diff --git a/tests/scripts/tests_symfony.sh b/tests/scripts/tests_symfony.sh index 74f57be..bac176c 100755 --- a/tests/scripts/tests_symfony.sh +++ b/tests/scripts/tests_symfony.sh @@ -1,5 +1,8 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash -php -v +. "$(dirname "$0")/utils.sh" +. "$(dirname "$0")/php.sh" + +echo -e "\n\e[30;42mAll tests passed!\e[49m\n" exit 0