The Container Image ​
YOLO deploys your app as a single Docker image to Fargate. That one image runs everything — the web server and, optionally, queue workers and the scheduler — supervised by supervisord.
You own a small Dockerfile; YOLO generates the moving parts (entrypoint, process config) into the build context at build time. This page is the contract between the two.
What yolo init scaffolds ​
yolo init writes a Dockerfile and .dockerignore to your project root. The default Dockerfile is built on FrankenPHP and looks like this:
FROM dunglas/frankenphp:1-php8.4-alpine
# nodejs is the runtime for Inertia SSR (tasks.web.ssr); drop it if you don't use SSR.
RUN apk add --no-cache git supervisor nodejs \
&& install-php-extensions intl pcntl bcmath redis pdo_mysql opcache excimer
WORKDIR /app
COPY --chown=www-data:www-data . /app
# Place the generated supervisor config at a default search path so both
# `supervisord` and an interactive `supervisorctl` find it without -c.
COPY docker/supervisord.conf /etc/supervisord.conf
RUN chmod +x /app/.yolo-entrypoint.sh
USER www-data
ENV SERVER_NAME=:8000
EXPOSE 8000
# The entrypoint dispatches on the role argument (default web → supervisord).
# Each ECS task definition passes its own role; a one-off command (e.g. a
# deploy migration) is exec'd directly.
ENTRYPOINT ["/app/.yolo-entrypoint.sh"]
CMD ["web"]Customise it freely — add PHP extensions, system packages, a different base image. Just keep the contract below intact.
What YOLO generates ​
During yolo build, YOLO writes two files into the build context that your Dockerfile copies in:
| File | Purpose |
|---|---|
.yolo-entrypoint.sh | The container entrypoint. Runs your deploy-all hooks (e.g. php artisan optimize) on startup, then dispatches on the container command: a role (web / queue / scheduler) is supervised and traps SIGTERM so the web tier keeps serving across the ALB drain window before forwarding the stop; any other command — a one-off task such as a deploy migration — is exec'd directly (no supervise, no drain). ECS can override the command, not the entrypoint, which is why the dispatch lives here. |
docker/supervisord.conf | The web container's supervisord program tree — FrankenPHP/Octane, plus the queue:work worker and the scheduler unless you've extracted them into their own services. A crontab is generated alongside it (the scheduler always runs somewhere). A standalone queue that also hosts the scheduler gets a second docker/supervisord.queue.conf. |
Because these are generated, your Dockerfile doesn't need to know how to run Octane, the queue, or the scheduler — it just copies the config and runs the entrypoint.
The contract ​
For your image to work with YOLO, the Dockerfile must:
- Copy the application into
/app:dockerfileWORKDIR /app COPY --chown=www-data:www-data . /app - Copy the generated supervisord config to a default search path:dockerfile
COPY docker/supervisord.conf /etc/supervisord.conf - Make the generated entrypoint executable and use it:dockerfile
RUN chmod +x /app/.yolo-entrypoint.sh ENTRYPOINT ["/app/.yolo-entrypoint.sh"] CMD ["web"] - Expose the web port, and make sure it matches
tasks.web.portin your manifest (default8000). The ALB health-checks this port at/up(Laravel's built-in health route) — override the path or timing viatasks.web.health-check.*. - Have
supervisorinstalled (the default Dockerfile installs it viaapk add).
Processes in the container ​
Every app runs three roles — web, the queue worker, and the scheduler — and by default they all share the one web container:
tasks:
web:
port: 8000- Web always runs —
php artisan octane:startserving Laravel Octane.octane:startboots whichever server your app'sOCTANE_SERVERnames;yolo initseedsOCTANE_SERVER=frankenphpin your.envto match the scaffolded Dockerfile. The server is an app concern, not infrastructure YOLO injects — to run a different Octane server, change the base image andOCTANE_SERVERtogether. - The queue worker runs
queue:work, bundled in the web container until you extract it. - The scheduler runs a busybox
crondthat firesphp artisan schedule:runevery minute, bundled until you extract it. (YOLO uses cron, notschedule:work, so the scheduler survivesSIGTERMcleanly.) ssr: trueadds Inertia's SSR renderer — see Inertia SSR below.
Independent task groups
Run web in isolation by extracting the worker tier: add a top-level tasks.queue block and the queue worker and scheduler move to their own service, leaving web as pure Octane. Add tasks.scheduler too for a dedicated singleton cron. Where each role runs is derived from which blocks you've added; see Where each role runs and Scaling.
Inertia SSR ​
Set tasks.web.ssr: true to server-render your Inertia + Vue pages (better SEO, faster first paint). YOLO adds an ssr program to supervisord that runs php artisan inertia:start-ssr — a Node process listening on 127.0.0.1:13714. PHP calls it on localhost for each render, so SSR is always bundled in the web container — never its own service. YOLO injects INERTIA_SSR_ENABLED=true (unless your .env already sets it); the render URL comes from Inertia's default config/inertia.php.
Two things are on you:
- A Node runtime in your image. The scaffolded Dockerfile already installs
nodejs, so SSR works out of the box. If you've slimmed it out (or moved to a base image without Node), add it back —yolo buildchecks the Dockerfile for a Node runtime whenssris on and asks for confirmation if it can't find one (a warning only — it never blocks a non-interactive CI build). - An SSR bundle from your build. Your
npm run buildmust emit the SSR bundle (bootstrap/ssr/) — that's standard Inertia SSR setup in yourvite.config.js. The bundle is copied into the image automatically (it isn't excluded by.dockerignoreor the build'snode_modulescleanup).
If the SSR process is down, Inertia falls back to client-side rendering, so the app keeps serving — the ALB health check stays on PHP's /up and isn't coupled to SSR. supervisord restarts a crashed renderer automatically.
The .dockerignore ​
The scaffolded .dockerignore trims the build context but deliberately keeps a few things the image depends on:
.env— the environment's file, baked in at build timevendor— installed by yourbuildhook, not the Dockerfilepublic/build— compiled Vite assetsdocker/— the generatedsupervisord.conf.yolo-entrypoint.sh— the generated entrypoint
Don't add those to .dockerignore or the build will produce a broken image.
Graceful shutdown ​
When ECS replaces a task it sends SIGTERM. The entrypoint traps it and holds the web tier open for the shutdown grace period so the ALB can drain in-flight requests before the container exits — that's what gives you deploys with no 502s. Tune it per process:
tasks:
web:
shutdown-grace-period: 30 # seconds; bump for long uploads/exports/SSEThe same value sets the container's stopTimeout and the ALB deregistration delay, keeping all three in lock-step. See tasks.web.shutdown-grace-period.
