Back to Article List

How to deploy Laravel on Coolify in 2026

How to deploy Laravel on Coolify in 2026

There's a small irony in deploying a Laravel app on Coolify, which is that Coolify itself is a Laravel app. The whole control panel runs on Laravel 11, with Livewire for the UI and Alpine on top. Andras Bacsai (the maintainer) has been pretty open about it, which means if you've ever debugged a weird Coolify behavior you may have ended up reading PHP source. Anyway.

This guide walks through deploying a real Laravel application on a self-hosted Coolify instance. Not just the happy-path "click deploy" version, the actual production setup with queue workers, the scheduler, Postgres or MySQL, Redis for cache and sessions, persistent storage for uploads and the deploy-time commands that Laravel needs to behave. Written against Coolify v4.0.0 and Laravel 10/11/12.

If Coolify isn't running yet, the LumaDock Coolify VPS ships with v4.0.0 pre-installed on Debian 12 and the dashboard is on port 8000 the moment your server provisions. The getting started guide covers initial dashboard access. Once you're in, the rest of this is sequential.

What your Laravel app needs to run

Quick honesty pass before we deploy anything. A production Laravel app is rarely "one container running PHP." It's usually a small system that includes a PHP-FPM or Laravel Octane process serving HTTP, a queue worker container running php artisan queue:work, a scheduler that runs the Laravel cron once a minute, a database (Postgres or MySQL), Redis for cache and sessions and queue and persistent storage for user uploads.

If you skip any of those pieces you'll have something that looks like it works for a few minutes and then quietly breaks. Queues fail silently, sessions reset on every deploy, file uploads vanish when the container restarts. We're going to set all of it up.

Step 1, prepare your repo for Coolify deployment

Before connecting Coolify to your repo, make sure your Laravel app has a few things in place.

Pin your PHP version

Add a composer.json require entry like "php": "^8.3" or include a .php-version file at the repo root. Coolify's Nixpacks build pack reads either of these and picks the right PHP version for your build container. Without a pin, Nixpacks defaults to whatever PHP version it considers current, which can shift between Coolify updates and break your build unexpectedly.

Add a Procfile or set the start command explicitly

Laravel doesn't have a single canonical way to start, so Coolify needs to know what to run. The two patterns that work cleanly are:

Use php artisan serve --host=0.0.0.0 --port=8000 as the start command for development-style deploys (fine for low-traffic internal tools, not great for production).

Use a process manager like Octane with FrankenPHP for production (php artisan octane:start --server=frankenphp --host=0.0.0.0 --port=8000). FrankenPHP is fast, stable and the official Octane integration is solid as of Laravel 11.

Set whichever you pick as the Start Command in Coolify's application General tab once we get there.

Set up a Dockerfile if you need fine control

For most Laravel apps the Nixpacks build pack works. But if you have specific extensions to install (like php-imagick for image manipulation or php-redis compiled with specific flags), you'll save yourself pain by writing a Dockerfile. The community has standard Laravel Dockerfile patterns that bundle PHP-FPM with nginx in a single container or use Octane with FrankenPHP for a single-process server. The serversideup PHP Docker images are a solid starting point if you don't want to roll your own.

Make sure your migrations are deploy-safe

If your migrations include destructive operations (dropping columns, renaming tables), think about deploy ordering. Laravel's migration system runs everything in one batch, so if a deploy contains both a schema change and the code that uses it, you need the schema change to land before the new code starts serving traffic. Coolify's pre-deployment command runs migrations before the new container takes over, which handles this for backward-compatible changes. For backward-incompatible ones, you want a multi-step deploy (schema change first, then code change in a separate deploy).

Step 2, set up the project and connect your repo

