Most project skeletons are demos: they run composer install, serve a hello
page, and stop where your actual work begins. We took the opposite route —
Pragmatic FrankenPHP is the template real production projects actually
grew out of, with the lessons ported back. About 25 of the first 40 commits
of the first product built on it were infrastructure repair; every one of
those commits is now pre-paid in the template.
github.com/k2gl/pragmatic-franken — PHP 8.5, Symfony 8, FrankenPHP worker
mode, PostgreSQL 17, Vertical Slices + CQRS over Messenger, MIT.
Claims you can check, not believe
Every marketing sentence in the README is backed by a required CI job:
-
"The production image boots." The
prod-imagejob builds the realphp_prodtarget on every PR, runs it against Postgres+Redis and asserts/readyreturns{"ok":true,"db":true,"redis":true}. (When we added this job, it immediately found three independent reasons the previous image could never start. That's the point.) -
"AI agents land green here." The
agent-smokejob scaffolds a vertical slice with the same generator agents use (make slice) and requires it to pass Pint + PHPStan level 10 + tests with zero manual edits. On its very first run it caught a code-style drift in the scaffolder. -
"You can verify what you deploy." Release images carry SLSA build
provenance (GitHub Artifact Attestations, keyless). The deploy script can
refuse unverified images, and
bin/console app:verify-attestationverifies any artifact offline in pure PHP (k2gl/sigstore-verify — passes the official sigstore-conformance suite). The test suite commits a real signed artifact and proves fail-closed behavior: one tampered byte → rejected. -
"Docs don't lie."
make docs-checkis a CI gate that greps every claimed route, make target and ADR reference against the codebase. The agent-facing context file (AGENTS.md, ≤2000 tokens, budget-linted) cannot silently drift from reality.
What's actually inside
- A real example vertical: Task entity → migration → typed repository →
Foundry factory → validated 422 problem+json → e2e tests → async
TaskCompletedevent → Mercure publish to the/taskstopic. Delete it with one flag when you start your own domain (make init name=my-app prune=1). -
SharedKernel ported from production: typed
DoctrineRepository<T>(PHPStan level 10, zero magic), RFC 9457 problem+json listeners, worker heartbeat for container healthchecks. -
A deploy story that ends in production: blue-green rollout gated on
/ready(a failing container never replaces the old one), pre-migration dumps, encrypted off-site backups with a documented restore drill, front Caddy with dynamic upstreams — all on one cheap VDS. -
Parallel agent sessions:
dev/worktree.shspins isolated stacks (own branch, own docker project, own port slot) so several Claude/Codex sessions work without colliding. - Opt-in recipes instead of bloat: JWT auth (production-proven configs), feature flags, SPA frontend, PR preview environments.
Non-goals (on purpose)
No auth in core (it's a product decision — recipe included), no bundled SPA,
no Kubernetes, no multi-DB. A template that won't say "no" accretes
everything and dies.
Try it
gh repo create my-app --template k2gl/pragmatic-franken --clone && cd my-app
make install && make init name=my-app && make smoke
# or straight from Packagist:
composer create-project k2gl/pragmatic-franken my-app
Feedback welcome — especially the critical kind. All 17 architecture
decision records are in the repo.
Top comments (0)