Dockerfile, how GitHub Actions gates merges, and how the backend container boots (cache, migrate, queue) and serves traffic behind nginx.Dockerfile:8 declares ARG SERVICE_ROLE=frontend; Railway injects the per-service value as a build ARG (docs.railway.com/builds/dockerfiles). Final line selects the stage:| Service | SERVICE_ROLE | Base image | Artifact |
|---|---|---|---|
| Frontend | frontend | nginx:1.27-alpine | fullstack/Frontend/ static site + nginx.conf + entrypoint.sh (sed-injects API_BASE) |
| Backend | backend | php:8.5-fpm-alpine (two stages: vendor → backend) | Laravel API + vendor/ + storage/ perms + docker/{php.ini,nginx.conf,supervisord.conf,entrypoint.sh} |
railway.json:3-14 locks this in: "builder":"DOCKERFILE", dockerfilePath:"Dockerfile", runtime:"V2", numReplicas:1, restartPolicyType:"ON_FAILURE" maxRetries:10, sleepApplication:false.vendor: php:8.5-fpm-alpine + apk add git libzip gd ... + composer install --no-dev --prefer-dist --optimize-autoloader --no-scripts from fullstack/Backend/composer.json (platform php 8.5).backend: nginx + supervisor + php extensions (pdo_mysql zip pcntl bcmath gd) + move php.ini-production, copy docker/php.ini, copy vendor + source, mkdir -p storage/framework/{cache,sessions,views} storage/logs bootstrap/cache + chown www-data:www-data.EXPOSE 8080, HEALTHCHECK curl -sf http://localhost:8080/up | grep -q '"status":"ok"'.docker/php.ini — opcache.enable=1, memory 256M, max_files 20000, revalidate_freq 0, validate_timestamps 0, memory_limit 256M, post_max_size 50M, display_errors Off, session.cookie_secure=1..github/workflows/ci.yml:1-48push: [main, develop, feat/community-hub] and pull_request: [main, develop]. Two parallel jobs, both working-directory: fullstack/Backend:shivammathur/setup-php@2 php 8.2 + composer install → ./vendor/bin/pint --test.extensions: mbstring pdo pdo_sqlite dom gd intl zip → cp .env.example .env && php artisan key:generate && php artisan jwt:secret --force → php artisan test.npm test/build in CI — showcase itinera-showcase-react is gated locally via oxlint/vitest (see tasks/todo.md).docker/entrypoint.sh + supervisord.confset -e, cd /var/www/html):php artisan config:cache — bakes Railway env (CORS_ALLOWED_ORIGINS, PAYMOB_HMAC, JWT_SECRET, etc.) into bootstrap cache.php artisan route:cachephp artisan view:cache (|| true — API-only may have no blades)php artisan storage:link (|| true)php artisan migrate --force — idempotent schema drift.if [ "$SEED_ON_DEPLOY" = "true" ] → php artisan db:seed --force (first deploy only).chown -R www-data:www-data storage bootstrap/cache && chmod -R 775.exec supervisord -c /etc/supervisor/conf.d/supervisord.conf.supervisord.conf (nodaemon, logfile /var/log/supervisor/supervisord.log):php-fpm --nodaemonize priority 10.nginx priority 20 depends_on php-fpm, runs envsubst '${PORT}' < /etc/nginx/nginx.conf > /tmp/nginx.conf && nginx -c /tmp/nginx.conf -g 'daemon off;' — PORT injected by Railway.queue: php artisan queue:work --sleep=3 --tries=3 --max-time=3600 priority 30, numprocs 1 (FulfillOrderListener, mail queue; see Phase 3).docker/nginx.conf (backend)php-fpm via fastcgi_pass 127.0.0.1:9000, listen $PORT, root /var/www/html/public, try_files $uri $uri/ /index.php?$query_string, client_max_body_size 50M, fastcgi buffers. Health /up is public (no auth) for Railway probe.fullstack/Frontend/{nginx.conf,entrypoint.sh}nginx.conf — listen ${PORT}, root /usr/share/nginx/html, try_files $uri $uri/ /index.html (SPA fallback). Caching tiers:/assets/js/config.js + /assets/js/core/config.js — no-cache, must-revalidate (sed-injected API_BASE changes per deploy).*.js|css|map — public, max-age=3600, must-revalidate + etag on (was 7d, kept serving stale JS).png|jpg|svg|woff2 — max-age=604800.*.html — no-cache.entrypoint.sh — if $API_BASE set, find ... -name '*.js' -exec grep -l '__API_BASE__' {} + then sed -i "s#__API_BASE__#${API_BASE}#g"; then envsubst '${PORT}' for nginx and exec "$@".wget -q -O /dev/null http://127.0.0.1:${PORT:-80}/.FROM ${SERVICE_ROLE} avoids divergent build contexts; frontend stays nginx-only, backend bakes vendor then php-fpm — no Node in prod backend.config:cache runs at entrypoint, not build, so Railway vars are live. Frontend __API_BASE__ sed covers static JS without rebuild.migrate --force on every boot is safe; seeding is gated behind SEED_ON_DEPLOY=true./up responds without auth for Railway; CI's php artisan test covers auth/RBAC/throttle before merge.supervisord keeps queue:work alive beside php-fpm/nginx; --max-time 3600 bounds leaky jobs.