From the Coolify dashboard, set up a Sources entry for GitHub if you haven't already (Sources → Add → GitHub App, authorize and install on the repo's org or account). The Coolify GitHub guide covers the App setup in detail if you need it. Create a project (call it your app's name) and inside the project click Add a new resource → Private Repository (with GitHub App). Select the repo and branch, let Coolify scan the codebase.

For Laravel, Coolify's Nixpacks detection finds artisan at the repo root, sees PHP, picks the Nixpacks PHP build pack. This handles Composer install, Laravel-specific build steps and the default start command. If you have frontend assets (Vite or Laravel Mix), Nixpacks also runs npm install and npm run build as part of the same build, which is what you want.

Step 3, add Postgres or MySQL

Inside your project, click Add a new resource → Database. Most Laravel apps default to MySQL, but Postgres is increasingly common and Coolify supports both as one-clicks. Pick the version, accept the auto-generated credentials, click Start.

Note the resource name Coolify gives you, this becomes the internal Docker network hostname. We'll wire it into your app's DATABASE_URL in a moment.

If your app uses Postgres, the connection string format is:

DB_CONNECTION=pgsql
DB_HOST=postgres-resource-name
DB_PORT=5432
DB_DATABASE=your-db-name
DB_USERNAME=your-db-user
DB_PASSWORD=your-generated-password

For MySQL, swap pgsql for mysql and 5432 for 3306. Use the resource name as the host, never localhost or 127.0.0.1 from inside the container, which won't reach the database container.

Step 4, add Redis

Same project, click Add a new resource → Database → Redis. Default settings are fine. Click Start.

In your Laravel .env (which becomes Coolify environment variables in a moment), point the cache, session and queue drivers at Redis:

CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_HOST=redis-resource-name
REDIS_PASSWORD=null
REDIS_PORT=6379

Using Redis for cache and sessions is one of those things that's "optional" in Laravel but practically required for a production setup. The default file driver writes to the container's filesystem, which means cache and session data is lost every deploy. The default database driver works but adds load to your Postgres or MySQL instance. Redis is fast and cheap.

Step 5, set environment variables

Open your application in Coolify, go to Environment Variables, click Bulk Edit. Paste your full .env contents minus anything sensitive that you don't want stored in plain text outside of the secrets system (Coolify's env vars are stored encrypted, so this is mostly fine).

