Instructions for AI agents working on this project.
Everything goes through the builder container, via Castor:
castor builder -- bin/console cache:clear # one-off command
castor builder -- bin/console make:migration # one-off commandHost prerequisites only: Docker, Bash, Castor.
castor start # build + install + up + migrate
castor stop # stop the stack
castor logs [--service=service] # logs (frontend, postgres, ...)
castor app:install # composer install + importmap:install + qa:install
castor app:db:migrate # Doctrine migrations (alias: castor migrate)
castor app:db:fixtures # fixtures (alias: castor fixtures)
castor app:cache-clear # clears var/cache (alias: castor cache-clear)
castor postgres # opens a psql shell (alias: castor pg)
castor pg -- "SELECT now();" # one-shot psql query (or raw psql args: castor pg -- -c "\dt")Docker / workers:
castor docker:build [--service=service]
castor docker:up [--service=service]
castor start-workers # start workers (worker profile, currently unused — see Stack)
castor stop-workersThe context changes how tasks are executed (APP_ENV, compose files, etc.):
castor --context=test qa:phpunit # APP_ENV=test, for tests
castor --context=ci ... # like test, tuned for CI
castor --context=prod ... # production images on a dedicated local stack (docker-compose.prod.yml)Always run tests and anything touching the database with --context=test.
Without option, the default context applies.
- Symfony at the repo root (docroot =
public/) - PostgreSQL 16: user/pass/db =
qotd/qotd/qotd - nginx + php-fpm (service
frontend), Traefik router, HTTPS on<root_domain>(seecastor.php) cronservice for scheduled tasks (e.g. posting the daily quote)- Symfony AssetMapper for JS/CSS (
importmap.php) — no Node/yarn build step - A Messenger
workerservice is defined ininfrastructure/docker/docker-compose.ymlbut currently commented out (no async transport in use yet) - Production ships as two images (
phpandnginx), built from the "Production stages" ofinfrastructure/docker/services/php/Dockerfileand pushed by.github/workflows/build-push.yml. php-fpm and nginx configuration (services/php/php/,services/php/nginx/) is shared with the devfrontendcontainer. The production cron job runsbin/console qotd:runwith thephpimage (thecronservice is dev only)
Tools run inside the builder.
castor qa # everything: cs + phpstan + twig-cs + phpunit
castor qa:cs [--dry-run] # PHP-CS-Fixer (.php-cs-fixer.php)
castor qa:phpstan [-b] # PHPStan level 8 (phpstan.neon)
castor qa:twig-cs # Twig-CS-Fixer
castor qa:phpunit # PHPUnitAfter any PHP/Twig code change: castor qa:cs --dry-run,
castor qa:phpstan, then castor qa:phpunit.
- Never invoke
docker composeby hand: use thedocker_compose()/docker_compose_run()functions from.castor/docker.phpto write new tasks. - Never hardcode ports or project names: git worktree support automatically
isolates project/volumes/ports (
castor docker:ports). Usevariable('project_name')etc. - New recurring task? Make it a Castor task (
castor.phpor.castor/*.php), not a shell script. - QA tool dependencies live in
tools/<tool>/composer.json(not in the rootcomposer.json).