From 8de031d70f76657d95e2c0f72bd91eeca461ab09 Mon Sep 17 00:00:00 2001 From: David Hollis Date: Mon, 11 Nov 2024 08:12:03 -0500 Subject: [PATCH 1/4] Update docker bits to symfony-docker --- .dockerignore | 34 ++++++++++ Dockerfile | 106 +++++++++++++++++++++++++----- compose.override.yaml | 30 +++++++++ compose.prod.yaml | 10 +++ compose.yaml | 61 +++++++++++++++++ composer.json | 3 +- frankenphp/Caddyfile | 47 +++++++++++++ frankenphp/conf.d/10-app.ini | 13 ++++ frankenphp/conf.d/20-app.dev.ini | 5 ++ frankenphp/conf.d/20-app.prod.ini | 2 + frankenphp/docker-entrypoint.sh | 60 +++++++++++++++++ frankenphp/worker.Caddyfile | 4 ++ 12 files changed, 356 insertions(+), 19 deletions(-) create mode 100644 .dockerignore create mode 100644 compose.override.yaml create mode 100644 compose.prod.yaml create mode 100644 compose.yaml create mode 100644 frankenphp/Caddyfile create mode 100644 frankenphp/conf.d/10-app.ini create mode 100644 frankenphp/conf.d/20-app.dev.ini create mode 100644 frankenphp/conf.d/20-app.prod.ini create mode 100755 frankenphp/docker-entrypoint.sh create mode 100644 frankenphp/worker.Caddyfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dc5a875 --- /dev/null +++ b/.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/Dockerfile b/Dockerfile index 81b585c..34bd295 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,98 @@ -FROM php:8.1-cli -LABEL maintainer Matthias Straka +#syntax=docker/dockerfile:1 -RUN apt-get update -y && \ - apt-get install -y --no-install-recommends unzip npm +# Versions +FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream -RUN pecl install apcu -RUN docker-php-ext-enable apcu -RUN docker-php-ext-install bcmath +# The different stages of this Dockerfile are meant to be built into separate images +# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage +# https://docs.docker.com/compose/compose-file/#target -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# Base FrankenPHP image +FROM frankenphp_upstream AS frankenphp_base WORKDIR /app -COPY . /app -RUN composer install --no-dev -RUN php bin/console doctrine:schema:create -#RUN php bin/console doctrine:fixtures:load -n --group=seeder +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 \ + bcmath \ + intl \ + opcache \ + zip \ + ; + +# 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 ### +###> doctrine/doctrine-bundle ### +RUN install-php-extensions pdo_pgsql +###< doctrine/doctrine-bundle ### +###< 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/caddy/Caddyfile + +ENTRYPOINT ["docker-entrypoint"] + +HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1 +CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ] + +# Dev FrankenPHP image +FROM frankenphp_base AS frankenphp_dev + +ENV APP_ENV=dev XDEBUG_MODE=off + +RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" + +RUN set -eux; \ + install-php-extensions \ + xdebug \ + ; + +COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/ + +CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ] + +# Prod FrankenPHP image +FROM frankenphp_base AS frankenphp_prod + +ENV APP_ENV=prod +ENV FRANKENPHP_CONFIG="import worker.Caddyfile" + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" -VOLUME [ "/app/var" ] +COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/ +COPY --link frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile -# Setup up local environment with dummy secret -RUN echo "APP_SECRET='DockerSecret'" > /app/.env.local +# prevent the reinstallation of vendors at every changes in the source code +COPY --link composer.* symfony.* ./ +RUN set -eux; \ + composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress -EXPOSE 8000 +# copy sources +COPY --link . ./ +RUN rm -Rf frankenphp/ -WORKDIR /app/public -CMD php -S 0.0.0.0:8000 +RUN set -eux; \ + mkdir -p var/cache var/log; \ + composer dump-autoload --classmap-authoritative --no-dev; \ + composer dump-env prod; \ + composer run-script --no-dev post-install-cmd; \ + chmod +x bin/console; sync; diff --git a/compose.override.yaml b/compose.override.yaml new file mode 100644 index 0000000..22e80a5 --- /dev/null +++ b/compose.override.yaml @@ -0,0 +1,30 @@ +# Development environment override +services: + php: + build: + context: . + target: frankenphp_dev + volumes: + - ./:/app + - ./frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro + - ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro + # 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: + MERCURE_EXTRA_DIRECTIVES: demo + # See https://xdebug.org/docs/all_settings#mode + XDEBUG_MODE: "${XDEBUG_MODE:-off}" + 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 ### + +###> doctrine/doctrine-bundle ### + database: + ports: + - "5432" +###< doctrine/doctrine-bundle ### diff --git a/compose.prod.yaml b/compose.prod.yaml new file mode 100644 index 0000000..f0db05d --- /dev/null +++ b/compose.prod.yaml @@ -0,0 +1,10 @@ +# Production environment override +services: + php: + 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} diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..68a5a80 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,61 @@ +services: + php: + image: ${IMAGES_PREFIX:-}app-php + restart: unless-stopped + environment: + SERVER_NAME: ${SERVER_NAME:-localhost}, php:80 + 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}/.well-known/mercure} + MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} + # The two next lines can be removed after initial installation + SYMFONY_VERSION: ${SYMFONY_VERSION:-} + STABILITY: ${STABILITY:-stable} + volumes: + - caddy_data:/data + - caddy_config:/config + ports: + # HTTP + - target: 80 + published: ${HTTP_PORT:-80} + protocol: tcp + # HTTPS + - target: 443 + published: ${HTTPS_PORT:-443} + protocol: tcp + # HTTP/3 + - 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 ### + +###> doctrine/doctrine-bundle ### + database: + image: postgres:${POSTGRES_VERSION:-16}-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB:-app} + # You should definitely change the password in production + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} + POSTGRES_USER: ${POSTGRES_USER:-app} + volumes: + - database_data:/var/lib/postgresql/data:rw + # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! + # - ./docker/db/data:/var/lib/postgresql/data:rw +###< doctrine/doctrine-bundle ### + +volumes: + caddy_data: + caddy_config: +###> symfony/mercure-bundle ### +###< symfony/mercure-bundle ### + +###> doctrine/doctrine-bundle ### + database_data: +###< doctrine/doctrine-bundle ### diff --git a/composer.json b/composer.json index ce5dec7..492b7cf 100644 --- a/composer.json +++ b/composer.json @@ -99,7 +99,8 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "^6.4" + "require": "^6.4", + "docker": true } } } diff --git a/frankenphp/Caddyfile b/frankenphp/Caddyfile new file mode 100644 index 0000000..b8bb57d --- /dev/null +++ b/frankenphp/Caddyfile @@ -0,0 +1,47 @@ +{ + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + {$FRANKENPHP_CONFIG} + } +} + +{$CADDY_EXTRA_CONFIG} + +{$SERVER_NAME:localhost} { + log { + # 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=()" + + php_server +} diff --git a/frankenphp/conf.d/10-app.ini b/frankenphp/conf.d/10-app.ini new file mode 100644 index 0000000..79a17dd --- /dev/null +++ b/frankenphp/conf.d/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.d/20-app.dev.ini b/frankenphp/conf.d/20-app.dev.ini new file mode 100644 index 0000000..e50f43d --- /dev/null +++ b/frankenphp/conf.d/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.d/20-app.prod.ini b/frankenphp/conf.d/20-app.prod.ini new file mode 100644 index 0000000..3bcaa71 --- /dev/null +++ b/frankenphp/conf.d/20-app.prod.ini @@ -0,0 +1,2 @@ +opcache.preload_user = root +opcache.preload = /app/config/preload.php diff --git a/frankenphp/docker-entrypoint.sh b/frankenphp/docker-entrypoint.sh new file mode 100755 index 0000000..9823560 --- /dev/null +++ b/frankenphp/docker-entrypoint.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -e + +if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then + # Install the project the first time PHP is started + # After the installation, the following block can be deleted + if [ ! -f 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" runtime/frankenphp-symfony + composer config --json extra.symfony.docker 'true' + + 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 -d --wait" + sleep infinity + fi + fi + + if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then + composer install --prefer-dist --no-progress --no-interaction + fi + + 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 +fi + +exec docker-php-entrypoint "$@" diff --git a/frankenphp/worker.Caddyfile b/frankenphp/worker.Caddyfile new file mode 100644 index 0000000..d384ae4 --- /dev/null +++ b/frankenphp/worker.Caddyfile @@ -0,0 +1,4 @@ +worker { + file ./public/index.php + env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime +} From 6a6b1f06f4487a5349e889dd9c816ec715e3837d Mon Sep 17 00:00:00 2001 From: David Hollis Date: Mon, 11 Nov 2024 20:12:09 -0500 Subject: [PATCH 2/4] Set the user table name to avoid reserved word on some DB platforms --- src/Entity/User.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Entity/User.php b/src/Entity/User.php index db8b103..c876404 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -10,6 +10,7 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: UserRepository::class)] +#[ORM\Table(name: 'users')] #[UniqueEntity(fields: ["username"], message: "There is already an account with this username")] class User implements UserInterface, PasswordAuthenticatedUserInterface { From 20044f14fc27d66d683772cf7325e26d2f354244 Mon Sep 17 00:00:00 2001 From: David Hollis Date: Mon, 11 Nov 2024 08:12:03 -0500 Subject: [PATCH 3/4] Update docker bits to symfony-docker --- .dockerignore | 34 ++++++++++ Dockerfile | 106 +++++++++++++++++++++++++----- compose.override.yaml | 30 +++++++++ compose.prod.yaml | 10 +++ compose.yaml | 61 +++++++++++++++++ composer.json | 3 +- frankenphp/Caddyfile | 47 +++++++++++++ frankenphp/conf.d/10-app.ini | 13 ++++ frankenphp/conf.d/20-app.dev.ini | 5 ++ frankenphp/conf.d/20-app.prod.ini | 2 + frankenphp/docker-entrypoint.sh | 60 +++++++++++++++++ frankenphp/worker.Caddyfile | 4 ++ 12 files changed, 356 insertions(+), 19 deletions(-) create mode 100644 .dockerignore create mode 100644 compose.override.yaml create mode 100644 compose.prod.yaml create mode 100644 compose.yaml create mode 100644 frankenphp/Caddyfile create mode 100644 frankenphp/conf.d/10-app.ini create mode 100644 frankenphp/conf.d/20-app.dev.ini create mode 100644 frankenphp/conf.d/20-app.prod.ini create mode 100755 frankenphp/docker-entrypoint.sh create mode 100644 frankenphp/worker.Caddyfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dc5a875 --- /dev/null +++ b/.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/Dockerfile b/Dockerfile index 81b585c..34bd295 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,98 @@ -FROM php:8.1-cli -LABEL maintainer Matthias Straka +#syntax=docker/dockerfile:1 -RUN apt-get update -y && \ - apt-get install -y --no-install-recommends unzip npm +# Versions +FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream -RUN pecl install apcu -RUN docker-php-ext-enable apcu -RUN docker-php-ext-install bcmath +# The different stages of this Dockerfile are meant to be built into separate images +# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage +# https://docs.docker.com/compose/compose-file/#target -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# Base FrankenPHP image +FROM frankenphp_upstream AS frankenphp_base WORKDIR /app -COPY . /app -RUN composer install --no-dev -RUN php bin/console doctrine:schema:create -#RUN php bin/console doctrine:fixtures:load -n --group=seeder +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 \ + bcmath \ + intl \ + opcache \ + zip \ + ; + +# 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 ### +###> doctrine/doctrine-bundle ### +RUN install-php-extensions pdo_pgsql +###< doctrine/doctrine-bundle ### +###< 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/caddy/Caddyfile + +ENTRYPOINT ["docker-entrypoint"] + +HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1 +CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ] + +# Dev FrankenPHP image +FROM frankenphp_base AS frankenphp_dev + +ENV APP_ENV=dev XDEBUG_MODE=off + +RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" + +RUN set -eux; \ + install-php-extensions \ + xdebug \ + ; + +COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/ + +CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ] + +# Prod FrankenPHP image +FROM frankenphp_base AS frankenphp_prod + +ENV APP_ENV=prod +ENV FRANKENPHP_CONFIG="import worker.Caddyfile" + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" -VOLUME [ "/app/var" ] +COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/ +COPY --link frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile -# Setup up local environment with dummy secret -RUN echo "APP_SECRET='DockerSecret'" > /app/.env.local +# prevent the reinstallation of vendors at every changes in the source code +COPY --link composer.* symfony.* ./ +RUN set -eux; \ + composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress -EXPOSE 8000 +# copy sources +COPY --link . ./ +RUN rm -Rf frankenphp/ -WORKDIR /app/public -CMD php -S 0.0.0.0:8000 +RUN set -eux; \ + mkdir -p var/cache var/log; \ + composer dump-autoload --classmap-authoritative --no-dev; \ + composer dump-env prod; \ + composer run-script --no-dev post-install-cmd; \ + chmod +x bin/console; sync; diff --git a/compose.override.yaml b/compose.override.yaml new file mode 100644 index 0000000..22e80a5 --- /dev/null +++ b/compose.override.yaml @@ -0,0 +1,30 @@ +# Development environment override +services: + php: + build: + context: . + target: frankenphp_dev + volumes: + - ./:/app + - ./frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro + - ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro + # 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: + MERCURE_EXTRA_DIRECTIVES: demo + # See https://xdebug.org/docs/all_settings#mode + XDEBUG_MODE: "${XDEBUG_MODE:-off}" + 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 ### + +###> doctrine/doctrine-bundle ### + database: + ports: + - "5432" +###< doctrine/doctrine-bundle ### diff --git a/compose.prod.yaml b/compose.prod.yaml new file mode 100644 index 0000000..f0db05d --- /dev/null +++ b/compose.prod.yaml @@ -0,0 +1,10 @@ +# Production environment override +services: + php: + 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} diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..68a5a80 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,61 @@ +services: + php: + image: ${IMAGES_PREFIX:-}app-php + restart: unless-stopped + environment: + SERVER_NAME: ${SERVER_NAME:-localhost}, php:80 + 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}/.well-known/mercure} + MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} + # The two next lines can be removed after initial installation + SYMFONY_VERSION: ${SYMFONY_VERSION:-} + STABILITY: ${STABILITY:-stable} + volumes: + - caddy_data:/data + - caddy_config:/config + ports: + # HTTP + - target: 80 + published: ${HTTP_PORT:-80} + protocol: tcp + # HTTPS + - target: 443 + published: ${HTTPS_PORT:-443} + protocol: tcp + # HTTP/3 + - 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 ### + +###> doctrine/doctrine-bundle ### + database: + image: postgres:${POSTGRES_VERSION:-16}-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB:-app} + # You should definitely change the password in production + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} + POSTGRES_USER: ${POSTGRES_USER:-app} + volumes: + - database_data:/var/lib/postgresql/data:rw + # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! + # - ./docker/db/data:/var/lib/postgresql/data:rw +###< doctrine/doctrine-bundle ### + +volumes: + caddy_data: + caddy_config: +###> symfony/mercure-bundle ### +###< symfony/mercure-bundle ### + +###> doctrine/doctrine-bundle ### + database_data: +###< doctrine/doctrine-bundle ### diff --git a/composer.json b/composer.json index ce5dec7..492b7cf 100644 --- a/composer.json +++ b/composer.json @@ -99,7 +99,8 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "^6.4" + "require": "^6.4", + "docker": true } } } diff --git a/frankenphp/Caddyfile b/frankenphp/Caddyfile new file mode 100644 index 0000000..b8bb57d --- /dev/null +++ b/frankenphp/Caddyfile @@ -0,0 +1,47 @@ +{ + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + {$FRANKENPHP_CONFIG} + } +} + +{$CADDY_EXTRA_CONFIG} + +{$SERVER_NAME:localhost} { + log { + # 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=()" + + php_server +} diff --git a/frankenphp/conf.d/10-app.ini b/frankenphp/conf.d/10-app.ini new file mode 100644 index 0000000..79a17dd --- /dev/null +++ b/frankenphp/conf.d/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.d/20-app.dev.ini b/frankenphp/conf.d/20-app.dev.ini new file mode 100644 index 0000000..e50f43d --- /dev/null +++ b/frankenphp/conf.d/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.d/20-app.prod.ini b/frankenphp/conf.d/20-app.prod.ini new file mode 100644 index 0000000..3bcaa71 --- /dev/null +++ b/frankenphp/conf.d/20-app.prod.ini @@ -0,0 +1,2 @@ +opcache.preload_user = root +opcache.preload = /app/config/preload.php diff --git a/frankenphp/docker-entrypoint.sh b/frankenphp/docker-entrypoint.sh new file mode 100755 index 0000000..9823560 --- /dev/null +++ b/frankenphp/docker-entrypoint.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -e + +if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then + # Install the project the first time PHP is started + # After the installation, the following block can be deleted + if [ ! -f 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" runtime/frankenphp-symfony + composer config --json extra.symfony.docker 'true' + + 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 -d --wait" + sleep infinity + fi + fi + + if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then + composer install --prefer-dist --no-progress --no-interaction + fi + + 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 +fi + +exec docker-php-entrypoint "$@" diff --git a/frankenphp/worker.Caddyfile b/frankenphp/worker.Caddyfile new file mode 100644 index 0000000..d384ae4 --- /dev/null +++ b/frankenphp/worker.Caddyfile @@ -0,0 +1,4 @@ +worker { + file ./public/index.php + env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime +} From 6026a5d0da1a30db91f6c8fbe4af3e6c1a0c570a Mon Sep 17 00:00:00 2001 From: David Hollis Date: Mon, 11 Nov 2024 20:12:09 -0500 Subject: [PATCH 4/4] Set the user table name to avoid reserved word on some DB platforms --- src/Entity/User.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Entity/User.php b/src/Entity/User.php index db8b103..c876404 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -10,6 +10,7 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: UserRepository::class)] +#[ORM\Table(name: 'users')] #[UniqueEntity(fields: ["username"], message: "There is already an account with this username")] class User implements UserInterface, PasswordAuthenticatedUserInterface {