runtime is the base
container image every FrankenPress site extends. It ships:
Published images: a multi-arch manifest list (
linux/amd64 +
linux/arm64) per supported PHP minor — :php8.3, :php8.4,
:php8.5. The default channel is ghcr.io/frankenpress/runtime:php8.3.
See Runtime matrix for the full tag schema.
Consumer pattern
Downstream sites build on top:site-template ships this Dockerfile
out of the box.
Environment variables
All optional. Defaults produce a working config out of the box.Logging
The public server emits Caddy access logs as JSON to stdout (one line per request); PHP errors flow to stderr viaphp.ini. The metrics server is
intentionally unlogged. End-to-end shipping setup (Vector → Loki / Datadog)
is in Operations → Logging.
Build args
Override at build time with--build-arg:
Cacheability model
Souin caches only anonymous GETs to public paths. Two layers of bypass keep authenticated and admin traffic out of the cache, regardless of whether the upstreamCache-Control header is honoured by Souin
itself (cache-handler v0.16.0 is unreliable about no-store /
private):
Layer 1 — path bypass
The globalcache block uses regex.exclude to skip any path matching:
/wp/...) and classic (/...) layouts. Set
FP_CACHE_BYPASS_EXTRA to add custom paths — the value is appended
verbatim to the regex, so use an alternation fragment with a leading
|:
Layer 2 — cookie bypass
A server-block matcher routes any request carrying a per-user state cookie around thecache directive entirely:
What’s still cached
Everything else: anonymous GETs to/, /<slug>/, /category/<x>/,
feeds, sitemaps, etc. Per the standard HTTP semantics — upstream
Cache-Control is honoured, with FP_CACHE_DEFAULT_CONTROL as the
fallback when the response is silent.
Note on cache key scheme
You will seeCache-Status headers like:
https://. This is intentional and
benign:
- Caddy 2.7.x’s
trusted_proxiesdirective controls which upstream hops may setX-Forwarded-For(client IP propagation). It does not propagateX-Forwarded-Protointo the request scheme that Souin reads. So when a TLS-terminating proxy (Envoy Gateway, Cloudflare, an LB) sits in front, Souin sees plain HTTP from inside the cluster and keys cache entries withhttpregardless of how the client connected. - The two failure modes this could matter for are both already
closed: authenticated requests are bypassed entirely via the cookie
matcher, and invalidation works regardless via mu-plugin
v0.2.1+‘s dual-scheme DEL (the
SouinInvalidatordeletes bothGET-http-...andGET-https-...variants on every write).
https natively with either
cache_keys { .+ { disable_scheme } } (drops scheme from the key
shape; needs a coordinated invalidator update + cache flush) or an
out-of-tree Caddy plugin that rewrites r.URL.Scheme. Neither is
worth the churn for what is purely a Cache-Status cosmetic.
Page cache invalidation
Souin caches GET responses in Redis. Invalidation is performed bymu-plugin’s SouinInvalidator,
connecting directly to Redis and DELing cache keys. Souin’s
documented HTTP invalidation APIs (PURGE, POST-CRUD, /api.souin/*
admin) are broken in cache-handler v0.16.0 — the workaround is empirical.
Redis key shape (verified):
SURROGATE_<tag> (a Redis SET), then
pipeline-DEL all members. Sub-millisecond.
Full investigation log: runtime/PHASE-0.md.
Local development
CI / publishing
Built and pushed by GitHub Actions on every push tomain and on
v*.*.* tag pushes. CI exercises the full (php, arch) matrix and
emits per-PHP rolling, per-PHP per-sha, per-PHP per-release, and an
unprefixed per-release tag (default PHP only). The full schema lives
in Runtime matrix.