Deploy / Laravel
Deploy Laravel to Your Own Server
To deploy Laravel to your own server, install Temps on a Linux VPS and run up --preset nixpacks-php from the project root. Temps installs Composer dependencies and serves public/ with FrankenPHP on the PORT it injects, behind automatic HTTPS. You link PostgreSQL or Redis, declare their PHP extensions in composer.json, and run php artisan migrate yourself after deploys that change the schema.
Last updated
Preset required
Choose the Autopack (PHP) preset (nixpacks-php). A composer.json on its own isn't a detection signal; without a preset the build stops with "Could not auto-detect project type from files".
How to deploy Laravel with Temps
Commands run on your machine unless they say otherwise. The CLI runs with bunx or npx.
- 1
Install Temps on a Linux server
On a VPS with at least 2 CPU cores and 4 GB of RAM, download the installer, read it, then run it. It installs Docker if needed, the Temps binary and its database, and asks how the console is reached: an sslip.io address or your own domain.
curl -fsSL https://temps.sh/deploy.sh -o deploy.sh less deploy.sh bash deploy.sh
- 2
Log in from your machine
Point the CLI at your Temps console. It opens a browser to approve the login.
bunx @temps-sdk/cli login https://console.example.com
- 3
Read the connection Temps injects
Temps injects
POSTGRES_URL, notDB_URLorDATABASE_URL. Point thepgsqlconnection at it and setDB_CONNECTION=pgsql.config/database.php'pgsql' => [ 'driver' => 'pgsql', 'url' => env('POSTGRES_URL'), // ...keep the rest of the connection settings ], - 4
Declare the PHP extensions you use
Temps adds the PHP extensions listed as
ext-*in therequiresection ofcomposer.jsonto the image. Withoutext-pdo_pgsql, pages that query PostgreSQL fail with "could not find driver"; phpredis, Laravel's default Redis client, needsext-redis.composer require ext-pdo_pgsql:* composer require ext-redis:* # only with a linked Redis
- 5
Commit your built assets (beta.56)
beta.56 doesn't run a front-end build for PHP projects, and a page that uses
@vitereturns 500 withoutpublic/build/manifest.json. Build locally, take/public/buildout of.gitignoreand commit the folder, or deploy with your own Dockerfile. Nightly builds, and the next release, run yourbuildscript during the deploy.npm run build git add public/build
- 6
Trust the proxy
Temps terminates HTTPS at its proxy, so trust forwarded headers or Laravel generates
http://URLs.bootstrap/app.php->withMiddleware(function (Middleware $middleware) { $middleware->trustProxies(at: '*'); }) - 7
Set APP_KEY and APP_URL
Generate the key locally and store it as a project variable; never commit it.
bunx @temps-sdk/cli environments vars set APP_KEY "$(php artisan key:generate --show)" -e production bunx @temps-sdk/cli environments vars set APP_URL "https://app.example.com" -e production
- 8
Point the health check at a path that doesn't need the database
Before switching traffic, Temps probes
/until it answers. On the first deploy the tables don't exist yet, so if/queries the database the deploy fails before you can migrate. Sethealth.pathto/upin.temps.yaml. Laravel 11 and later register/upinbootstrap/app.php; on older versions, add a route that returns 200..temps.yamlhealth: path: /up
- 9
Deploy Laravel
From the project root, run
upwith the preset, or set it under Project → Settings → Build & deploy → Build → Framework. If the directory isn't linked to a project yet,upwalks you through creating one first. For a project created from a Git repository, pushes to the tracked branch deploy automatically.bunx @temps-sdk/cli up --preset nixpacks-php
- 10
Create and link a PostgreSQL database
Temps doesn't create a database for you. Create one, then link it to the project (or use Databases → Create database in the dashboard). Linking injects
POSTGRES_URLplusPOSTGRES_HOST,POSTGRES_PORT,POSTGRES_DB,POSTGRES_USERandPOSTGRES_PASSWORDinto the next deployment. It doesn't injectDATABASE_URL.bunx @temps-sdk/cli services create -t postgres -n my-laravel-app-db bunx @temps-sdk/cli services link --id <service-id> -p my-laravel-app
- 11
Run migrations after deploys that change the schema
Temps doesn't run migrations and has no release phase. Find the running container, then run the migration in it over SSH, by hand or from CI right after the deploy step. Don't add it to the start command: restarts and replicas would run it more than once.
bunx @temps-sdk/cli projects show -p my-laravel-app --json # note the numeric "id" bunx @temps-sdk/cli containers list -p <project-id> # note the container_name ssh your-server 'docker exec <container_name> php artisan migrate --force'
Production configuration for Laravel on Temps
Laravel reads its configuration from environment variables, so production settings are project variables, not a committed .env. The Laravel reference lists what the build does for you.
# Set once per environment bunx @temps-sdk/cli environments vars set APP_DEBUG false -e production bunx @temps-sdk/cli environments vars set LOG_CHANNEL stderr -e production bunx @temps-sdk/cli environments vars set DB_CONNECTION pgsql -e production # With a linked Redis (REDIS_URL is injected) bunx @temps-sdk/cli environments vars set CACHE_STORE redis -e production bunx @temps-sdk/cli environments vars set SESSION_DRIVER redis -e production bunx @temps-sdk/cli environments vars set QUEUE_CONNECTION redis -e production
Redis for cache, sessions and queues
Laravel's default Redis connection already reads REDIS_URL, which Temps injects when you link a managed Redis. Its default client, phpredis, is a PHP extension: add ext-redis to composer.json so the build includes it.
// config/database.php — Laravel's default Redis connection
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'default' => [
'url' => env('REDIS_URL'),
// ...keep the rest of the connection settings
],
],Uploads on S3-compatible storage
Containers start from a clean filesystem on every deploy, so keep uploads in a bucket. A managed S3 service on Temps works with Laravel's s3 disk once you add the bucket and endpoint.
# With a linked managed S3 service: AWS_ACCESS_KEY_ID, # AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION are injected for you. bunx @temps-sdk/cli environments vars set FILESYSTEM_DISK s3 -e production bunx @temps-sdk/cli environments vars set AWS_BUCKET my-uploads -e production bunx @temps-sdk/cli environments vars set AWS_ENDPOINT "<the S3_ENDPOINT value>" -e production bunx @temps-sdk/cli environments vars set AWS_USE_PATH_STYLE_ENDPOINT true -e production
Common Laravel deploy errors and fixes
- The build stops with "Could not auto-detect project type from files".
- A
composer.jsonisn't a detection signal. Deploy withup --preset nixpacks-php, or set the framework under Project → Settings → Build & deploy → Build. - Every request returns 500: "No application encryption key has been specified".
APP_KEYis missing. Generate it locally withphp artisan key:generate --showand set it as a project variable, a different key per environment.- Links, redirects and pagination use
http://. - Temps terminates HTTPS at its proxy. Trust forwarded headers with
$middleware->trustProxies(at: '*')inbootstrap/app.php, and setAPP_URLto thehttps://address. - Jobs are dispatched but never run.
- Temps runs the web process only;
worker:,scheduler:andhorizon:Procfile lines are ignored. Runqueue:workor Horizon as a service in a Docker Compose project, or outside Temps, against the same Redis. - Pages that query the database fail with "could not find driver".
- The image has no
pdo_pgsqlextension, becausecomposer.jsondoesn't ask for it. Runcomposer require ext-pdo_pgsql:*, commitcomposer.jsonandcomposer.lock, and deploy again. Class "Redis" not found.- phpredis, Laravel's default Redis client, is a PHP extension that Temps only builds when
composer.jsonlists it. Runcomposer require ext-redis:*and deploy again, or install the pure-PHP client withcomposer require predis/predisand setREDIS_CLIENT=predis. - Pages return 500 with "Vite manifest not found".
- beta.56 doesn't build front-end assets for PHP projects. Commit
public/buildafternpm run build, or build the assets in your own Dockerfile. Nightly builds, and the next release, run yourbuildscript. - Uploaded files vanish after a deploy, or
/storagelinks return 404. - App containers have no persistent volume, so the
publicdisk is wiped on each deploy. Store uploads on an S3 disk, as above. - Composer can't install a private package.
- Set
COMPOSER_AUTHas a project variable with the JSON credentials Composer expects, for example a GitHub token.
How Temps builds and runs Laravel
Every app runs in a Docker container on your server, behind the Temps proxy. Before traffic switches to a new deployment, Temps health-checks it; if the check fails, the deployment is marked failed and the current version keeps serving.
| Install | composer install --no-dev, then composer dump-autoload --optimize |
|---|---|
| Web server | FrankenPHP, one process that is both the web server and the PHP runtime, serving public/; a Procfile web: line replaces the start command |
| PHP | The first version in the require.php constraint in composer.json (^8.2 gives 8.2), or 8.3 without one. From the next release: the newest version composer.json and composer.lock both allow |
| Extensions | Built from the ext-* entries in composer.json require |
| Assets | Not built on beta.56: commit public/build, or build it in your own Dockerfile. Nightly builds, and the next release, run your build script (vite build) |
| Artisan caches | config:cache and route:cache aren't run; Laravel works without them |
| Environment | APP_ENV=production is set for you |
| Migrations | Not run by Temps; run php artisan migrate --force yourself |
What works with Laravel on Temps
- FrankenPHP serving
public/ - PHP extensions built from
ext-*incomposer.json - Managed PostgreSQL and Redis you link
- Your own start command with a Procfile
web:line - Health check before each cutover
- Preview environments per branch (opt-in)
Managed services for Laravel
Temps runs these services on your server. Create one, link it to the project, and its connection variables reach the next deployment. Set up managed services.
PostgreSQL
Injects POSTGRES_URL and POSTGRES_HOST/PORT/DB/USER/PASSWORD once linked
Redis
Injects REDIS_URL for cache, sessions and queues
Environment variables for Laravel
Set your own with environments vars set or in the project settings; values are encrypted at rest.
| Variable | What it does |
|---|---|
| APP_KEY | From php artisan key:generate --show |
| APP_ENV / APP_URL | production and your public https:// URL |
| DB_CONNECTION | pgsql |
| LOG_CHANNEL | stderr, so Laravel's logs reach the container logs; set for you from the next release |
| POSTGRES_URL / REDIS_URL | Injected by Temps when the services are linked |
| QUEUE_CONNECTION | redis or database; never sync in production |
What it costs to self-host Laravel
Temps is free to self-host under MIT / Apache 2.0, with no per-seat fee and nothing metered, so hosting Laravel costs what the server costs. A Hetzner CX23 (2 shared vCPU, 4 GB RAM, 40 GB NVMe, 20 TB traffic) meets the minimum Temps needs and is €5.49/month excluding VAT, plus €0.50/month for an IPv4 address; Hetzner sells it in Germany and Finland only. Prices checked October 10, 2026.
| Temps | Free and open source (MIT / Apache 2.0) |
|---|---|
| Server | Hetzner CX23: €5.49/month excluding VAT, plus €0.50/month for IPv4 |
| What Laravel runs | One FrankenPHP container, plus the managed PostgreSQL and Redis you link, on the same server. Queue workers and Horizon need their own service in a Docker Compose project. |
| Observability | Web analytics, error tracking, session replay and uptime monitoring are included and run on the same server |
| Temps Premium | Optional: $399 per organisation per month for SAML SSO, SCIM, custom roles, approval gates and a tamper-evident audit log |
For one to three production apps, the server guide recommends 4 vCPU and 8 GB RAM: a Hetzner CX33 is €8.49/month excluding VAT. Any Linux VPS that meets the minimum works; sizes and other providers are in Choosing a server. For what the same app costs on Heroku, see Temps vs Heroku.
Laravel on Temps: frequently asked questions
Does Temps run Laravel migrations automatically?
No. Run php artisan migrate --force after a deploy that changes the schema, with docker exec over SSH or from CI. Don't put it in the start command or a Procfile web: line, where restarts and replicas would run it more than once.
How do I run Laravel queue workers and the scheduler on Temps?
Temps runs the web process only and ignores Procfile lines other than web:, so queue:work, schedule:work and Horizon don't run as separate processes. Run them as services in a Docker Compose project, or outside Temps, against a linked Redis with QUEUE_CONNECTION=redis.
How do I fix "No application encryption key has been specified"?
APP_KEY is missing. Generate one with php artisan key:generate --show, set it as a project variable, and deploy again. Each environment should have its own key.
Where should Laravel store uploaded files on Temps?
Not on the local disk: app containers have no persistent volume, so files written there disappear on the next deploy. Use an S3 disk. A managed S3 service linked on Temps injects S3_* variables plus AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION; set AWS_BUCKET, AWS_ENDPOINT (the S3_ENDPOINT value) and AWS_USE_PATH_STYLE_ENDPOINT=true yourself.
How do I see Laravel errors and logs on Temps?
Set LOG_CHANNEL=stderr as a project variable and stream the output with bunx @temps-sdk/cli runtime-logs -p my-laravel-app -f. For exceptions with stack traces, point the Sentry Laravel SDK at the SENTRY_DSN Temps injects; the project's built-in error tracking receives them.
How do I run one-off Artisan commands on Temps?
Through Docker on your server: find the container with bunx @temps-sdk/cli containers list -p <project-id>, then run ssh your-server 'docker exec <container_name> php artisan <command>'. The CLI has no remote exec command yet.
Included with every Temps project
Deployments, analytics, error tracking, session replay and uptime monitoring run in the same self-hosted platform, so your Laravel app doesn't need separate subscriptions for them.
Automatic HTTPS
Let's Encrypt certificates for every domain, issued and renewed for you.
Preview deployments
Turn on preview environments and each branch gets its own URL on your domain.
Web analytics
First-party analytics stored on your server. Add the React provider or a script tag.
Error tracking
Sentry-compatible: any Sentry SDK can report to the SENTRY_DSN Temps injects.
Session replay
Off by default. When on, it records the DOM with every input masked.
Uptime monitoring
HTTP monitors for your apps, with alerts through your notification channels.
Related guides
- Every setting for this stack is in the Laravel deployment reference.
- Which stacks are detected and which need a preset is listed in supported languages and frameworks.
- How health checks, cutover and rollbacks work is covered in Deployments.
- Moving off Heroku? Follow Migrate from Heroku.
- Comparing self-hosted platforms? See Temps vs Coolify.
Deploy another framework
Deploy Laravel on a server you own
Temps is free and open source to self-host (MIT / Apache 2.0). You pay only for the server.