This guide deploys a Laravel app end to end: dply detects it from composer.json, builds a container image, runs it on the edge, and wires in the resources the app needs. It covers the database, Valkey (Redis), queues, the scheduler, migrations and logs. It assumes a standard Laravel 11 or 12 app in a GitHub, GitLab or Bitbucket repository.
Note
Laravel apps run as container apps, which are available on every plan. See Plans & pricing.
Before you start
- Commit
composer.lock. dply installs dependencies withcomposer install --no-devfrom it. - If the app builds front-end assets with Vite, keep the
buildscript inpackage.json. dply runs it in a separate Node stage and copiespublic/into the image. - Pin PHP in
composer.jsonif you care which minor you get (see PHP version).
Create the app
- Choose New app (or Deploy an app on an empty dashboard).
- Step 1 of 3 · Connect your source control: pick a connected GitHub, GitLab or Bitbucket account, or paste a repository under Or paste a repository.
- Step 2 of 3 · Select a repository: choose the repository and the branch or tag to deploy.
- Step 3 of 3 · Create your application: dply reads
composer.json, findslaravel/framework, and sets Mode to Container in the Deploy summary. Give the app a name and choose Deploy.
If your organization has no plan yet, Deploy sends you to Stripe Checkout for the $0 Starter plan, then back to this page with your choices kept.
The first deploy builds the image, rolls it out, and checks that the app's URL answers with a status below 500. You land on the app's Overview while it builds; follow progress under Deploys.
What dply builds
When the repository has no Dockerfile, dply generates one (Dockerfile.dply) for you:
- Base image: an Alpine PHP image with
pdo_pgsql,pdo_mysql,redis,intl,zip,bcmath,pcntlandopcache. Any otherext-*yourcomposer.jsonor locked packages require is installed on top. - Web server: nginx in front of PHP-FPM, listening on port 8080. If
laravel/octaneis installed, the app runsphp artisan octane:startinstead: RoadRunner when aspiral/roadrunnerpackage is present, otherwise Swoole. - Assets: when
package.jsonhas abuild(orproduction) script, a Node stage runs it and the builtpublic/directory is copied into the image. - Inertia SSR: when the build produces
bootstrap/ssr, the image also startsphp artisan inertia:start-ssr.
If your repository has its own Dockerfile at the root, dply uses it instead and routes traffic to the first EXPOSE port (8080 when there is none).
Important
With your own Dockerfile, the dply-specific boot steps (migrations on start, queue worker mode, SQLite restore) are not added. Use the generated image unless you need something it cannot do.
PHP version
dply picks the PHP version from composer.json:
config.platform.php, if set.- Otherwise the newest version your
require.phpconstraint allows, up to PHP 8.4. An explicit^8.5or8.5.*gets 8.5.
Available versions are 8.2, 8.3, 8.4 and 8.5.
Environment variables
On the first deploy dply adds the variables a Laravel app needs to boot, only where you have not set them:
| Variable | Value |
|---|---|
APP_KEY |
A generated key |
APP_ENV |
production |
APP_URL, ASSET_URL |
The app's live URL |
LOG_CHANNEL |
stderr |
SESSION_DRIVER |
cookie |
They are saved on the app, so they stay stable across deploys and you can change them under Environment. Add your own variables there too; they reach both the build and the running app. See Environment variables.
Warning
APP_URL and ASSET_URL are saved once, with the address the app had on its first deploy. After you add a custom domain, update both under Environment and redeploy, or Laravel keeps generating links to the old address.
Sessions default to the cookie driver because containers share no disk. If you move sessions to Redis or the database, set SESSION_DRIVER yourself.
Add a database
Without a database, the app boots on SQLite at /tmp/database.sqlite. dply migrates it on every start and, where available, saves the file while the app runs and restores it when the app wakes. A SQLite app is served by one instance.
For anything beyond a prototype, attach a managed database:
- On the app's Overview, choose Add resource, then Database.
- Pick Postgres or MySQL (or MongoDB) and a size, and confirm.
- Redeploy. The next deploy sets:
| Variable | Postgres | MySQL |
|---|---|---|
DB_CONNECTION |
pgsql |
mysql |
DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD |
set | set |
DB_SSLMODE |
require |
— |
DATABASE_URL |
postgresql://…?sslmode=require |
mysql://…?ssl-mode=REQUIRED |
MongoDB sets MONGODB_URI instead. A value you save under Environment always wins over the injected one. Databases are billed by usage; see Postgres, MySQL & MongoDB.
Run migrations
There is no separate release step: containers start fresh each time. To migrate, turn on Run migrations when a container starts in Overview → App card → Sleep, scaling, region (also in the database's settings). Each start then runs php artisan migrate --force --isolated.
You can also run Migrate, Seed and roll back from the database's Settings without redeploying.
Tip
Migrating on start repeats on every wake from sleep and costs a second framework boot. For large apps, leave it off and run Migrate from the database settings after a deploy that changes the schema.
Add Valkey for cache and queues
- Choose Add resource, then dply Valkey.
- Start a new instance or paste the address of an existing Redis.
- Redeploy.
The next deploy sets REDIS_URL, REDIS_HOST, REDIS_PORT, REDIS_USERNAME and REDIS_PASSWORD, and for Laravel also CACHE_STORE=redis, REDIS_CLIENT=phpredis and REDIS_PERSISTENT=true. See Valkey (Redis).
Run queued jobs
You have two ways to process jobs.
Queue workers run php artisan queue:work as always-on instances of your app's image, pulling from Valkey or your database:
- Choose Add resource, then Queue workers.
- Set the connection, queue names, instance count and, on Pro and Team, autoscaling.
Starter allows 1 worker instance with no autoscaling; Pro allows 5 instances and 2 extra worker groups; Team allows 10 and 4. Workers never sleep, so they bill compute all month. See Queue workers.
A Queue resource pushes each job to the app over HTTP instead, so nothing runs while the queue is empty:
- Choose Add resource, then Queue.
- Redeploy. dply sets
DPLY_QUEUEandQUEUE_CONNECTION=dply.
dispatch() then sends jobs to the queue and the app runs them as they arrive. See Queues.
Both paths rely on the dply/laravel package. dply adds it to the generated image when the app uses a database, queue, key-value store, bucket, queue workers or the scheduler, unless you already require it.
Run the scheduler
In Overview, select Add resource → Scheduled task → Run Laravel’s scheduler (or turn on Run the Laravel scheduler every minute in the App card's Sleep, scaling, region), then deploy. dply calls schedule:run every minute. When queue workers are running, the first worker runs the scheduler instead. To run a single artisan command on its own schedule, add it as a Scheduled task on Overview; Run now in its sheet runs it once in the live app and shows the output. See Scheduled tasks.
Broadcasting
For Laravel Echo and Reverb, add a Realtime resource. dply runs the WebSocket server; you do not run reverb:start. See Laravel broadcasting with Realtime.
Size, scaling and sleep
On the App card on Overview you set the size, Instances, Always awake and Sleeps after (5 minutes by default). An idle app sleeps and the next request wakes it, which adds a cold start to that request. Set Min instances above 0 to keep one warm. See Scaling & sleep.
Logs
- Build output: Deploys, then the deployment, or Build & deploy logs.
- Application logs: Build & deploy logs, then Everything it printed in the last 15 minutes. Laravel logs to
stderr, so exceptions appear here. - Queue worker output: Worker logs in the queue workers panel.
Custom domain
Add the domain under Routing, Domains, and follow Domains. Then update APP_URL and ASSET_URL as described above.