Back to all posts

Deploy Laravel to Production with Temps

Deploy Laravel with Temps: git-push deploys, automatic HTTPS via Pingora, managed PostgreSQL and Redis, Artisan migrations on every deploy, and built-in monitoring.

Temps Team

Temps Team

June 6, 2026 · 2mo ago

Deploying Laravel to production without shared hosting means you get a reliable server with full control over your stack — but setting up Nginx, PHP-FPM, SSL, queue workers, and scheduled tasks by hand takes hours. Temps collapses that entire stack into a single Rust binary you control: git-push to deploy, automatic HTTPS via Pingora (Cloudflare's open-source proxy), managed PostgreSQL and Redis, Artisan migrations that run on every deploy, and built-in monitoring — free to self-host on the server you already pay for, with a Temps Cloud managed add-on for telemetry retention, offsite backups, and AI credits coming soon.

TL;DR: bunx @temps-sdk/cli deploy my-laravel-app -b main -e production -y — your Laravel app is live with HTTPS, managed database, queue workers, and health-checked deployments in under 5 minutes. Temps is Apache 2.0, self-hostable for free, with a Temps Cloud managed add-on (telemetry retention, offsite backups, AI credits) coming soon.


How Do I Deploy Laravel to Production Without Laravel Forge?

The fastest path: push your code to a git repository, connect it to Temps, and run one CLI command. Temps auto-detects PHP projects, configures Nginx + PHP-FPM, installs Composer dependencies, runs your Artisan migrations, and starts routing encrypted traffic — all without touching a single config file.


What You'll Get

After this tutorial, your Laravel app will have:

  • Nginx + PHP-FPM production stack — the same setup Laravel recommends, configured automatically
  • Artisan migrations on every deployphp artisan migrate --force runs before traffic switches over
  • Managed PostgreSQL with automatic backups and HA support
  • Managed Redis for caching, sessions, and queues
  • Queue workers and scheduler — Laravel Horizon supported
  • Auto-rollback — health checks every 5 seconds, 2 consecutive failures trigger rollback within 60 seconds
  • Automatic HTTPS via Let's Encrypt, renewed without intervention
  • Built-in error tracking and request analytics — no Sentry subscription needed

Prerequisites

  • A Laravel application (8.x or newer)
  • Git repository (GitHub, GitLab, or Bitbucket)
  • composer.json at the project root

Project Structure

Temps works with standard Laravel project layouts:

my-laravel-app/
├── app/
├── bootstrap/
├── config/
├── database/
│   └── migrations/
├── public/              # Document root
├── routes/
├── storage/
├── composer.json
└── .env.example

Your .env file stays local — never commit it. You'll set production variables through the Temps CLI.


Deploy Laravel with the Temps CLI

Step 1: Install Temps CLI

# macOS / Linux
curl -fsSL https://temps.sh/install.sh | bash

Step 2: Login and Create Project

bunx @temps-sdk/cli login

# Create project and connect your git repository
bunx @temps-sdk/cli projects create \
  -n "My Laravel App" \
  -d "Laravel application" \
  --repo your-org/your-laravel-app \
  --branch main \
  --preset php

Temps auto-detects PHP/Laravel projects and configures the Nginx + PHP-FPM stack. Use --preset docker if you already have a Dockerfile.

Step 3: Set Environment Variables

# Application key (generate locally with: php artisan key:generate --show)
bunx @temps-sdk/cli environments vars set APP_KEY "base64:your-key-here" -e production
bunx @temps-sdk/cli environments vars set APP_ENV "production" -e production
bunx @temps-sdk/cli environments vars set APP_DEBUG "false" -e production

# These will be set after provisioning services in Step 4
# bunx @temps-sdk/cli environments vars set DB_CONNECTION "pgsql" -e production
# bunx @temps-sdk/cli environments vars set DATABASE_URL "..." -e production
# bunx @temps-sdk/cli environments vars set REDIS_URL "..." -e production

# Import entire .env.production in one shot
# bunx @temps-sdk/cli environments vars import .env.production -e production

Environment variables are encrypted at rest and injected at runtime — they never appear in image layers.

Step 4: Provision Managed PostgreSQL and Redis

# Create a managed PostgreSQL database
bunx @temps-sdk/cli services create \
  --type postgres \
  --name laravel-db \
  -e production

# Create a managed Redis instance
bunx @temps-sdk/cli services create \
  --type redis \
  --name laravel-cache \
  -e production

Temps provisions the database, generates credentials, and injects DATABASE_URL and REDIS_URL automatically into your environment. Update your config/database.php to read from these:

// config/database.php — PostgreSQL
'pgsql' => [
    'driver' => 'pgsql',
    'url' => env('DATABASE_URL'),
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '5432'),
    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'charset' => 'utf8',
    'prefix' => '',
    'schema' => 'public',
    'sslmode' => 'prefer',
],

