Automations
A new Laravel release isn't ready the moment its container starts. Storage needs its link. The database needs its migrations. Config, routes, views, and events need caching. Set one environment variable, and the container does all of it before it serves a request.
services:
app:
image: my-org/my-app:1.0.0
ports:
- "80:8080"
environment:
AUTORUN_ENABLED: "true"
That's the whole setup. There's no entrypoint script to write and no deploy step to remember. The official PHP images leave this work to you.
Three steps run on every start
Link storage
php artisan storage:link connects public/storage to your app's public files. When the link already exists, the script moves on.
Migrate the database
The script waits until your database accepts connections. It clears the config cache, then runs php artisan migrate --force.
Cache the app
php artisan optimize caches your config, routes, views, and events. It's the command Laravel recommends for every production deploy.
Then the web server starts, or your own command runs. Every step is on by default. Set a step's variable to false to skip it.
| Step | Variable | Default |
|---|---|---|
| Link storage | AUTORUN_LARAVEL_STORAGE_LINK | true |
| Migrate the database | AUTORUN_LARAVEL_MIGRATION | true |
| Cache the app | AUTORUN_LARAVEL_OPTIMIZE | true |
Your logs show each step
The container prints a line as each step starts. Laravel's own output appears between them.
π Running storage link: "php artisan storage:link"...
π Clearing Laravel cache before attempting migrations...
β‘οΈ Attempting connection to default database...
β
Database connection successful
π Running migrations for default database
π Running optimize command: "php artisan optimize"...
The startup banner above these lines shows Automations: true whenever the script is on.
Turn it on for one service
The script runs in every container that starts with AUTORUN_ENABLED=true, whatever its command. Set it on your web service and leave it off your workers. Each deployment then migrates once.
services:
app:
image: my-org/my-app:1.0.0
ports:
- "80:8080"
environment:
AUTORUN_ENABLED: "true"
queue:
image: my-org/my-app:1.0.0
command: ["php", "artisan", "queue:work"]
One-off commands count too. docker compose run app php artisan tinker starts a new app container, and that container runs the automations first. Running workers has a full example with Horizon and the scheduler.
Migrations wait for your database
Your database container may still be starting when your app comes up. The script tries to connect once a second for up to 30 seconds. As soon as the database answers, it migrates.
| Variable | Default | What it does |
|---|---|---|
AUTORUN_LARAVEL_MIGRATION_TIMEOUT | 30 | Seconds to wait for the database. |
AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK | false | true migrates without waiting. |
AUTORUN_LARAVEL_MIGRATION_DATABASE | "" | Connections to migrate in turn, such as mysql,pgsql. Empty uses your default connection. |
AUTORUN_LARAVEL_MIGRATION_ISOLATION | false | true adds --isolated. |
AUTORUN_LARAVEL_MIGRATION_SEED | false | true adds --seed. |
AUTORUN_LARAVEL_MIGRATION_FORCE | true | false removes --force. |
AUTORUN_LARAVEL_MIGRATION_MODE | default | fresh or refresh runs migrate:fresh or migrate:refresh. |
Run several replicas safely
When you scale a service to several containers, they all start at once. --isolated takes a lock, and only one container migrates at a time. It needs Laravel 9.38.0 or newer, the default migration mode, and a cache server every container shares, such as Redis or your database. A file or array cache gives each container its own lock, so they all migrate at once.
The flag needs the migrations table to exist. On your first deployment, the script leaves it off and adds it from the next one.
fresh drops every table, and refresh rolls back every migration. Use them only in development or testing.Choose what gets cached
Each cache has its own variable. All four are on by default.
| Variable | Default | Caches |
|---|---|---|
AUTORUN_LARAVEL_CONFIG_CACHE | true | Config |
AUTORUN_LARAVEL_ROUTE_CACHE | true | Routes |
AUTORUN_LARAVEL_VIEW_CACHE | true | Blade views |
AUTORUN_LARAVEL_EVENT_CACHE | true | Events |
With every cache on, the script runs php artisan optimize. Packages can add their own steps to that command, and they run too.
Turn one cache off, and the script runs php artisan optimize --except=... without it. Laravel added --except in 11.38.0. On older versions, the script runs one cache command for each cache that's still on.
Set AUTORUN_LARAVEL_OPTIMIZE=false to skip optimize and its package steps. The script still runs a cache command for each cache variable set to true. Set those to false too, and nothing gets cached.
A broken release never serves traffic
If a step fails, the script exits with an error. The container stops before the web server starts. A failing migration shows up as a container that won't start, with the error in its logs.
The script also stops when it can't find Laravel. It looks for artisan and a vendor directory in APP_BASE_DIR. Run composer install when you build the image. If a container can start before Laravel is installed, set AUTORUN_LARAVEL_SKIP_IF_NOT_FOUND=true to skip the automations instead.
Two variables help you find the cause:
AUTORUN_DEBUG=trueprints the script's settings and each connection attempt.LOG_OUTPUT_LEVEL=debugturns on debug output for every startup script.
Keep it for production
In development, you change routes and config all day. Cached copies hide those changes until the container restarts. Leave AUTORUN_ENABLED off on your machine, its default, and run artisan yourself.
To turn the automations off anywhere, set AUTORUN_ENABLED=false.
Every AUTORUN_* variable is in the environment variable specification.