Laravel is detected from artisan and built on the official PHP image with nginx and PHP-FPM. Vite assets are built for you and config, route and view caches are warmed on start.
Last updated
These are the defaults. Override any of them in Settings → Build.
No Dockerfile or nginx config is needed. We build every PHP app from the official php:X.Y-fpm image:
require.php in composer.json (or config.platform.php). We support 8.2 to 8.5 and prefer 8.4 when your constraint allows it.ext-* in composer.json or composer.lock is added.package.json has a build script, it runs on Node 22 after composer install, and the result lands in public/build.storage:link, config:cache, route:cache and view:cache (plus a fresh SQLite file and migrations if the app is still on SQLite), then start PHP-FPM and nginx with public/ as the web root.LOG_CHANNEL=stderr), so they show in the Logs tab.Trust the platform's proxy so Laravel generates https:// URLs and asset links. In bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(at: '*');
})Add these in the project's settings before the first deploy:
APP_KEY=base64:... # php artisan key:generate --show APP_ENV=production APP_DEBUG=false APP_URL=https://myapp.ethiodeploy.com
Config is cached at start, after env vars are loaded, so a changed value takes effect on the next deploy. VITE_ values are baked into the assets at build time. See Environment variables.
Add a MySQL or Postgres addon. Its connection string is injected as DATABASE_URL, but Laravel 11 and later read DB_URL. Make the connection you use fall back to it in config/database.php:
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL', env('DATABASE_URL')),
// ...
],Then set DB_CONNECTION=pgsql (or mysql). Laravel 10 and earlier already read DATABASE_URL. Run migrations before the server starts by setting the Start command to:
php artisan migrate --force && ethiodeploy-start
ethiodeploy-start is the script that warms the caches and starts nginx and PHP-FPM, so keep it at the end.
Laravel 11+ stores cache, sessions and queued jobs in the database by default, which works once migrations have run. To move them to Redis, add a Redis addon (REDIS_URL is injected) and set:
CACHE_STORE=redis SESSION_DRIVER=redis QUEUE_CONNECTION=redis
Laravel's default Redis client is the phpredis extension. Add "ext-redis": "*" to require in composer.json so it's installed, or install predis/predis and set REDIS_CLIENT=predis.
Addons are private to the project that owns them, so a separate worker project can't reach your database or Redis. Run the queue worker and the scheduler in the same container:
php artisan migrate --force && (php artisan queue:work --tries=3 &) && (php artisan schedule:work &) && ethiodeploy-start
The background processes aren't restarted on their own if they crash, so check the Logs tab after changes. For low volume, QUEUE_CONNECTION=sync runs jobs inline and needs no worker at all.
Files written to storage/app are lost on the next deploy, because each deploy starts a fresh container. Use the s3 disk with any S3-compatible storage for user uploads. Requests up to 64 MB are accepted.
<project-slug>.ethiodeploy.com. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy.Data disappears after every deploy
Cause: The app is still on SQLite, the Laravel 11+ default, and the file lives in the container.
Fix: Add a MySQL or Postgres addon and set DB_CONNECTION and the DB_URL fallback.
No application encryption key has been specified
Cause: APP_KEY isn't set.
Fix: Run php artisan key:generate --show locally and add the value as APP_KEY.
Mixed content warnings, or CSS and JS load over http://
Cause: Laravel doesn't trust the proxy's forwarded headers.
Fix: Add trustProxies(at: '*') and set APP_URL to your https URL.
Build fails: composer.json requires php ..., which none of the supported versions satisfy
Cause: Your PHP constraint excludes 8.2 to 8.5.
Fix: Widen require.php, for example to ^8.2.
Class "Redis" not found
Cause: The phpredis extension isn't installed.
Fix: Add "ext-redis": "*" to composer.json, or use predis.
Changed an env var but the app still uses the old value
Cause: Config is cached when the container starts.
Fix: Redeploy after changing env vars.
Still stuck? Check the deployment's build log in the dashboard, or see Troubleshooting.
Which PHP version does my Laravel app run on?
The newest of 8.2, 8.3, 8.4 and 8.5 that your composer.json php constraint allows, preferring 8.4. Images come from the official PHP Docker images, which are refreshed soon after each PHP security release.
Do I need a Dockerfile, nginx.conf or Procfile?
No. nginx, PHP-FPM, the web root and the start script are set up for you. A Procfile is ignored for PHP apps; set a custom start command in Settings if you need one.
Can I use MySQL instead of Postgres?
Yes. Add a MySQL addon instead (a project has one or the other) and set DB_CONNECTION=mysql. Both inject DATABASE_URL.
How do I run Laravel's scheduler without cron?
Run php artisan schedule:work in the background from your start command. It runs due tasks every minute, the same as a cron entry calling schedule:run.
Ready to deploy? Sign in with GitHub and pick your repo. No Dockerfile, no YAML.
See the full docs for custom domains, env vars, addons, and billing.