A Symfony application running on FrankenPHP in worker mode, deployed on the FrankenPHP runtime of Clever Cloud (no Docker image to build). Demonstrates how to run a modern PHP app with persistent worker processes — dressed with the Clever Brand Kit, certification front and centre.
-
Fork this repository
-
Create a FrankenPHP application and link it to your fork:
clever create -t frankenphp demo-php-frankenphp clever link <app_id> # writes the binding into .clever.json
The committed
.clever.jsonis empty ({"apps": []}): no application is bound in this repository,clever linkfills it in your own clone. -
Set the environment variables (console → Environment variables, or
clever env set) — see the table below.APP_SECRETis mandatory: the committed.envonly carries a placeholder. -
No add-on needed by default (SQLite, see Data below)
-
git push(orclever deploy) → Clever Cloud runscomposer install --no-dev --no-scripts, then the hooks, then starts FrankenPHP
| Variable | Required | Value / description |
|---|---|---|
APP_SECRET |
yes | Real secret, e.g. php -r 'echo bin2hex(random_bytes(32));' — overrides the placeholder committed in .env |
APP_ENV |
yes | prod |
APP_DEBUG |
yes | 0 |
CC_WEBROOT |
yes | public |
CC_FRANKENPHP_WORKER |
yes | /public/index.php — enables worker mode, the whole point of this demo (symfony/runtime + runtime/frankenphp-symfony support it natively) |
CC_PRE_RUN_HOOK |
yes | php bin/console doctrine:migrations:migrate --no-interaction — creates the schema before each start |
CC_POST_BUILD_HOOK |
yes | php bin/console assets:install public --no-interaction — installs the Swagger UI assets of API Platform (public/bundles/ is git-ignored and Composer scripts are not run at build time) |
CC_HEALTH_CHECK_PATH |
recommended | /health — served by MainController::health(), returns 200 {"status":"ok"} |
DATABASE_URL |
no | Defaults to SQLite in .env; set it to the PostgreSQL add-on URI if you link one |
Equivalent CLI:
clever env set APP_SECRET "$(php -r 'echo bin2hex(random_bytes(32));')"
clever env set APP_ENV prod
clever env set APP_DEBUG 0
clever env set CC_WEBROOT public
clever env set CC_FRANKENPHP_WORKER /public/index.php
clever env set CC_PRE_RUN_HOOK "php bin/console doctrine:migrations:migrate --no-interaction"
clever env set CC_POST_BUILD_HOOK "php bin/console assets:install public --no-interaction"
clever env set CC_HEALTH_CHECK_PATH /healthThe default DATABASE_URL points to SQLite in var/data.db. On Clever Cloud this file lives on the instance's ephemeral filesystem: it is recreated at every deployment or restart (CC_PRE_RUN_HOOK replays the migration and its seed) and it is not shared between instances — keep the app at 1 instance, or treat the data as throw-away demo data.
For persistent data, link a PostgreSQL add-on (plan DEV is enough for a demo) and set DATABASE_URL to its POSTGRESQL_ADDON_URI. Note that the committed migration Version20220929114250 is SQLite-specific (INTEGER PRIMARY KEY AUTOINCREMENT, CLOB): on PostgreSQL it must be regenerated (php bin/console doctrine:migrations:diff against the add-on, or doctrine:schema:update for a throw-away demo).
.envis committed on purpose (Symfony Dotenv needs it to boot) and only contains non-secret defaults plus anAPP_SECRETplaceholder. Real environment variables set in the console always win over.env.Caddyfileandbenchmark.Caddyfileare not used by the Clever Cloud runtime (which ships its own Caddy configuration driven byCC_*variables). They are kept for running FrankenPHP locally (frankenphp run) and for the k6 benchmarks.static-build.Dockerfileis not used by the Clever Cloud runtime either: it builds an optional standalone static binary of the app (see frankenphp.dev/docs/static)..dockerignorekeeps.git,var/,vendor/and local.env.*files out of that build context.
| Layer | Technology |
|---|---|
| Language | PHP 8.2+ (Clever Cloud FrankenPHP runtime) |
| Framework | Symfony 7.4 + API Platform 3.4 |
| Server | FrankenPHP (worker mode via CC_FRANKENPHP_WORKER) |
| Database | SQLite (ephemeral) — PostgreSQL add-on optional |
| Deploy | Clever Cloud FrankenPHP runtime |
| Design | Clever Brand Kit (Plus Jakarta Sans, navy #13172e, dégradé Clever) |
- FrankenPHP worker mode — PHP process stays alive between requests for maximum performance
- Symfony routing and templating (Twig), API Platform resource
Monsterwith Swagger UI at/api - Clever Brand Kit landing page: sticky brand bar, hero, certification block, four content tabs (Worker Mode / API Platform / Performance / Deploy), k6 benchmark cards, platform panel, footer
- Platform panel "Vu depuis Clever Cloud" reads the variables injected by the platform (see below)
- k6 reports served at
/benchmark/{name}(e.g./benchmark/summary-100-vus-worker) from the committedbenchmark/folder /healthendpoint for the Clever Cloud health check- Responsive layout — no horizontal scroll at 375 px, single dark theme
POST/PUT/PATCH/DELETE /api/monsters are deliberately left open (no authentication) so the Swagger UI can be demoed end to end; the name field is validated (NotBlank, max 255 characters → 422 otherwise) and the SQLite database is reset at every deployment. To lock it down, either restrict the resource to read operations (#[ApiResource(operations: [new Get(), new GetCollection()])]) or add an access_control rule on ^/api for write methods in config/packages/security.yaml.
The homepage puts the Clever Cloud Academy certification right under the hero: badge, the two official tracks (Cloud Computing Fundamentals, Advanced Deployment) and a call to action to academy.clever.cloud. The brand bar also carries a permanent « Se certifier ↗ » pill.
MainController::platform() reads the environment injected by Clever Cloud and passes it to Twig as cc:
| Variable | Shown as |
|---|---|
CC_APP_NAME |
Application |
APP_ID |
App ID — absent locally → pill « Local · hors Clever Cloud » |
INSTANCE_NUMBER + CC_PRETTY_INSTANCE_NAME |
Instance (#0 · Pikachu) |
INSTANCE_TYPE |
Type d'instance |
CC_COMMIT_ID (7 chars) |
Commit déployé |
CC_DEPLOYMENT_ID (16 chars) |
Déploiement |
PHP_VERSION, $_SERVER['FRANKENPHP_WORKER'] |
PHP, Worker actif / inactif |
Reference: Clever Cloud environment variables.
demo-php-frankenphp/
├── templates/
│ ├── base.html.twig # <head> Brand Kit, brand bar, footer
│ ├── homepage/index.html.twig # Hero, certification, tabs, benchmarks, platform panel
│ └── partials/
│ ├── cc-logo.svg.twig # Official Clever Cloud logo (inlined)
│ └── cc-badge.svg.twig # Certification badge (inlined)
├── public/
│ ├── cc-brand.css # Shared Clever Brand Kit — copied as-is, do not edit
│ ├── demo.css # Demo-specific styles (tabs, benchmark table)
│ └── index.php # Web root / FrankenPHP worker script
├── src/
│ ├── Controller/MainController.php # Homepage (+ platform panel data), /health, /benchmark/{name}, /download-logo
│ ├── EventSubscriber/SecurityHeadersSubscriber.php # nosniff, Referrer-Policy, X-Frame-Options
│ └── Entity/Monster.php # API Platform resource (validated name)
├── migrations/ # Doctrine migration (SQLite schema + seed), replayed by CC_PRE_RUN_HOOK
├── benchmark/ # k6 script and HTML reports (FPM / no-worker / worker)
├── Caddyfile # Local FrankenPHP config only (not used on Clever Cloud)
├── static-build.Dockerfile # Optional static binary build (not used on Clever Cloud)
├── .dockerignore # Build context of static-build.Dockerfile
├── .env # Committed — Symfony defaults + APP_SECRET placeholder
└── .clever.json # Clever Cloud app binding — committed empty, filled by `clever link`
composer install
APP_ENV=dev php -S 127.0.0.1:8083 -t public
# http://127.0.0.1:8083/ · http://127.0.0.1:8083/api · http://127.0.0.1:8083/health
php bin/console lint:twig templates
composer auditWithout FrankenPHP the platform panel shows « Worker : inactif (php -S) » — expected outside Clever Cloud. To try worker mode locally: frankenphp run (uses the committed Caddyfile).
- App type on Clever Cloud: FrankenPHP runtime (not Docker, not the PHP/Apache runtime)
.envmust stay committed — Symfony requires it at boot time; secrets are set in the console.clever.jsonis committed empty ({"apps": []}) — no application is bound here;clever linkwrites the binding of your application into it- HTTPS is terminated at the Clever Cloud proxy — no HTTPS config needed inside the app;
trusted_proxies/trusted_headersinconfig/packages/framework.yamlmake Symfony honourX-Forwarded-Proto/For - Known dependency debt:
api-platform/core3.4 has two medium advisories only fixed in 4.x (migration to plan separately)