README.md quick-start, fullstack/Backend/.env.example:1-164 env matrix, and the docker/entrypoint.sh boot sequence into a reproducible local workflow plus a tinker/queue troubleshooting runbook.| Tool | Version | Verify |
|---|---|---|
| PHP | 8.2+ (prod image 8.5-fpm-alpine) | php -v |
| Composer | 2.x | composer --version |
| Node | 20+ | node -v |
| DB | sqlite (default) or MySQL 8 | sqlite3 --version |
CACHE_STORE=database, QUEUE_CONNECTION=database are the defaults (Phase 6/12). Telescope is off unless TELESCOPE_ENABLED=true.README.md:49-75 and docker/entrypoint.sh:8-50 — the local sequence mirrors the container boot:itinera-showcase-react — npm install; npm run dev -- --port 5199, or python -m http.server 8080 for fullstack/Frontend vanilla)..env.example:1-164APP_KEY + JWT_SECRET are mandatory for boot; everything else has a local default.| Key | Local default | Prod value | Notes |
|---|---|---|---|
APP_ENV | local | production | gates TELESCOPE_ENABLED warning |
APP_DEBUG | false | false | never true on Railway |
DB_CONNECTION | sqlite | mysql | DB_DATABASE defaults to database/database.sqlite |
CACHE_STORE | database | redis | CACHE_PREFIX threedos_ |
QUEUE_CONNECTION | database | redis (recommended) | see QUEUE_WORKER_SLEEP 3 / TRIES 3 |
SESSION_DRIVER | database | database | SESSION_LIFETIME 120 |
CORS_ALLOWED_ORIGINS | http://localhost:8080, http://127.0.0.1:8080, http://localhost:5173 ... | https://yourdomain.com | comma-separated, baked by config:cache |
JWT_SECRET | (empty) → artisan jwt:secret | vault | — |
PAYMOB_* | (empty) | PAYMOB_PUBLIC_KEY/SECRET_KEY/HMAC/INTEGRATION_IDS | PAYMOB_HMAC must be set in prod or PaymobGateway throws SEC-05 |
GROQ_API_KEY / OPENAI_API_KEY | (empty) | vault | gsk-test-mock-key in phpunit.xml |
FRONTEND_URL | (empty → localhost:8080 fallback) | https://frontend.railway.app | used for Paymob callback redirect |
TELESCOPE_ENABLED | false | false | never on prod unless telescope.yourdomain.com restricted |
SITE_FORK_PRICE_CENTS / PLATFORM_COMMISSION_RATE | 50000 / 0.05 | — | Phase 9 settings seeder |
LOG_CHANNEL | stack → single | stack | LOG_LEVEL debug |
| Command | When |
|---|---|
| `php artisan route:list --json | measure` |
php artisan config:cache && route:cache && view:cache | reproduce Railway entrypoint locally after env change |
php artisan migrate --force | entrypoint does this on every boot; run after pulling migrations |
php artisan db:seed --force | repopulate 60 orders / 60 payments / hotels / test users — only if SEED_ON_DEPLOY=true in prod |
php artisan queue:work --sleep=3 --tries=3 --max-time=3600 | keep FulfillOrderListener and mail queue alive; supervisord does this in container (Phase 12) |
php artisan tinker | see §5 |
php artisan storage:link | after migrate:fresh if public/storage missing |
php artisan pint --test | lint gate (CI lint job) |
supervisord priority 30 runs queue:work --sleep=3 --tries=3 --max-time=3600 (Phase 12). The worker sleeps 3s when idle, retries 3x per job, and recycles every 3600s to bound leaks.QUEUE_CONNECTION=database, jobs are sync-equivalent in tests (phpunit.xml sets QUEUE_CONNECTION sync) but async in dev — you must keep the worker running to see PaymentSucceeded → FulfillOrderListener → subscription + AI quota reset.| Log | Where |
|---|---|
Laravel stack → single | storage/logs/laravel.log |
Log::warning('Paymob webhook HMAC failed') | same — greps for HMAC |
Log::warning('Order expired beyond grace period') | same — greps for grace |
Log::error('Order fulfillment failed') | same — includes order_id, payment_id, exception |
PHP error_log /var/log/php_errors.log (docker/php.ini) | container only |
supervisor | /var/log/supervisor/supervisord.log |
docker/entrypoint.sh steps in the same order: config:cache before route:cache before migrate before queue:work.database cache/queue + sqlite file are enough; Redis is a prod upgrade, not a dev requirement.migrate:fresh --seed always yields the same 60 paid orders — telemetry and admin dashboards are comparable across machines.CheckoutService, WebhookService, AiUsageService) is resolvable via app(); snippets above are the fastest way to reproduce a bug without Postman.