Step 5: Deploy

bunx @temps-sdk/cli deploy my-laravel-app -b main -e production -y

Temps will:

  1. Detect your Laravel application
  2. Install Composer dependencies (composer install --no-dev --optimize-autoloader)
  3. Build frontend assets if package.json is present
  4. Run php artisan migrate --force before traffic switches
  5. Start Nginx + PHP-FPM
  6. Provision an SSL certificate via Let's Encrypt

Your app is live at your-app.temps.sh within minutes.


Running Artisan Migrations

Migrations run automatically as part of every deploy. Temps executes php artisan migrate --force after the build succeeds and before routing traffic to the new container. If migrations fail, the deploy halts and the previous version keeps serving traffic.

To run Artisan commands manually:

# Open a shell into the running container
bunx @temps-sdk/cli exec -p my-laravel-app -- php artisan tinker

# Run a specific command
bunx @temps-sdk/cli exec -p my-laravel-app -- php artisan migrate:status

# Seed the database
bunx @temps-sdk/cli exec -p my-laravel-app -- php artisan db:seed

Queue Workers

Basic Queue Worker

Add a worker process to your project:

bunx @temps-sdk/cli workers create \
  --project my-laravel-app \
  --name queue-worker \
  --command "php artisan queue:work --sleep=3 --tries=3" \
  -e production

The worker starts alongside your app and restarts automatically if it crashes.

Laravel Horizon

Temps supports Laravel Horizon for advanced queue monitoring:

# Install Horizon (in your local project)
composer require laravel/horizon
php artisan horizon:install

# Deploy and start Horizon as a worker
bunx @temps-sdk/cli workers create \
  --project my-laravel-app \
  --name horizon \
  --command "php artisan horizon" \
  -e production

Task Scheduler

Run the Laravel scheduler every minute:

bunx @temps-sdk/cli workers create \
  --project my-laravel-app \
  --name scheduler \
  --command "php artisan schedule:run" \
  --schedule "* * * * *" \
  -e production

Storage and File Uploads

For file uploads in production, use S3-compatible storage (Temps Cloud or your own bucket):

bunx @temps-sdk/cli environments vars set FILESYSTEM_DISK "s3" -e production
bunx @temps-sdk/cli environments vars set AWS_ACCESS_KEY_ID "your-key" -e production
bunx @temps-sdk/cli environments vars set AWS_SECRET_ACCESS_KEY "your-secret" -e production
bunx @temps-sdk/cli environments vars set AWS_DEFAULT_REGION "us-east-1" -e production
bunx @temps-sdk/cli environments vars set AWS_BUCKET "your-bucket" -e production
// Store files
Storage::disk('s3')->put('avatar.jpg', $fileContents);

// Generate URL
$url = Storage::disk('s3')->url('avatar.jpg');

Do not rely on the local filesystem for persistent storage in containerized deployments — containers are ephemeral.


Health Checks

Temps polls your application every 5 seconds. Two consecutive failures within the 60-second error window trigger automatic rollback to the last healthy deployment. Add a health route:

// routes/web.php
Route::get('/health', function () {
    return response()->json(['status' => 'ok']);
});

Or in an API context:

// routes/api.php
Route::get('/health', function () {
    return response()->json([
        'status' => 'ok',
        'timestamp' => now()->toISOString(),
    ]);
});

Keep the health endpoint fast — avoid database queries in the health route when possible.


Environment Variables Reference

# Required
bunx @temps-sdk/cli environments vars set APP_KEY "base64:..." -e production
bunx @temps-sdk/cli environments vars set APP_ENV "production" -e production
bunx @temps-sdk/cli environments vars set APP_DEBUG "false" -e production
bunx @temps-sdk/cli environments vars set APP_URL "https://your-app.temps.sh" -e production

