feat: switch to FrankenPHP (#460)

This commit is contained in:
Kévin Dunglas 2023-09-20 16:53:05 +02:00 committed by GitHub
parent b5710da39c
commit 88f5c19cac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 93 additions and 148 deletions

View File

@ -1,10 +1,8 @@
#syntax=docker/dockerfile:1.4 #syntax=docker/dockerfile:1.4
# Versions # Versions
FROM php:8.2-fpm-alpine AS php_upstream FROM dunglas/frankenphp:latest-alpine AS frankenphp_upstream
FROM mlocati/php-extension-installer:2 AS php_extension_installer_upstream
FROM composer/composer:2-bin AS composer_upstream FROM composer/composer:2-bin AS composer_upstream
FROM caddy:2-alpine AS caddy_upstream
# The different stages of this Dockerfile are meant to be built into separate images # The different stages of this Dockerfile are meant to be built into separate images
@ -12,10 +10,10 @@ FROM caddy:2-alpine AS caddy_upstream
# https://docs.docker.com/compose/compose-file/#target # https://docs.docker.com/compose/compose-file/#target
# Base PHP image # Base FrankenPHP image
FROM php_upstream AS php_base FROM frankenphp_upstream AS frankenphp_base
WORKDIR /srv/app WORKDIR /app
# persistent / runtime deps # persistent / runtime deps
# hadolint ignore=DL3018 # hadolint ignore=DL3018
@ -27,9 +25,6 @@ RUN apk add --no-cache \
git \ git \
; ;
# php extensions installer: https://github.com/mlocati/docker-php-extension-installer
COPY --from=php_extension_installer_upstream --link /usr/bin/install-php-extensions /usr/local/bin/
RUN set -eux; \ RUN set -eux; \
install-php-extensions \ install-php-extensions \
apcu \ apcu \
@ -41,17 +36,12 @@ RUN set -eux; \
###> recipes ### ###> recipes ###
###< recipes ### ###< recipes ###
COPY --link docker/php/conf.d/app.ini $PHP_INI_DIR/conf.d/ COPY --link frankenphp/conf.d/app.ini $PHP_INI_DIR/conf.d/
COPY --link frankenphp/conf.d/app.ini $PHP_INI_DIR/conf.d/
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
COPY --link frankenphp/Caddyfile /etc/caddy/Caddyfile
COPY --link docker/php/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf
RUN mkdir -p /var/run/php
COPY --link --chmod=755 docker/php/docker-healthcheck.sh /usr/local/bin/docker-healthcheck
HEALTHCHECK --start-period=1m CMD docker-healthcheck
COPY --link --chmod=755 docker/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
ENTRYPOINT ["docker-entrypoint"] ENTRYPOINT ["docker-entrypoint"]
CMD ["php-fpm"]
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser # https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
ENV COMPOSER_ALLOW_SUPERUSER=1 ENV COMPOSER_ALLOW_SUPERUSER=1
@ -59,12 +49,14 @@ ENV PATH="${PATH}:/root/.composer/vendor/bin"
COPY --from=composer_upstream --link /composer /usr/bin/composer COPY --from=composer_upstream --link /composer /usr/bin/composer
HEALTHCHECK CMD wget --no-verbose --tries=1 --spider http://localhost:2019/metrics || exit 1
CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ]
# Dev PHP image # Dev FrankenPHP image
FROM php_base AS php_dev FROM frankenphp_base AS frankenphp_dev
ENV APP_ENV=dev XDEBUG_MODE=off ENV APP_ENV=dev XDEBUG_MODE=off
VOLUME /srv/app/var/ VOLUME /app/var/
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
@ -73,15 +65,20 @@ RUN set -eux; \
xdebug \ xdebug \
; ;
COPY --link docker/php/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/ COPY --link frankenphp/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/
# Prod PHP image CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ]
FROM php_base AS php_prod
# Prod FrankenPHP image
FROM frankenphp_base AS frankenphp_prod
ENV APP_ENV=prod ENV APP_ENV=prod
ENV FRANKENPHP_CONFIG="import worker.Caddyfile"
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --link docker/php/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/
COPY --link frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/
COPY --link frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile
# prevent the reinstallation of vendors at every changes in the source code # prevent the reinstallation of vendors at every changes in the source code
COPY --link composer.* symfony.* ./ COPY --link composer.* symfony.* ./
@ -90,7 +87,7 @@ RUN set -eux; \
# copy sources # copy sources
COPY --link . ./ COPY --link . ./
RUN rm -Rf docker/ RUN rm -Rf frankenphp/
RUN set -eux; \ RUN set -eux; \
mkdir -p var/cache var/log; \ mkdir -p var/cache var/log; \
@ -98,22 +95,3 @@ RUN set -eux; \
composer dump-env prod; \ composer dump-env prod; \
composer run-script --no-dev post-install-cmd; \ composer run-script --no-dev post-install-cmd; \
chmod +x bin/console; sync; chmod +x bin/console; sync;
# Base Caddy image
FROM caddy_upstream AS caddy_base
ARG TARGETARCH
WORKDIR /srv/app
# Download Caddy compiled with the Mercure and Vulcain modules
ADD --chmod=500 https://caddyserver.com/api/download?os=linux&arch=$TARGETARCH&p=github.com/dunglas/mercure/caddy&p=github.com/dunglas/vulcain/caddy /usr/bin/caddy
COPY --link docker/caddy/Caddyfile /etc/caddy/Caddyfile
HEALTHCHECK CMD wget --no-verbose --tries=1 --spider http://localhost:2019/metrics || exit 1
# Prod Caddy image
FROM caddy_base AS caddy_prod
COPY --from=php_prod --link /srv/app/public public/

