This document describes how the bundle's demo applications run under FrankenPHP in Docker, and how to reproduce development (no cache, changes visible on refresh) and production (worker mode, cache enabled) configurations. The same approach can be used in other Symfony bundles or applications that ship a FrankenPHP-based demo.
- Overview
- What the demos include
- Development configuration
- Production configuration
- Switching classic vs worker (
FRANKENPHP_MODE) - Reproducing in another bundle
- Troubleshooting
The demo/ folder is not shipped when the bundle is installed (e.g. via composer require nowo-tech/password-toggle-bundle). It is excluded from the Composer package (via archive.exclude in the bundle's composer.json). The demo applications exist only in the bundle's source repository and are intended for development, testing, and documentation. To run or modify the demos, use a clone of the bundle repository.
The demos use:
- FrankenPHP (Caddy + PHP) in a single container.
- Docker Compose with the app and the parent bundle mounted as volumes (
../..→/var/password-toggle-bundle). - Two Caddyfiles:
Caddyfile(production, with worker) andCaddyfile.dev(development, no worker). - An entrypoint that selects classic vs worker Caddyfile from
FRANKENPHP_MODE(classic|worker, defaultworkerin.env.example)
There are demos for Symfony 8 and 8 (PHP 8.5) (e.g. demo/symfony8, demo/symfony8-php85). Each has its own Dockerfile, docker-compose.yml and Makefile. From the bundle root you run e.g. make -C demo/symfony8 up (see the demo's README for the URL and port).
The main difference between development and production is:
| Aspect | Development | Production |
|---|---|---|
| FrankenPHP worker mode | Off (one PHP process per request) | On (workers keep app in memory) |
| Twig cache | Off (config/packages/dev/twig.yaml) |
On (default) |
| OPcache revalidation | Every request (docker/php-dev.ini) |
Default (e.g. 2 seconds) |
| HTTP cache headers | no-store, no-cache (in Caddyfile.dev) |
Omitted or cache-friendly |
APP_ENV / APP_DEBUG |
dev / 1 |
prod / 0 |
Ports: Each demo uses PORT from its .env. To run multiple demos at once, set a different PORT per demo.
The demo applications are configured for local development and debugging:
- Symfony Web Profiler — enabled in
devandtestenvironments. - Nowo Twig Inspector (
nowo-tech/twig-inspector-bundle) and Nowo Hot Reload (nowo-tech/hot-reload-bundle) — required together on FrankenPHP demos (dev/test only; Caddyfile Mercure +hot_reload, plusworker { watch }in worker mode). Do not enable Hot Reload in production. - Password Toggle Bundle (
Nowo\PasswordToggleBundle\NowoPasswordToggleBundle) — the bundle under test; enabled in the demos.
Example config/bundles.php (aligned with demo/symfony8):
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\UX\Icons\UXIconsBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Nowo\PasswordToggleBundle\NowoPasswordToggleBundle::class => ['all' => true],
Nowo\TwigInspectorBundle\NowoTwigInspectorBundle::class => ['dev' => true, 'test' => true],
];In production (APP_ENV=prod), only bundles registered for all or prod are loaded.
Goal: every change to PHP, Twig or config is visible on the next browser refresh without restarting the container. No long-lived PHP workers; cache disabled or revalidated on every request.
The development Caddyfile is docker/frankenphp/Caddyfile.dev in each demo. It uses plain php_server (no worker) and cache-busting headers. The entrypoint copies it over /etc/frankenphp/Caddyfile when APP_ENV=dev. Mount it in docker-compose so you can edit it without rebuilding.
The demos include docker/php-dev.ini with opcache.revalidate_freq=0. Mount it in docker-compose: ./docker/php-dev.ini:/usr/local/etc/php/conf.d/99-dev.ini:ro.
The demos use config/packages/dev/twig.yaml with twig.cache: false so template changes are visible on refresh.
Each demo's docker-compose.yml sets APP_ENV=dev and APP_DEBUG=1, mounts the app, the bundle (../..:/var/password-toggle-bundle), docker/frankenphp/Caddyfile.dev, and docker/php-dev.ini. The entrypoint copies Caddyfile.dev when APP_ENV=dev.
From the bundle root: make -C demo/symfony8 up (or symfony7, symfony8-php85). Or from the demo directory: make up.
Use the default Caddyfile (with worker). Set APP_ENV=prod and APP_DEBUG=0. Do not mount php-dev.ini. See TwigInspectorBundle DEMO-FRANKENPHP for the full production Caddyfile and steps.
- Classic:
FRANKENPHP_MODE=classic— entrypoint copiesCaddyfile.dev - Worker (default):
FRANKENPHP_MODE=worker— worker Caddyfile Recreate the container after changing.env(docker compose up -d).
After changing env or Caddyfile, restart: docker-compose restart or make -C demo/symfony8 restart.
See TwigInspectorBundle DEMO-FRANKENPHP section "Reproducing in another bundle" for the full checklist.
- Changes not visible: Ensure worker mode is off in dev (Caddyfile.dev has no
worker), add dev twig.yaml and php-dev.ini, restart container, hard-refresh browser. - Web Profiler not visible: Check
APP_ENV=devandAPP_DEBUG=1, and that WebProfilerBundle is enabled fordevin bundles.php. - Demo times out: Check port is free, container logs (
docker-compose logs php), and required env vars (e.g. APP_SECRET).