# Database (auto-injected when using managed services, or set manually)
bunx @temps-sdk/cli environments vars set DB_CONNECTION "pgsql" -e production
bunx @temps-sdk/cli environments vars set DATABASE_URL "postgresql://..." -e production

# Cache and sessions
bunx @temps-sdk/cli environments vars set CACHE_DRIVER "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
bunx @temps-sdk/cli environments vars set REDIS_URL "redis://..." -e production

# Mail
bunx @temps-sdk/cli environments vars set MAIL_MAILER "smtp" -e production
bunx @temps-sdk/cli environments vars set MAIL_HOST "smtp.example.com" -e production

# List all variables (values hidden)
bunx @temps-sdk/cli environments vars list -e production

Built-in Monitoring

After deployment, your Temps dashboard includes monitoring with no additional subscriptions:

Request Analytics

  • Request count and latency percentiles (p50, p95, p99)
  • Endpoint breakdown with per-route timing
  • Error rates by route
  • Geographic distribution of traffic

Error Tracking

  • Exception capture with full stack traces and request context
  • Error grouping and trend analysis
  • No Sentry subscription needed — it's in the same binary as your proxy

Stream logs from the CLI:

bunx @temps-sdk/cli runtime-logs -p my-laravel-app -f

Scaling

Horizontal Scaling

Scale to multiple replicas:

bunx @temps-sdk/cli environments scale -e production -r 3

Temps handles load balancing automatically via Pingora, Cloudflare's open-source Rust proxy.

Update Resource Allocation

bunx @temps-sdk/cli projects config -p my-laravel-app \
  --cpu-limit 2 \
  --memory-limit 1024 \
  -y
bunx @temps-sdk/cli deploy my-laravel-app -b main -e production -y

Custom Domains

DNS Configuration

Add an A record pointing to your Temps server IP:

TypeNameValue
A@YOUR_SERVER_IP
AwwwYOUR_SERVER_IP

Adding Your Domain

# HTTP-01 challenge (default)
bunx @temps-sdk/cli domains add -d yourdomain.com

# DNS-01 challenge (for wildcard domains)
bunx @temps-sdk/cli domains add -d "*.yourdomain.com" --challenge dns-01

Let's Encrypt certificates are provisioned and renewed automatically.


Comparing Laravel Deployment Options

Temps vs Laravel Forge vs Ploi vs Shared Hosting

