Skip to content

feat(api): enable HTTP cache with Souin via FrankenPHP/Caddy#619

Merged
vincentchalamon merged 10 commits into4.3from
feat/souin-http-cache
Mar 31, 2026
Merged

feat(api): enable HTTP cache with Souin via FrankenPHP/Caddy#619
vincentchalamon merged 10 commits into4.3from
feat/souin-http-cache

Conversation

@vincentchalamon
Copy link
Copy Markdown
Contributor

Summary

  • Compile Souin (RFC 7234 HTTP cache) + Otter (L1 in-memory) + Redis (L2 distributed) into the FrankenPHP binary via a custom builder stage
  • Cache is activated in production only via CADDY_GLOBAL_OPTIONS / CADDY_SERVER_CACHE env vars — dev experience unchanged
  • API Platform's SouinPurger handles automatic surrogate-key invalidation on every write (prod only, via config/packages/prod/api_platform.yaml)
  • Add Redis service to Docker Compose and Helm chart as the shared L2 cache store
  • Document the architectural decision in docs/adr/0005-http-cache-with-souin.md

Test plan

  • FrankenPHP binary includes Souin modules (admin.api.souin, http.handlers.cache, storages.cache.otter, storages.cache.redis)
  • Dev mode: no Cache-Status header on responses (cache inactive)
  • Prod mode first request: Cache-Status: Souin; fwd=uri-miss; stored
  • Prod mode second request: Cache-Status: Souin; hit; detail=OTTER (L1 hit)
  • Redis populated as L2 after process restart
  • 126 PHPUnit tests pass with no regression
  • PHPStan: no errors
  • Rector: no changes required
  • Helm lint: 0 failures

Closes #357

🤖 Generated with Claude Code

Compile Souin (RFC 7234 HTTP cache) + Otter (L1 in-memory) + Redis
(L2 distributed) into the FrankenPHP binary. The cache is active in
production only (controlled via CADDY_GLOBAL_OPTIONS and
CADDY_SERVER_CACHE env vars) so the dev experience is unchanged.

API Platform's SouinPurger handles automatic cache invalidation via
surrogate keys on every write operation (only in prod, via
config/packages/prod/api_platform.yaml).

Add Redis service to Docker Compose and Helm chart to serve as the
shared L2 cache store across pods. Document the decision in ADR #5.

Closes #357

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…le lint

Add a FaultTolerantSouinPurger decorator that catches transport exceptions
thrown when the Caddy admin API (port 2019) is unreachable — this happens
when running CLI commands such as doctrine:fixtures:load in a container
that does not have FrankenPHP running (e.g. `docker compose run --rm php`).

Also suppress Hadolint DL3062 on the go get line in the Dockerfile: using
@latest is intentional to always pick up security patches in the builder
stage, and pinning to a specific commit hash would make maintenance harder.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- readonly class removes implicit readonly on promoted properties
- Rename catch variable $e to $throwable (CatchExceptionNameMatchingTypeRector)
- Disable CADDY_SERVER_CACHE in compose.e2e.yaml: Souin would cache empty
  API responses during service startup (before fixtures load) and serve
  them as stale data during Playwright tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread api/config/packages/prod/api_platform.yaml Outdated
Comment thread api/.env
Comment thread helm/api-platform/values.yaml Outdated
Comment thread compose.override.yaml Outdated
Comment thread compose.yaml Outdated
vincentchalamon and others added 2 commits March 31, 2026 15:36
- Replace sed-based main.go patching with an explicit frankenphp/main.go
  that declares all Caddy plugins: FrankenPHP, Mercure, Vulcain, Souin,
  Otter and Redis — making the binary composition fully transparent
- Use when@prod syntax instead of config/packages/prod/ subdirectory
- Add blank line before CACHE_INVALIDATION_URL in .env
- Remove CADDY_SERVER_CACHE override from compose.override.yaml (useless
  in dev: compose.prod.yaml is required to activate Souin globally)
- Upgrade Redis image from 7-alpine to 8-alpine

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace manual go build with xcaddy, the method recommended by the
FrankenPHP documentation, so all Caddy plugins (FrankenPHP, Mercure,
Vulcain, Souin, Otter, Redis) are declared explicitly via --with flags.
Remove the hand-written main.go since xcaddy generates its own.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vincentchalamon vincentchalamon marked this pull request as ready for review March 31, 2026 13:50
vincentchalamon and others added 2 commits March 31, 2026 16:10
The frankenphp:1-builder image sets CGO_ENABLED=1 globally. xcaddy is a
pure Go tool and fails to compile with CGO in this environment
(runtime/cgo: unknown symbol stderr in pcrel). Explicitly disable CGO
for the xcaddy install step; CGO_ENABLED=1 is still set for the actual
FrankenPHP build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
go install places binaries in $GOPATH/bin which is not in $PATH in the
frankenphp builder image. Use $(go env GOPATH)/bin/xcaddy to invoke it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread api/config/packages/api_platform.yaml Outdated
Per review comment: the when@prod section must appear at the end of the
file. Also fix indentation of defaults and oauth which belong under
api_platform, not as direct keys of when@prod.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vincentchalamon vincentchalamon removed the deploy Deploys Pull Request label Mar 31, 2026
github.com/caddyserver/brotli does not exist. The correct module is
github.com/dunglas/caddy-cbrotli, a CGO-based brotli implementation
maintained by the FrankenPHP author, compatible with CGO_ENABLED=1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vincentchalamon vincentchalamon merged commit f2b1ad2 into 4.3 Mar 31, 2026
7 checks passed
@vincentchalamon vincentchalamon deleted the feat/souin-http-cache branch March 31, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable Caddy cache

1 participant