View File

@ -1,6 +1,7 @@
# Symfony Docker # Symfony Docker
A [Docker](https://www.docker.com/)-based installer and runtime for the [Symfony](https://symfony.com) web framework, with full [HTTP/2](https://symfony.com/doc/current/weblink.html), HTTP/3 and HTTPS support. A [Docker](https://www.docker.com/)-based installer and runtime for the [Symfony](https://symfony.com) web framework,
with [FrankenPHP](https://frankenphp.dev) and [Caddy](https://caddyserver.com/) inside!
![CI](https://github.com/dunglas/symfony-docker/workflows/CI/badge.svg) ![CI](https://github.com/dunglas/symfony-docker/workflows/CI/badge.svg)
@ -15,13 +16,14 @@ A [Docker](https://www.docker.com/)-based installer and runtime for the [Symfony
## Features ## Features
* Production, development and CI ready * Production, development and CI ready
* Just 1 service by default
* Blazing-fast performance thanks to [the worker mode of FrankenPHP](https://github.com/dunglas/frankenphp/blob/main/docs/worker.md) (automatically enabled in prod mode)
* [Installation of extra Docker Compose services](docs/extra-services.md) with Symfony Flex * [Installation of extra Docker Compose services](docs/extra-services.md) with Symfony Flex
* Automatic HTTPS (in dev and in prod!) * Automatic HTTPS (in dev and prod)
* HTTP/2, HTTP/3 and [Preload](https://symfony.com/doc/current/web_link.html) support * HTTP/3 and [Early Hints](https://symfony.com/blog/new-in-symfony-6-3-early-hints) support
* Built-in [Mercure](https://symfony.com/doc/current/mercure.html) hub * Real-time messaging thanks to a built-in [Mercure hub](https://symfony.com/doc/current/mercure.html)
* [Vulcain](https://vulcain.rocks) support * [Vulcain](https://vulcain.rocks) support
* Native [XDebug](docs/xdebug.md) integration * Native [XDebug](docs/xdebug.md) integration
* Just 2 services (PHP FPM and Caddy server)
* Super-readable configuration * Super-readable configuration
**Enjoy!** **Enjoy!**
@ -43,4 +45,4 @@ Symfony Docker is available under the MIT License.
## Credits ## Credits
Created by [Kévin Dunglas](https://dunglas.fr), co-maintained by [Maxime Helias](https://twitter.com/maxhelias) and sponsored by [Les-Tilleuls.coop](https://les-tilleuls.coop). Created by [Kévin Dunglas](https://dunglas.dev), co-maintained by [Maxime Helias](https://twitter.com/maxhelias) and sponsored by [Les-Tilleuls.coop](https://les-tilleuls.coop).

View File

@ -5,30 +5,21 @@ services:
php: php:
build: build:
context: . context: .
target: php_dev target: frankenphp_dev
volumes: volumes:
- ./:/srv/app - ./:/app
- ./docker/php/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini:ro - ./frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro
- ./frankenphp/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini:ro
# If you develop on Mac or Windows you can remove the vendor/ directory # 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: # from the bind-mount for better performance by enabling the next line:
#- /srv/app/vendor #- /app/vendor
environment: environment:
MERCURE_EXTRA_DIRECTIVES: demo
# See https://xdebug.org/docs/all_settings#mode # See https://xdebug.org/docs/all_settings#mode
XDEBUG_MODE: "${XDEBUG_MODE:-off}" XDEBUG_MODE: "${XDEBUG_MODE:-off}"
extra_hosts: extra_hosts:
# Ensure that host.docker.internal is correctly defined on Linux # Ensure that host.docker.internal is correctly defined on Linux
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
caddy:
command: [ "caddy", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ]
build:
context: .
target: caddy_base
volumes:
- ./public:/srv/app/public:ro
- ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
environment:
MERCURE_EXTRA_DIRECTIVES: demo
###> symfony/mercure-bundle ### ###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ### ###< symfony/mercure-bundle ###

View File

@ -5,15 +5,8 @@ services:
php: php:
build: build:
context: . context: .
target: php_prod target: frankenphp_prod
environment: environment:
APP_SECRET: ${APP_SECRET} APP_SECRET: ${APP_SECRET}
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET}
caddy:
build:
context: .
target: caddy_prod
environment:
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}

View File

@ -4,34 +4,22 @@ services:
php: php:
image: ${IMAGES_PREFIX:-}app-php image: ${IMAGES_PREFIX:-}app-php
restart: unless-stopped restart: unless-stopped
volumes:
- php_socket:/var/run/php
environment: 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!}
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16} TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
TRUSTED_HOSTS: ^${SERVER_NAME:-example\.com|localhost}|caddy$$ TRUSTED_HOSTS: ^${SERVER_NAME:-example\.com|localhost}|php$$
# The two next lines can be removed after initial installation
SYMFONY_VERSION: ${SYMFONY_VERSION:-}
STABILITY: ${STABILITY:-stable}
# Run "composer require symfony/orm-pack" to install and configure Doctrine ORM # 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} 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 # Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration
MERCURE_URL: ${CADDY_MERCURE_URL:-http://caddy/.well-known/mercure} MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
MERCURE_PUBLIC_URL: https://${SERVER_NAME:-localhost}/.well-known/mercure MERCURE_PUBLIC_URL: https://${SERVER_NAME:-localhost}/.well-known/mercure
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
# The two next lines can be removed after initial installation
caddy: SYMFONY_VERSION: ${SYMFONY_VERSION:-}
image: ${IMAGES_PREFIX:-}app-caddy STABILITY: ${STABILITY:-stable}
depends_on:
php:
condition: service_healthy
restart: true
environment:
SERVER_NAME: ${SERVER_NAME:-localhost}, caddy:80
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
restart: unless-stopped
volumes: volumes:
- php_socket:/var/run/php
- caddy_data:/data - caddy_data:/data
- caddy_config:/config - caddy_config:/config
ports: ports:
@ -53,7 +41,6 @@ services:
###< symfony/mercure-bundle ### ###< symfony/mercure-bundle ###
volumes: volumes:
php_socket:
caddy_data: caddy_data:
caddy_config: caddy_config:
###> symfony/mercure-bundle ### ###> symfony/mercure-bundle ###

View File

@ -1,2 +0,0 @@
opcache.preload_user = www-data
opcache.preload = /srv/app/config/preload.php

View File

@ -1,8 +0,0 @@
#!/bin/sh
set -e
if env -i REQUEST_METHOD=GET SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping cgi-fcgi -bind -connect /var/run/php/php-fpm.sock; then
exit 0
fi
exit 1

View File

@ -1,9 +0,0 @@
[global]
daemonize = no
process_control_timeout = 20
[www]
listen = /var/run/php/php-fpm.sock
listen.mode = 0666
ping.path = /ping
access.suppress_path[] = /ping

View File

@ -85,16 +85,3 @@ docker compose -f docker-compose.yml -f docker-compose.prod.yml up --wait
If you want to deploy your app on a cluster of machines, you can use [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/), If you want to deploy your app on a cluster of machines, you can use [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/),
which is compatible with the provided Compose files. which is compatible with the provided Compose files.
To deploy on Kubernetes, take a look at [the Helm chart provided with API Platform](https://api-platform.com/docs/deployment/kubernetes/), which can be easily adapted for use with Symfony Docker. To deploy on Kubernetes, take a look at [the Helm chart provided with API Platform](https://api-platform.com/docs/deployment/kubernetes/), which can be easily adapted for use with Symfony Docker.
## Configuring a Load Balancer or a Reverse Proxy
Since Caddy 2.5, XFF values of incoming requests will be ignored to prevent spoofing.
So if Caddy is not the first server being connected to by your clients (for example when a CDN is in front of Caddy), you may configure `trusted_proxies` with a list of IP ranges (CIDRs) from which incoming requests are trusted to have sent good values for these headers.
As a shortcut, `private_ranges` may be configured to trust all private IP ranges.
```diff
-php_fastcgi unix//var/run/php/php-fpm.sock
+php_fastcgi unix//var/run/php/php-fpm.sock {
+ trusted_proxies private_ranges
+}
```

View File

@ -7,11 +7,11 @@ You must add the authority to the trust store of the host :
``` ```
# Mac # Mac
$ docker cp $(docker compose ps -q caddy):/data/caddy/pki/authorities/local/root.crt /tmp/root.crt && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/root.crt $ docker cp $(docker compose ps -q php):/data/caddy/pki/authorities/local/root.crt /tmp/root.crt && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/root.crt
# Linux # Linux
$ docker cp $(docker compose ps -q caddy):/data/caddy/pki/authorities/local/root.crt /usr/local/share/ca-certificates/root.crt && sudo update-ca-certificates $ docker cp $(docker compose ps -q php):/data/caddy/pki/authorities/local/root.crt /usr/local/share/ca-certificates/root.crt && sudo update-ca-certificates
# Windows # Windows
$ docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt %TEMP%/root.crt && certutil -addstore -f "ROOT" %TEMP%/root.crt $ docker compose cp php:/data/caddy/pki/authorities/local/root.crt %TEMP%/root.crt && certutil -addstore -f "ROOT" %TEMP%/root.crt
``` ```
## Using Custom TLS Certificates ## Using Custom TLS Certificates
@ -23,16 +23,17 @@ For instance, to use self-signed certificates created with [mkcert](https://gith
1. Locally install `mkcert` 1. Locally install `mkcert`
2. Create the folder storing the certs: 2. Create the folder storing the certs:
`mkdir docker/caddy/certs -p` `mkdir frankenphp/certs -p`
3. Generate the certificates for your local host (example: "server-name.localhost"): 3. Generate the certificates for your local host (example: "server-name.localhost"):
`mkcert -cert-file docker/caddy/certs/tls.pem -key-file docker/caddy/certs/tls.key "server-name.localhost"` `mkcert -cert-file frankenphp/certs/tls.pem -key-file frankenphp/certs/tls.key "server-name.localhost"`
4. Add these lines to the `./docker-compose.override.yml` file about `CADDY_EXTRA_CONFIG` environment and volume for the `caddy` service : 4. Add these lines to the `./docker-compose.override.yml` file about `CADDY_EXTRA_CONFIG` environment and volume for the `php` service :
```diff ```diff
caddy: php:
+ environment: environment:
+ CADDY_EXTRA_CONFIG: "tls /etc/caddy/certs/tls.pem /etc/caddy/certs/tls.key" + CADDY_EXTRA_CONFIG: "tls /etc/caddy/certs/tls.pem /etc/caddy/certs/tls.key"
# ...
volumes: volumes:
+ - ./docker/caddy/certs:/etc/caddy/certs:ro + - ./frankenphp/certs:/etc/caddy/certs:ro
- ./public:/srv/app/public:ro - ./public:/app/public:ro
``` ```
5. Restart your `caddy` container 5. Restart your `php` service

View File

@ -29,7 +29,7 @@ First, [create a PHP debug remote server configuration](https://www.jetbrains.co
* Port: `443` * Port: `443`
* Debugger: `Xdebug` * Debugger: `Xdebug`
* Check `Use path mappings` * Check `Use path mappings`
* Absolute path on the server: `/srv/app` * Absolute path on the server: `/app`
You can now use the debugger! You can now use the debugger!

View File

@ -1,5 +1,9 @@
{ {
{$CADDY_GLOBAL_OPTIONS} {$CADDY_GLOBAL_OPTIONS}
frankenphp {
{$FRANKENPHP_CONFIG}
}
} }
{$SERVER_NAME:localhost} {$SERVER_NAME:localhost}
@ -19,7 +23,7 @@ log {
} }
route { route {
root * /srv/app/public root * /app/public
mercure { mercure {
# Transport to use (default to Bolt) # Transport to use (default to Bolt)
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
@ -36,7 +40,26 @@ route {
} }
vulcain vulcain
php_fastcgi unix//var/run/php/php-fpm.sock # Add trailing slash for directory requests
@canonicalPath {
file {path}/index.php
not path */
}
redir @canonicalPath {path}/ 308
# If the requested file does not exist, try index files
@indexFiles file {
try_files {path} {path}/index.php index.php
split_path .php
}
rewrite @indexFiles {http.matchers.file.relative}
# FrankenPHP!
@phpFiles path *.php
php @phpFiles
encode zstd gzip encode zstd gzip
file_server file_server
respond 404
} }

View File

@ -1,3 +1,4 @@
variables_order = EGPS
expose_php = 0 expose_php = 0
date.timezone = UTC date.timezone = UTC
apc.enable_cli = 1 apc.enable_cli = 1

View File

@ -0,0 +1,2 @@
opcache.preload_user = root
opcache.preload = /app/config/preload.php

View File

@ -1,12 +1,7 @@
#!/bin/sh #!/bin/sh
set -e set -e
# first arg is `-f` or `--some-option` if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
if [ "${1#-}" != "$1" ]; then
set -- php-fpm "$@"
fi
if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
# Install the project the first time PHP is started # Install the project the first time PHP is started
# After the installation, the following block can be deleted # After the installation, the following block can be deleted
if [ ! -f composer.json ]; then if [ ! -f composer.json ]; then
@ -18,7 +13,7 @@ if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
cd - cd -
rm -Rf tmp/ rm -Rf tmp/
composer require "php:>=$PHP_VERSION" composer require "php:>=$PHP_VERSION" runtime/frankenphp-symfony
composer config --json extra.symfony.docker 'true' composer config --json extra.symfony.docker 'true'
if grep -q ^DATABASE_URL= .env; then if grep -q ^DATABASE_URL= .env; then

View File

@ -0,0 +1,4 @@
worker {
file ./public/index.php
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
}