Critical variables to verify:

  • APP_KEY must be set to a 32-character base64 string (run php artisan key:generate --show locally and paste the output)
  • APP_ENV should be production
  • APP_DEBUG should be false in production
  • APP_URL should match your final production URL with the protocol (https://app.example.com)
  • DB_* variables match the Postgres/MySQL resource you spun up
  • REDIS_HOST matches the Redis resource name

For multi-line variables (private keys, certificates), uncheck Is build variable on those entries so they only get injected at runtime. Coolify's Dockerfile-based build can choke on multi-line values during build.

Step 6, set up the deploy commands

Laravel needs a few commands to run as part of every deploy. In Coolify's application General tab, find the Pre-deployment Command field and put:

php artisan migrate --force && php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan storage:link

The --force flag on migrate bypasses the production confirmation prompt. config:cache compiles your .env and config files into a single optimized cache file. route:cache does the same for routes. view:cache precompiles Blade templates. storage:link creates the public storage symlink that Laravel needs for file uploads to be web-accessible.

The pre-deployment command runs inside your app's container before the new version takes over from the old one. If any of these commands fail, the deploy fails and the old container keeps serving traffic, which is the behavior you want for safety.

Step 7, deploy and verify

Click Deploy. Coolify pulls the latest commit, runs Composer install, runs npm install and the asset build, runs the pre-deployment command (migrations + cache compilation), starts the new container, waits for the health check, swaps Traefik routing.

The first build is slow because Composer downloads everything from scratch. Subsequent builds use the layer cache and complete in 60 to 120 seconds for a typical Laravel app. Watch the Deployments tab logs for any failures.

Once it's up, the Coolify-generated subdomain works for testing. Hit it, log in, click around. Specific things to verify on first deploy:

  • Database queries return real data (not "no such table" errors)
  • Sessions persist across requests (you stay logged in)
  • File uploads work (try uploading something through your app's UI)
  • Cached config is loading (no APP_KEY errors)
  • Routes resolve (no 404s on routes that should exist)

If file uploads vanish or sessions reset on every deploy, you're missing the persistent storage step in step 8.

Step 8, set up persistent storage for uploads

Laravel's default storage/app/public directory lives inside the container's filesystem. When the container restarts (every deploy or after a crash), anything written there is gone. For user uploads to survive deploys, you need to mount a Docker volume on the storage path.

In your Coolify application's settings, find the Storages section. Add a new persistent storage entry. Mount path inside the container is /var/www/html/storage (assuming your Dockerfile or Nixpacks setup puts the app at /var/www/html, which is the default). Name it something like laravel-storage. Save.

Coolify creates a Docker volume on the host and mounts it into the container at the specified path. Anything Laravel writes to storage/ persists across deploys.

For larger setups, prefer S3-compatible storage instead. Set FILESYSTEM_DISK=s3 in your env and configure the S3 keys (Cloudflare R2, Backblaze B2, Wasabi or self-hosted MinIO all work). Your uploads land outside the VPS entirely, which means your VPS disk doesn't fill up and uploads survive even a full server replacement.

Step 9, run the queue worker as a separate resource

The Laravel queue worker is a long-running process that pulls jobs from Redis and executes them. You don't run it inside your web container because the web container restarts on deploy and you don't want that to interrupt jobs in flight. You run it as a separate Coolify application pointing at the same repo.

Inside your project, click Add a new resource → Private Repository (with GitHub App). Pick the same repo and branch as your web app. In the General tab, set the Start Command to:

php artisan queue:work --tries=3 --max-time=3600

The --max-time=3600 flag tells the worker to gracefully exit after one hour, at which point Docker restarts it. This is a defense against memory leaks and stale database connections, both of which are real failure modes for long-running PHP processes.

Set the same environment variables as the web app (Coolify lets you copy env vars between resources or paste them again from your .env). Skip the pre-deployment command, this resource doesn't need migrations to run, the web app already handled them.

Deploy. Now jobs get processed in a separate container that doesn't restart on web deploys.

If you're using Laravel Horizon for queue management instead of plain queue workers, the start command becomes php artisan horizon and you get the Horizon dashboard at /horizon on your web app. Horizon needs Redis and works well with Coolify's setup.

Step 10, set up the scheduler

Laravel's scheduler runs php artisan schedule:run every minute and dispatches whatever you defined in app/Console/Kernel.php. On a managed host this is usually a one-line cron entry. On Coolify, use the application's Scheduled Tasks feature.

Inside your web app's settings, find Scheduled Tasks. Add a new task with the cron expression * * * * * and the command php artisan schedule:run. Coolify wakes up the container at the scheduled time and runs the command inside the existing container.

This works fine for small to medium apps. For high-frequency or memory-heavy scheduled jobs, run them as a separate worker container similar to the queue worker, with a start command that loops the schedule:

while true; do php artisan schedule:run; sleep 60; done

That keeps the schedule running in a dedicated process without depending on Coolify's task scheduler waking it up each minute.

Step 11, add a custom domain and verify HTTPS

Open the web app in Coolify, go to Domains, add your real domain (app.example.com). Save. Update the A record at your DNS provider to point at your VPS IP.

Coolify's Traefik issues a Let's Encrypt cert within a couple of minutes of DNS resolving. Watch the Logs tab for ACME success. If the cert doesn't issue, the Coolify SSL guide covers the Cloudflare proxy gotcha and other common ACME failures.

Once HTTPS is up, go back to your app's environment variables and confirm APP_URL is set to https://app.example.com (the full HTTPS URL). Laravel uses this for generating signed URLs and redirecting after auth and getting it wrong causes weird mixed-content warnings or broken password reset emails. Redeploy after changing it so the cached config picks up the new value.

Performance tuning, what actually matters

Three things make Laravel meaningfully faster on Coolify, none of which are obvious from the framework docs.

Use Octane with FrankenPHP

Default Laravel deploys boot the framework on every request. Octane keeps the application booted in memory between requests, which makes a typical request 5 to 10 times faster. FrankenPHP is the easiest server to use because it's a single binary that does HTTP and PHP together, no PHP-FPM to manage. Install it as a Composer dependency, set the start command to php artisan octane:start --server=frankenphp and you're done.

The catch is that Octane keeps state in memory, so any code that assumed a fresh boot per request will misbehave (singletons that should be per-request, static variables that accumulate, etc.). The Octane docs have a list of patterns to avoid.

Use Redis aggressively

For cache, sessions and queue we already covered. The often-missed one is using Redis as the lock driver (LOCK_DRIVER=redis) which makes Cache::lock() calls work correctly across multiple worker processes. Default is file which doesn't lock across containers.

Cache the config and routes in production

The pre-deployment command we set up already does config:cache, route:cache and view:cache. These give a measurable speedup on every request because Laravel skips the runtime parsing step. The trade-off is you can't use env() inside config files anywhere except config/*.php, because env() only reads from .env when the cache is being built. Once cached, env() calls return null.

This trips people up because it works fine in development (where you don't cache) and breaks in production. The fix is to never call env() outside config files, always go through config('your.setting').

Common errors and how to fix them

500 error with no useful log on first request

Almost always a missing APP_KEY. Generate one with php artisan key:generate --show locally, paste the output as the APP_KEY environment variable in Coolify, redeploy. Don't commit the key to your repo.

Database connection refused

Your DB_HOST is wrong. Inside Coolify's Docker network, the hostname is the database resource name (visible in the resource's General tab), not localhost or 127.0.0.1. Also confirm the database container is actually running.

Session resets on every page load

Your sessions are using the file driver and writing to the container, which gets reset. Switch to Redis (SESSION_DRIVER=redis) or database (SESSION_DRIVER=database with the sessions table migrated).

File uploads disappear after deploy

You don't have persistent storage mounted on storage/. See step 8 for the volume setup or move uploads to S3-compatible storage entirely.

Queue jobs aren't running

Your queue worker resource isn't deployed or it's deployed but the start command is wrong. Check the worker's Logs tab. The worker should log "Processing: ..." lines when there are jobs to process. If it's silent, the worker is connecting to the wrong Redis instance or the queue connection name doesn't match.

Scheduler not running scheduled tasks

You didn't set up the Scheduled Tasks entry in Coolify or the cron expression is wrong. Add * * * * * as the schedule with command php artisan schedule:run. Verify it's running by adding a temporary schedule->call(fn () => Log::info('hello')) in Kernel.php, deploying and checking your logs after a minute.

Out of memory during build

Composer install can consume significant memory, especially for apps with hundreds of dependencies. Add COMPOSER_MEMORY_LIMIT=-1 as a build variable to lift the Composer memory cap. If the build process itself is OOMing (rare for pure PHP, more common when Vite is bundling a large frontend), see the Node.js heap memory guide for the swap and NODE_OPTIONS approach.

Where to go from here

Once your Laravel app is shipping, the next things to set up are automated database backups (the Coolify database backups guide covers Postgres and MySQL backup to S3-compatible storage), monitoring (the Coolify monitoring guide works for Laravel apps too with the right exporter) and security hardening on the underlying server (the production server security guide covers SSH hardening, firewall rules and Fail2ban which all apply regardless of what runtime you're hosting).

Your idea deserves better hosting

24/7 support 30-day money-back guarantee Cancel anytime
Platební období

1 GB RAM VPS

$3.99 Save  25 %
$2.99 Měsíčně
  • 1 vCPU AMD EPYC
  • 30 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Správa firewallu
  • Monitorování serveru zdarma

2 GB RAM VPS

$5.99 Save  17 %
$4.99 Měsíčně
  • 2 vCPU AMD EPYC
  • 30 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Správa firewallu
  • Monitorování serveru zdarma

6 GB RAM VPS

$14.99 Save  33 %
$9.99 Měsíčně
  • 6 vCPU AMD EPYC
  • 70 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P1

$7.99 Save  25 %
$5.99 Měsíčně
  • 2 vCPU AMD EPYC
  • 4 GB RAM paměť
  • 40 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P2

$14.99 Save  27 %
$10.99 Měsíčně
  • 2 vCPU AMD EPYC
  • 8 GB RAM paměť
  • 80 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P4

$29.99 Save  20 %
$23.99 Měsíčně
  • 4 vCPU AMD EPYC
  • 16 GB RAM paměť
  • 160 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P5

$36.49 Save  21 %
$28.99 Měsíčně
  • 8 vCPU AMD EPYC
  • 16 GB RAM paměť
  • 180 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P6

$56.99 Save  21 %
$44.99 Měsíčně
  • 8 vCPU AMD EPYC
  • 32 GB RAM paměť
  • 200 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P7

$69.99 Save  20 %
$55.99 Měsíčně
  • 16 vCPU AMD EPYC
  • 32 GB RAM paměť
  • 240 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G1

$4.99 Save  20 %
$3.99 Měsíčně
  • 1 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 1 GB DDR5 RAM paměť
  • 25 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G2

$12.99 Save  23 %
$9.99 Měsíčně
  • 2 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 4 GB DDR5 RAM paměť
  • 50 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G4

$25.99 Save  27 %
$18.99 Měsíčně
  • 4 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 8 GB DDR5 RAM paměť
  • 100 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G5

$44.99 Save  33 %
$29.99 Měsíčně
  • 4 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 16 GB DDR5 RAM paměť
  • 150 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G6

$48.99 Save  31 %
$33.99 Měsíčně
  • 8 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 16 GB DDR5 RAM paměť
  • 200 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G7

$74.99 Save  27 %
$54.99 Měsíčně
  • 8 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 32 GB DDR5 RAM paměť
  • 250 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

Frequently asked questions

How do I run Laravel queue workers on Coolify?

Create a separate Coolify application pointing at the same repo as your web app and set its start command to php artisan queue:work --tries=3 --max-time=3600. This runs the worker in its own container that doesn't restart when your web app deploys, so jobs in flight aren't interrupted. The --max-time=3600 flag makes the worker exit gracefully after an hour, after which Docker restarts it, which protects against memory leaks in long-running PHP processes.

Stop installing. Start shipping.

LumaDock Coolify plans come with the dashboard pre-installed, unmetered bandwidth and a flat monthly bill. Try the server risk free with a 30-day refund guarantee.

GPU products are in high demand at the moment. Fill the form to get notified as soon as your preferred GPU server is back in stock.