FeatureTempsLaravel ForgePloiShared Hosting
Setup time~5 minutes~10 minutes~10 minutes~30 minutes
Nginx + PHP-FPMAutomaticAutomaticAutomaticManual / limited
Managed PostgreSQLBuilt-inExternal providerExternal providerMySQL only (often)
Managed RedisBuilt-inSeparate provisioningSeparate provisioningRarely available
Artisan migrations on deployAutomaticManual deploy hooksDeploy hooksManual SSH
Queue worker managementBuilt-inDaemon config in UIDaemon config in UINot available
Laravel Horizon supportYesYesYesNo
Health checks + auto-rollbackBuilt-in (5s interval)Not includedNot includedNot included
Built-in error trackingYes (no Sentry needed)NoNoNo
Built-in request analyticsYesNoNoLimited
SSL / HTTPSAutomatic (Let's Encrypt)AutomaticAutomaticOften extra cost
Git-push deployYesYesYesFTP/SFTP typically
Self-hostableYes (Apache 2.0, free)NoNoNo
Vendor lock-inNoneForge-managed serversPloi-managed serversComplete
PricingFree self-host (Temps Cloud add-on coming soon)See forge.laravel.com/pricingSee ploi.io/pricingVaries

The main difference: Temps bundles the things you'd otherwise need Forge + Sentry + a separate analytics tool to cover. If you're running multiple Laravel apps and paying per server on Forge, self-hosting Temps often eliminates those per-server subscriptions entirely.


Troubleshooting

Composer Install Fails

Check build logs:

bunx @temps-sdk/cli deploy my-laravel-app -b main -e production

Common issues:

  • Missing PHP extensions (add to your Dockerfile or nixpacks.toml)
  • Private Composer packages (set COMPOSER_AUTH env var with your token)
  • Memory limit during install — the build environment allows up to 512MB

App Shows 500 Error

# Stream live logs
bunx @temps-sdk/cli runtime-logs -p my-laravel-app -f

Common causes:

  • APP_KEY not set — generate with php artisan key:generate --show locally, then set via CLI
  • APP_DEBUG=true in production (not an error, but reveals internals)
  • Missing storage/ permissions — ensure your Dockerfile runs chmod -R 775 storage bootstrap/cache
  • Database connection refused — verify DATABASE_URL is set and the service is running

Migration Fails at Deploy

Migrations run with --force in production. If a migration fails:

  1. Check bunx @temps-sdk/cli runtime-logs -p my-laravel-app for the SQL error
  2. Fix the migration locally and test with a fresh database
  3. Never edit existing migration files — add a new corrective migration instead

Health Check Failures

// routes/web.php — keep this simple and fast
Route::get('/health', fn() => response()->json(['status' => 'ok']));

If your app takes more than 2 seconds to respond under normal load, the health check may time out. Keep the health endpoint free from database and cache queries.


FAQ

Do I need Laravel Forge?

No. Forge is a server management tool that provisions and configures Linux servers for you. Temps does the same — auto-configures Nginx, PHP-FPM, queues, and SSL — but it also includes built-in error tracking, request analytics, and health-checked deployments that Forge doesn't provide. And Temps is self-hostable for free, whereas Forge is a subscription service (see forge.laravel.com/pricing for current pricing).

How do I run Artisan migrate?

Temps runs php artisan migrate --force automatically on every deploy before switching traffic to the new container. To run migrations or any Artisan command manually, use:

bunx @temps-sdk/cli exec -p my-laravel-app -- php artisan migrate --force

Does Temps support Laravel queues?

Yes. You can run a standard queue worker, Laravel Horizon, or the task scheduler as persistent worker processes. Add them with bunx @temps-sdk/cli workers create. Workers restart automatically if they crash, and you can view their logs in the Temps dashboard.

Is Temps free?

Yes — Temps is Apache 2.0 and completely free to self-host on any Linux server. Temps Cloud is a separate, not-yet-shipped managed add-on for telemetry retention, offsite backups, and AI credits (pricing not yet announced). You own your data and can migrate away at any time.

What PHP version does Temps use?

Temps uses Nixpacks to auto-detect your PHP version from composer.json. You can pin a specific PHP version by adding a nixpacks.toml to your project root:

# nixpacks.toml
[phases.setup]
nixPkgs = ["php83", "php83Extensions.pdo", "php83Extensions.pgsql", "php83Extensions.redis"]

Does Temps support Laravel Octane?

Yes — deploy Octane with Swoole or RoadRunner by setting the appropriate OCTANE_SERVER environment variable and adjusting your start command in a Dockerfile:

CMD ["php", "artisan", "octane:start", "--server=swoole", "--host=0.0.0.0", "--port=8080"]

Quick Reference

# Install CLI
curl -fsSL https://temps.sh/install.sh | bash

# Login
bunx @temps-sdk/cli login

# Create project and connect repo
bunx @temps-sdk/cli projects create \
  -n "My Laravel App" \
  -d "Laravel application" \
  --repo myorg/my-laravel-app \
  --branch main \
  --preset php

# Set required env vars
bunx @temps-sdk/cli environments vars set APP_KEY "base64:..." -e production
bunx @temps-sdk/cli environments vars set APP_ENV "production" -e production
bunx @temps-sdk/cli environments vars set APP_DEBUG "false" -e production

# Provision services
bunx @temps-sdk/cli services create --type postgres --name laravel-db -e production
bunx @temps-sdk/cli services create --type redis --name laravel-cache -e production

# Deploy
bunx @temps-sdk/cli deploy my-laravel-app -b main -e production -y

# Stream logs
bunx @temps-sdk/cli runtime-logs -p my-laravel-app -f

# Scale replicas
bunx @temps-sdk/cli environments scale -e production -r 3

# Run Artisan command
bunx @temps-sdk/cli exec -p my-laravel-app -- php artisan migrate:status

# Add domain
bunx @temps-sdk/cli domains add -d yourdomain.com

Ready to deploy your Laravel app? Get started at temps.sh — self-host for free (Apache 2.0), with a managed Temps Cloud add-on coming soon:

curl -fsSL https://temps.sh/install.sh | bash && bunx @temps-sdk/cli login
#laravel#deployment#php#postgresql#redis#self-hosted#tutorial