Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .docker/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# FrankenPHP configuration for the POC. Ported from .docker/nginx.conf and
# .docker/templates/default.conf.template.
#
# Mounted over the image default at /etc/frankenphp/Caddyfile.
{
# Traefik terminates TLS and Caddy is only ever reached over plain HTTP on
# the app network, so Caddy must neither request nor serve certificates.
auto_https off
skip_install_trust

# Request metrics, exposed at /metrics below.
metrics

servers {
# set_real_ip_from / real_ip_recursive / real_ip_header X-Forwarded-For.
#
# private_ranges, not the template's 172.16.0.0/16: compose puts the
# frontend network on 172.18.0.0/16 and the client on 172.22.0.0/16,
# neither of which that /16 covers, so real-IP resolution never
# happened. private_ranges is 10/8, 172.16/12, 192.168/16 and
# localhost, which is what a /12 in the template would have meant.
trusted_proxies static private_ranges
client_ip_headers X-Forwarded-For
}

frankenphp {
{$FRANKENPHP_CONFIG}
}
}

# A bare port means no hostname, and so no certificate handling.
{$SERVER_NAME::8080} {
root {$SERVER_ROOT:/app/public}

# gzip on
encode zstd br gzip

# access_log /dev/stdout main
#
# JSON rather than nginx's `main` layout. Every field that format carried is
# here — client_ip, user_id, ts, method/uri/proto, status, size, and the
# Referer, User-Agent and X-Forwarded-For headers — plus duration, which
# nginx did not log. Reproducing the text layout byte for byte needs the
# transform encoder, which is not in the published image: this build has
# console, json, append, filter and journald only. It matches supercronic,
# which the fpm image already runs with -json.
log {
output stdout
format json
}

# client_max_body_size
request_body {
max_size {$PHP_MAX_BODY_SIZE:5MB}
}

# Prometheus metrics, behind ITKMetricsAuth on its own Traefik router, the
# way /cron-metrics was.
#
# This replaces the nginx `location = /cron-metrics` proxy to
# supercronic. That proxy pointed at ${NGINX_CRON_METRICS}, and the fpm
# entrypoint only starts supercronic when /app/crontab exists — this
# project has no crontab, so nothing ever listened and the route answered
# 502. nginx itself exported nothing: stub_status is compiled into the
# image but the template never enabled it.
#
# Caddy does export, so the endpoint finally has something behind it:
# request counts, durations and sizes by code, method and handler, requests
# in flight, plus Go runtime and process metrics. A supercronic sidecar, if
# one is ever added, needs a route of its own.
handle /metrics {
metrics
}

# Protect files and directories from prying eyes.
#
# The nginx version leans on a negative lookahead to let /.well-known
# through. RE2, which Caddy uses, has no lookaheads, so the exception is a
# matcher of its own instead.
#
# Note that the extension list covers yml but not yaml: public/ serves
# api-spec-v1.yaml.
@hidden {
path_regexp hidden /\.
not path /.well-known/*
}
respond @hidden 404

@protected path_regexp protected (?i)(\.(engine|inc|install|make|module|profile|po|sh|.*sql|tar|gz|bz2|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?|/(Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)|/#[^/]*#|\.php(~|\.sw[op]|\.bak|\.orig|\.save))$
respond @protected 404

# location ~ \.php$ { return 404; } plus the `internal` on the front
# controller: no .php path is reachable from outside, /index.php and
# /index.php/… included. The matcher sees the request as it arrived, so the
# rewrite php_server does further down is unaffected.
route {
@directPhp path *.php *.php/*
respond @directPhp 404

# try_files $uri /index.php$is_args$args
#
# Uncommenting the worker line is all worker mode needs: FrankenPHP sets
# FRANKENPHP_WORKER=1, and symfony/runtime has picked its own
# FrankenPhpWorkerRunner off that since 7.4 — no PHP package, no
# APP_RUNTIME override.
#
# Symfony 8.1 adds FRANKENPHP_RESET_KERNEL=1, which clones the kernel
# after each request. That does mean a kernel boot per request —
# AbstractKernel::__clone() nulls the container and clears `booted`, so
# the next handle() runs initializeBundles() and instantiates the
# compiled container again — but it keeps the PHP runtime, OPcache and
# autoloader warm, so it is not the same as no worker at all. Measured on
# /admin in prod: 1319 rps and a 9.0 ms median without a worker, 1494 and
# 4.3 ms with one, 1395 and 6.1 ms with one plus the reset. Roughly half
# the gain, and immune to state leaking between requests.
php_server {
#worker /app/public/index.php
}
}
}
15 changes: 15 additions & 0 deletions .docker/php-dev.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
; Xdebug settings, mounted only by docker-compose.override.yml.
;
; The extension is installed in the Dockerfile's `dev` stage and absent from
; `prod`, so neither this file nor the variables it reads reach a server.
;
; A port of mods-available/xdebug.ini from itkdev/php8.5-fpm, keeping its
; variable names: PHP_XDEBUG_MODE and PHP_XDEBUG_START_WITH_REQUEST are what
; itkdev-docker-compose sets when starting with a debugger attached. Xdebug also
; reads the XDEBUG_MODE environment variable directly, and that takes precedence
; — which is how CI turns on coverage without touching this file.
xdebug.mode = ${PHP_XDEBUG_MODE}
xdebug.client_host = ${PHP_XDEBUG_CLIENT_HOST}
xdebug.start_with_request = ${PHP_XDEBUG_START_WITH_REQUEST}
xdebug.max_nesting_level = ${PHP_XDEBUG_MAX_NESTING_LEVEL}
xdebug.output_dir = ${PHP_XDEBUG_OUTPUT_DIR}
47 changes: 47 additions & 0 deletions .docker/php.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
; PHP settings for the FrankenPHP POC, mounted into
; /usr/local/etc/php/conf.d/.
;
; A port of what itkdev/php8.5-fpm configures and the published FrankenPHP image
; does not: the env-driven ini templates `fpm/conf.d/90-php.ini`,
; and `mods-available/opcache.ini`, plus the error
; logging from `fpm/pool.d/zz-fpm-docker.conf`. Xdebug lives in php-dev.ini,
; which production does not mount. The variable names are the fpm
; image's own, so the same overrides work; the Dockerfile defaults every one of
; them, because an unset variable expands to the empty string and PHP warns.

; fpm/conf.d/90-php.ini
realpath_cache_size = 4096k
realpath_cache_ttl = 600
expose_php = Off
max_execution_time = ${PHP_MAX_EXECUTION_TIME}
memory_limit = ${PHP_MEMORY_LIMIT}
post_max_size = ${PHP_POST_MAX_SIZE}
upload_max_filesize = ${PHP_UPLOAD_MAX_FILESIZE}
date.timezone = ${PHP_TIMEZONE}
sendmail_path = ${PHP_SENDMAIL_PATH}
max_input_vars = ${PHP_MAX_INPUT_VARS}

; Logging. php-fpm sent its error log, its slowlog and — through
; catch_workers_output — everything a worker wrote to stderr to ${PHP_LOGS},
; which is /dev/stderr. There is no fpm master here to collect worker output, so
; PHP writes to the same place directly.
;
; php-fpm configured no access log at all: the nginx access log was the only
; per-request record. Caddy's access log replaces it, see .docker/Caddyfile.
error_log = ${PHP_LOGS}
log_errors = On
display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL & ~E_DEPRECATED

; mods-available/opcache.ini
opcache.enable = ${PHP_OPCACHE_ENABLED}
opcache.jit = ${PHP_OPCACHE_JIT}
opcache.memory_consumption = ${PHP_OPCACHE_MEMORY_CONSUMPTION}
opcache.max_accelerated_files = ${PHP_OPCACHE_MAX_ACCELERATED_FILES}
opcache.max_wasted_percentage = ${PHP_OPCACHE_MAX_WASTED_PERCENTAGE}
opcache.revalidate_freq = ${PHP_OPCACHE_REVALIDATE_FREQ}
opcache.validate_timestamps = ${PHP_OPCACHE_VALIDATE_TIMESTAMPS}
opcache.interned_strings_buffer = 16
opcache.fast_shutdown = 1
opcache.optimization_level = 0xFFFFFFEF
2 changes: 1 addition & 1 deletion .github/workflows/api-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
Please run the following command, then commit and push the changes:

```shell
docker compose exec phpfpm composer update-api-spec
docker compose exec frankenphp composer update-api-spec
```
EOF
)" \
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/composer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@
###
### #### Assumptions
###
### 1. A docker compose service named `phpfpm` can be run and `composer` can be
### run inside the `phpfpm` service.
### 1. A docker compose service named `frankenphp` can be run and `composer` can be
### run inside the `frankenphp` service.
### 2. [ergebnis/composer-normalize](https://github.com/ergebnis/composer-normalize)
### is a dev requirement in `composer.json`:
###
### ``` shell
### docker compose run --rm phpfpm composer require --dev ergebnis/composer-normalize
### docker compose run --rm frankenphp composer require --dev ergebnis/composer-normalize
### ```
###
### Normalize `composer.json` by running
###
### ``` shell
### docker compose run --rm phpfpm composer normalize
### docker compose run --rm frankenphp composer normalize
### ```

name: Composer
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
docker network create frontend

- run: |
docker compose run --rm phpfpm composer validate --strict
docker compose run --rm frankenphp composer validate --strict

composer-normalized:
runs-on: ubuntu-latest
Expand All @@ -63,8 +63,8 @@ jobs:
docker network create frontend

- run: |
docker compose run --rm phpfpm composer install
docker compose run --rm phpfpm composer normalize --dry-run
docker compose run --rm frankenphp composer install
docker compose run --rm frankenphp composer normalize --dry-run

composer-audit:
runs-on: ubuntu-latest
Expand All @@ -76,4 +76,4 @@ jobs:
docker network create frontend

- run: |
docker compose run --rm phpfpm composer audit --locked
docker compose run --rm frankenphp composer audit --locked
46 changes: 34 additions & 12 deletions .github/workflows/doctrine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ jobs:

- name: Run Composer Install
run: |
docker compose run --rm phpfpm composer install
docker compose run --rm frankenphp composer install

- name: Run Doctrine Migrations
run: |
docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction
docker compose run --rm frankenphp bin/console doctrine:migrations:migrate --no-interaction

- name: Setup messenger "failed" doctrine transport to ensure db schema is updated
run: |
docker compose run --rm phpfpm bin/console messenger:setup-transports failed
docker compose run --rm frankenphp bin/console messenger:setup-transports failed

- name: Validate Doctrine schema
run: |
docker compose run --rm phpfpm bin/console doctrine:schema:validate
docker compose run --rm frankenphp bin/console doctrine:schema:validate

load-fixtures:
name: Load Doctrine fixtures
Expand All @@ -53,15 +53,15 @@ jobs:

- name: Run Composer Install
run: |
docker compose run --rm phpfpm composer install --no-interaction
docker compose run --rm frankenphp composer install --no-interaction

- name: Run Doctrine Migrations
run: |
docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction
docker compose run --rm frankenphp bin/console doctrine:migrations:migrate --no-interaction

- name: Load fixtures
run: |
docker compose run --rm phpfpm composer fixtures
docker compose run --rm frankenphp composer fixtures

# The jobs above migrate an empty database. A deployment migrates a database
# that already holds rows, so a migration that cannot cope with existing
Expand Down Expand Up @@ -90,26 +90,48 @@ jobs:
run: |
git checkout ${{ github.event.pull_request.base.sha }}

- name: Resolve the PHP service name
run: |
# This job straddles two revisions of docker-compose.yml: one
# of them may still call the PHP service phpfpm while the
# other calls it frankenphp.
if docker compose config --services | grep -qx frankenphp; then
echo "PHP_SERVICE=frankenphp" >> "$GITHUB_ENV"
else
echo "PHP_SERVICE=phpfpm" >> "$GITHUB_ENV"
fi

- name: Run Composer Install
run: |
docker compose run --rm phpfpm composer install --no-interaction
docker compose run --rm "$PHP_SERVICE" composer install --no-interaction

- name: Run Doctrine Migrations
run: |
docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction
docker compose run --rm "$PHP_SERVICE" bin/console doctrine:migrations:migrate --no-interaction

- name: Load fixtures
run: |
docker compose run --rm phpfpm composer fixtures
docker compose run --rm "$PHP_SERVICE" composer fixtures

- name: Check out the pull request
run: |
git checkout ${{ github.event.pull_request.head.sha }}

- name: Resolve the PHP service name
run: |
# This job straddles two revisions of docker-compose.yml: one
# of them may still call the PHP service phpfpm while the
# other calls it frankenphp.
if docker compose config --services | grep -qx frankenphp; then
echo "PHP_SERVICE=frankenphp" >> "$GITHUB_ENV"
else
echo "PHP_SERVICE=phpfpm" >> "$GITHUB_ENV"
fi

- name: Run Composer Install
run: |
docker compose run --rm phpfpm composer install --no-interaction
docker compose run --rm "$PHP_SERVICE" composer install --no-interaction

- name: Run Doctrine Migrations on the populated database
run: |
docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction
docker compose run --rm "$PHP_SERVICE" bin/console doctrine:migrations:migrate --no-interaction
4 changes: 2 additions & 2 deletions .github/workflows/github_build_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ jobs:
- name: Composer install
run: |
docker network create frontend
docker compose run --rm --user=root --env APP_ENV=prod phpfpm composer install --no-dev -o --classmap-authoritative
docker compose run --rm --user=root --env APP_ENV=prod phpfpm composer clear-cache
docker compose run --rm --user=root --env APP_ENV=prod frankenphp composer install --no-dev -o --classmap-authoritative
docker compose run --rm --user=root --env APP_ENV=prod frankenphp composer clear-cache
docker compose run --rm node yarn install
docker compose run --rm node yarn build

Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/php.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@
###
### #### Assumptions
###
### 1. A docker compose service named `phpfpm` can be run and `composer` can be
### run inside the `phpfpm` service. 2.
### 1. A docker compose service named `frankenphp` can be run and `composer` can be
### run inside the `frankenphp` service. 2.
### [friendsofphp/php-cs-fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer)
### is a dev requirement in `composer.json`:
###
### ``` shell
### docker compose run --rm phpfpm composer require --dev friendsofphp/php-cs-fixer
### docker compose run --rm frankenphp composer require --dev friendsofphp/php-cs-fixer
### ```
###
### Clean up and check code by running
###
### ``` shell
### docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix
### docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff
### docker compose run --rm frankenphp vendor/bin/php-cs-fixer fix
### docker compose run --rm frankenphp vendor/bin/php-cs-fixer fix --dry-run --diff
### ```
###
### > [!NOTE] The template adds `.php-cs-fixer.dist.php` as [a configuration
Expand Down Expand Up @@ -61,6 +61,6 @@ jobs:
docker network create frontend

- run: |
docker compose run --rm phpfpm composer install
docker compose run --rm frankenphp composer install
# https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/usage.rst#the-check-command
docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff
docker compose run --rm frankenphp vendor/bin/php-cs-fixer fix --dry-run --diff
Loading
Loading