28 - Adopt dunglas/symfony-docker base

This commit is contained in:
myrmidex 2026-06-15 00:14:42 +02:00
parent 4964172ae9
commit 9e2f056fff
11 changed files with 461 additions and 1 deletions

34
.dockerignore Normal file
View file

@ -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

178
Dockerfile Normal file
View file

@ -0,0 +1,178 @@
#syntax=docker/dockerfile:1
# Versions
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
# The different stages of this Dockerfile are meant to be built into separate images
# https://docs.docker.com/build/building/multi-stage/#stop-at-a-specific-build-stage
# https://docs.docker.com/reference/compose-file/build/#target
# Base FrankenPHP image
FROM frankenphp_upstream AS frankenphp_base
SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
WORKDIR /app
# persistent deps
# hadolint ignore=DL3008
RUN <<-EOF
apt-get update
apt-get install -y --no-install-recommends \
file \
git
install-php-extensions \
@composer \
apcu \
intl \
opcache \
zip
rm -rf /var/lib/apt/lists/*
EOF
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
ENV COMPOSER_ALLOW_SUPERUSER=1
ENV PHP_INI_SCAN_DIR=":$PHP_INI_DIR/app.conf.d"
###> recipes ###
###< recipes ###
COPY --link frankenphp/conf.d/10-app.ini $PHP_INI_DIR/app.conf.d/
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
COPY --link frankenphp/Caddyfile /etc/frankenphp/Caddyfile
ENTRYPOINT ["docker-entrypoint"]
HEALTHCHECK --start-period=60s CMD php -r 'exit(false === @file_get_contents("http://localhost:2019/metrics", context: stream_context_create(["http" => ["timeout" => 5]])) ? 1 : 0);'
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ]
# Dev FrankenPHP image
FROM frankenphp_base AS frankenphp_dev
ENV APP_ENV=dev
ENV XDEBUG_MODE=off
ENV FRANKENPHP_WORKER_CONFIG=watch
# dev dependencies
# hadolint ignore=DL3008
RUN <<-EOF
mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
apt-get update
apt-get install -y --no-install-recommends \
aggregate \
curl \
dnsmasq \
dnsutils \
iproute2 \
ipset \
iptables \
jq \
sudo
install-php-extensions xdebug
rm -rf /var/lib/apt/lists/*
useradd -m -s /bin/bash nonroot
echo "nonroot ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/nonroot
git config --system --add safe.directory /app
EOF
COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--watch" ]
# Builder for the prod FrankenPHP image
FROM frankenphp_base AS frankenphp_prod_builder
ENV APP_ENV=prod
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/
# prevent the reinstallation of vendors at every changes in the source code
COPY --link composer.* symfony.* ./
RUN composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress
# copy sources
COPY --link --exclude=frankenphp/ . ./
RUN <<-EOF
mkdir -p var/cache var/log var/share
composer dump-autoload --classmap-authoritative --no-dev
composer dump-env prod
composer run-script --no-dev post-install-cmd
if [ -f importmap.php ]; then
php bin/console asset-map:compile
fi
chmod +x bin/console
chmod -R g=u var
sync
EOF
# Collect shared libraries needed by FrankenPHP and PHP extensions
# hadolint ignore=DL3008,SC3054,DL4006
RUN <<-'EOF'
apt-get update
apt-get install -y --no-install-recommends libtree
mkdir -p /tmp/libs
BINARIES=(frankenphp php file)
for target in $(printf '%s\n' "${BINARIES[@]}" | xargs -I{} which {}) \
$(find "$(php -r 'echo ini_get("extension_dir");')" -maxdepth 2 -name "*.so"); do
libtree -pv "$target" 2>/dev/null | grep -oP '(?:── )\K/\S+(?= \[)' | while IFS= read -r lib; do
[ -f "$lib" ] && cp -n "$lib" /tmp/libs/
done
done
rm -rf /var/lib/apt/lists/*
EOF
# Prod FrankenPHP image
FROM debian:13-slim AS frankenphp_prod
SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
ENV APP_ENV=prod
ENV PHP_INI_SCAN_DIR=":/usr/local/etc/php/app.conf.d"
COPY --from=frankenphp_prod_builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
COPY --from=frankenphp_prod_builder /usr/local/bin/php /usr/local/bin/php
COPY --from=frankenphp_prod_builder /usr/local/bin/docker-php-entrypoint /usr/local/bin/docker-php-entrypoint
COPY --from=frankenphp_prod_builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=frankenphp_prod_builder /tmp/libs /usr/lib
COPY --from=frankenphp_prod_builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=frankenphp_prod_builder /usr/local/etc/php/php.ini /usr/local/etc/php/php.ini
COPY --from=frankenphp_prod_builder /usr/local/etc/php/app.conf.d /usr/local/etc/php/app.conf.d
COPY --from=frankenphp_prod_builder /etc/frankenphp/Caddyfile /etc/frankenphp/Caddyfile
# CA certificates for TLS, file/libmagic for Symfony MIME type detection
COPY --from=frankenphp_prod_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=frankenphp_prod_builder /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf
COPY --from=frankenphp_prod_builder /usr/bin/file /usr/bin/file
COPY --from=frankenphp_prod_builder /usr/lib/file/magic.mgc /usr/lib/file/magic.mgc
ENV OPENSSL_CONF=/etc/ssl/openssl.cnf XDG_CONFIG_HOME=/config XDG_DATA_HOME=/data
RUN <<-EOF
mkdir -p /data/caddy /config/caddy
chown -R www-data:www-data /data /config
# Remove setuid/setgid bits
find / -perm /6000 -type f -exec chmod a-s {} + 2>/dev/null || true
EOF
COPY --link --exclude=var --from=frankenphp_prod_builder /app /app
# Group 0 + g=u for arbitrary-UID runtimes (e.g. OpenShift).
COPY --chown=www-data:0 --from=frankenphp_prod_builder /app/var /app/var
RUN chmod g=u /app/var
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
USER www-data
WORKDIR /app
ENTRYPOINT ["docker-entrypoint"]
HEALTHCHECK --start-period=60s CMD php -r 'exit(false === @file_get_contents("http://localhost:2019/metrics", context: stream_context_create(["http" => ["timeout" => 5]])) ? 1 : 0);'
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ]

30
compose.override.yaml Normal file
View file

@ -0,0 +1,30 @@
---
# Development environment override
services:
php:
image: ${IMAGES_PREFIX:-}app-php-dev
build:
context: .
target: frankenphp_dev
volumes:
- ./:/app
- ./frankenphp/Caddyfile:/etc/frankenphp/Caddyfile:ro
- ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro
# Keep var/ off the bind-mount for faster I/O on Mac/Windows; comment to inspect from the host.
- /app/var
# If you develop on Mac or Windows you can remove the vendor/ directory
# from the bind-mount for better performance by enabling the next line:
#- /app/vendor
environment:
FRANKENPHP_WORKER_CONFIG: watch
FRANKENPHP_SITE_CONFIG: hot_reload
MERCURE_EXTRA_DIRECTIVES: demo
# See https://xdebug.org/docs/all_settings#mode
XDEBUG_MODE: "${XDEBUG_MODE:-develop}"
APP_ENV: "${APP_ENV:-dev}"
extra_hosts:
# Ensure that host.docker.internal is correctly defined on Linux
- host.docker.internal:host-gateway
tty: true
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###

12
compose.prod.yaml Normal file
View file

@ -0,0 +1,12 @@
---
# Production environment override
services:
php:
image: ${IMAGES_PREFIX:-}app-php-prod
build:
context: .
target: frankenphp_prod
environment:
APP_SECRET: ${APP_SECRET}
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}

47
compose.yaml Normal file
View file

@ -0,0 +1,47 @@
---
services:
php:
restart: unless-stopped
environment:
SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
DEFAULT_URI: https://${SERVER_NAME:-localhost}:${HTTPS_PORT:-443}
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
# Run "composer require symfony/orm-pack" to install and configure Doctrine ORM
DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}&charset=${POSTGRES_CHARSET:-utf8}
# Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration
MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
MERCURE_PUBLIC_URL: ${CADDY_MERCURE_PUBLIC_URL:-https://${SERVER_NAME:-localhost}:${HTTPS_PORT:-443}/.well-known/mercure}
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
###> dunglas/symfony-docker ###
# Next lines are only used during initial installation
# This block will remove itself after the installation
SYMFONY_VERSION: ${SYMFONY_VERSION:-}
STABILITY: ${STABILITY:-stable}
###< dunglas/symfony-docker ###
volumes:
- caddy_data:/data
- caddy_config:/config
ports:
- name: http
target: 80
published: ${HTTP_PORT:-80}
protocol: tcp
- name: https
target: 443
published: ${HTTPS_PORT:-443}
protocol: tcp
- name: http3
target: 443
published: ${HTTP3_PORT:-443}
protocol: udp
# Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###
volumes:
caddy_data:
caddy_config:
###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###

View file

@ -79,7 +79,8 @@
"extra": {
"symfony": {
"allow-contrib": false,
"require": "8.1.*"
"require": "8.1.*",
"docker": true
}
}
}

66
frankenphp/Caddyfile Normal file
View file

@ -0,0 +1,66 @@
{
skip_install_trust
{$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 {
# 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 {
{$FRANKENPHP_SITE_CONFIG}
worker {
file ./public/index.php
{$FRANKENPHP_WORKER_CONFIG}
}
}
file_server {
hide *.php
}
}

View file

@ -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 = 32531
opcache.memory_consumption = 256
opcache.enable_file_override = 1

View file

@ -0,0 +1,5 @@
; See https://docs.docker.com/desktop/features/networking/networking-how-tos/#connect-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

View file

@ -0,0 +1,5 @@
; https://symfony.com/doc/current/performance.html#use-the-opcache-class-preloading
opcache.preload_user = www-data
opcache.preload = /app/config/preload.php
; https://symfony.com/doc/current/performance.html#don-t-check-php-files-timestamps
opcache.validate_timestamps = 0

View file

@ -0,0 +1,69 @@
#!/bin/sh
set -e
if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
###> dunglas/symfony-docker ###
# Install the project the first time PHP is started
# This block will remove itself after the installation
if [ "$(cat composer.json)" = '{}' ]; then
rm -Rf tmp/
composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install
cd tmp
cp -Rp . ..
cd -
rm -Rf tmp/
composer require "php:>=$PHP_VERSION"
composer config --json extra.symfony.docker 'true'
# Remove the project install block from this script and the compose.yaml
sed -i '/^\t###> dunglas\/symfony-docker ###/,/^\t###< dunglas\/symfony-docker ###/d' frankenphp/docker-entrypoint.sh
sed -i '/###> dunglas\/symfony-docker ###/,/###< dunglas\/symfony-docker ###/d' compose.yaml
if grep -q ^DATABASE_URL= .env; then
echo 'To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build --wait'
sleep infinity
fi
fi
###< dunglas/symfony-docker ###
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
echo 'PHP app ready!'
fi
exec docker-php-entrypoint "$@"