How to Deploy a Laravel 12 Application to Production in 2026

By LaraSpeed Team · · 18 min read

1. Introduction: Choosing a Deployment Strategy

You've built your Laravel application — now you need to deploy it to production. This is where many developers get stuck. There are dozens of hosting options, and the wrong choice can cost you hours of debugging or hundreds of dollars in unnecessary infrastructure.

In this Laravel deployment guide, we'll cover the four most popular ways to deploy a Laravel 12 app in 2026, along with CI/CD automation, SSL setup, queue workers, and a production checklist you can follow for every launch.

Here's a quick comparison to help you decide:

  • Laravel Forge — Best for most teams. Manages your own server on DigitalOcean, AWS, or Hetzner. $12/mo. Full control, easy setup.
  • Laravel Cloud — Serverless, auto-scaling Laravel hosting by the Laravel team. Best for apps with variable traffic.
  • VPS (manual) — Cheapest option. Full control, but you manage everything yourself. Good for learning.
  • Docker — Best for teams with DevOps experience. Portable, reproducible environments. Works with any cloud provider.

2. Preparing Your Laravel App for Production

Before deploying anywhere, you need to optimize your Laravel app for production. These steps apply regardless of which hosting option you choose.

Environment Configuration

Your production .env should differ significantly from development:

APP_NAME="Your SaaS"
APP_ENV=production
APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
APP_DEBUG=false
APP_URL=https://yourdomain.com

LOG_CHANNEL=stack
LOG_LEVEL=warning

DB_CONNECTION=mysql
DB_HOST=your-db-host
DB_PORT=3306
DB_DATABASE=your_database
DB_USERNAME=your_user
DB_PASSWORD=your_secure_password

CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

REDIS_HOST=127.0.0.1

MAIL_MAILER=ses
MAIL_FROM_ADDRESS=hello@yourdomain.com
MAIL_FROM_NAME="${APP_NAME}"

Critical: Never set APP_DEBUG=true in production. It exposes your entire configuration, database credentials, and stack traces to anyone.

Performance Optimizations

Run these commands as part of every deployment:

# Cache configuration (merges all config files into one)
php artisan config:cache

# Cache routes (skips route registration on every request)
php artisan route:cache

# Cache views (pre-compiles all Blade templates)
php artisan view:cache

# Cache events (auto-discovers event listeners)
php artisan event:cache

# Install production-only Composer dependencies
composer install --no-dev --optimize-autoloader

These commands can reduce your response time by 50-100ms on every request by eliminating file reads and parsing on each boot.

3. Option A: Deploy with Laravel Forge (Recommended)

Laravel Forge is a server management tool built by the Laravel team. It provisions and configures Ubuntu servers on DigitalOcean, AWS, Hetzner, or any custom VPS provider. It's the most popular way to deploy Laravel apps, and for good reason.

Step 1: Create a Server

Sign up at forge.laravel.com, connect your cloud provider (DigitalOcean is the cheapest at $6/mo for a server), and create a new server. Forge automatically installs:

  • PHP 8.3 with all required extensions
  • Nginx as the web server
  • MySQL 8 or PostgreSQL
  • Redis for caching and queues
  • Supervisor for queue workers
  • Let's Encrypt SSL (free)

Step 2: Create a Site & Connect Your Repository

In Forge, create a new site with your domain name. Connect your GitHub repository and Forge will clone it to the server. Set your deploy script:

cd /home/forge/yourdomain.com

git pull origin main

composer install --no-dev --optimize-autoloader

php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

npm ci
npm run build

php artisan queue:restart

Step 3: Enable Auto-Deploy

Toggle "Auto Deploy" in Forge — every push to main triggers an automatic deployment. Forge also supports deploy hooks for Slack notifications, health checks, and custom scripts.

Total cost: Forge ($12/mo) + DigitalOcean Droplet ($6-24/mo) = $18-36/mo for a production-ready Laravel server.

4. Option B: Deploy with Laravel Cloud (Serverless)

Laravel Cloud is Laravel's official serverless hosting platform. It auto-scales your app based on traffic, handles infrastructure automatically, and you only pay for what you use.

# Install the Cloud CLI
composer global require laravel/cloud-cli

# Login to your Cloud account
cloud login

# Initialize your project
cloud init

# Deploy to production
cloud deploy production

Laravel Cloud reads your cloud.yaml configuration:

# cloud.yaml
name: my-saas
environments:
  production:
    build:
      - composer install --no-dev --optimize-autoloader
      - npm ci && npm run build
      - php artisan config:cache
      - php artisan route:cache
      - php artisan view:cache
    workers:
      web:
        command: php-fpm
        scaling:
          min: 1
          max: 10
      queue:
        command: php artisan queue:work --tries=3
        scaling:
          min: 1
          max: 5
    schedule: true
    database: mysql
    cache: redis

Best for: Apps with unpredictable traffic (product launches, marketing campaigns) or teams that don't want to manage servers. Pricing is usage-based.

5. Option C: Deploy to a VPS Manually (Ubuntu + Nginx)

If you want full control and the lowest cost, you can deploy Laravel to a VPS manually. This is what Forge automates, but doing it yourself teaches you exactly how the stack works.

Step 1: Provision the Server

Get an Ubuntu 24.04 VPS from DigitalOcean, Hetzner, or Vultr. SSH in and install the stack:

# Update system
sudo apt update && sudo apt upgrade -y

# Install PHP 8.3 and extensions
sudo add-apt-repository ppa:ondrej/php -y
sudo apt install -y php8.3-fpm php8.3-cli php8.3-mysql \
    php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip \
    php8.3-gd php8.3-redis php8.3-bcmath php8.3-intl

# Install Nginx
sudo apt install -y nginx

# Install MySQL
sudo apt install -y mysql-server
sudo mysql_secure_installation

# Install Redis
sudo apt install -y redis-server

# Install Composer
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

# Install Node.js 22
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs

Step 2: Configure Nginx

Create an Nginx config for your Laravel app:

# /etc/nginx/sites-available/yourdomain.com
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /var/www/yourdomain.com/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;
    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

Enable the site and restart Nginx:

sudo ln -s /etc/nginx/sites-available/yourdomain.com \
           /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

Step 3: Clone and Configure Your App

# Create the web directory
sudo mkdir -p /var/www/yourdomain.com
sudo chown -R $USER:www-data /var/www/yourdomain.com

# Clone your repo
git clone git@github.com:you/your-app.git /var/www/yourdomain.com
cd /var/www/yourdomain.com

# Install dependencies
composer install --no-dev --optimize-autoloader
npm ci && npm run build

# Set permissions
sudo chown -R $USER:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache

# Configure environment
cp .env.example .env
php artisan key:generate
# Edit .env with your production settings

# Run migrations
php artisan migrate --force

# Cache everything
php artisan config:cache
php artisan route:cache
php artisan view:cache

6. Option D: Deploy with Docker & Docker Compose

Docker gives you reproducible, portable deployments. The same container runs identically on your laptop, CI, and production.

# Dockerfile
FROM php:8.3-fpm-alpine

# Install system dependencies
RUN apk add --no-cache \
    nginx supervisor curl zip unzip git \
    libpng-dev libjpeg-turbo-dev libwebp-dev \
    icu-dev oniguruma-dev libzip-dev

# Install PHP extensions
RUN docker-php-ext-configure gd \
        --with-jpeg --with-webp && \
    docker-php-ext-install \
        pdo_mysql mbstring exif pcntl bcmath \
        gd intl zip opcache

# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Set working directory
WORKDIR /var/www/html

# Copy application files
COPY . .

# Install dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction

# Set permissions
RUN chown -R www-data:www-data storage bootstrap/cache

# Cache Laravel config
RUN php artisan config:cache && \
    php artisan route:cache && \
    php artisan view:cache

EXPOSE 9000
CMD ["php-fpm"]

Use Docker Compose to orchestrate all services:

# docker-compose.production.yml
services:
  app:
    build: .
    restart: unless-stopped
    volumes:
      - storage:/var/www/html/storage
    depends_on:
      - mysql
      - redis
    environment:
      - APP_ENV=production

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./public:/var/www/html/public
      - certs:/etc/letsencrypt
    depends_on:
      - app

  mysql:
    image: mysql:8.0
    restart: unless-stopped
    volumes:
      - mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: ${DB_DATABASE}

  redis:
    image: redis:alpine
    restart: unless-stopped

  queue:
    build: .
    restart: unless-stopped
    command: php artisan queue:work --tries=3 --timeout=90
    depends_on:
      - mysql
      - redis

  scheduler:
    build: .
    restart: unless-stopped
    command: sh -c "while true; do php artisan schedule:run; sleep 60; done"
    depends_on:
      - mysql
      - redis

volumes:
  mysql_data:
  storage:
  certs:

Deploy with a single command:

docker compose -f docker-compose.production.yml up -d --build

7. CI/CD with GitHub Actions

Automate your testing and deployment with GitHub Actions. This workflow runs your tests on every push and deploys to production when you merge to main:

# .github/workflows/deploy.yml
name: Test & Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: testing
        ports: ['3306:3306']
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          extensions: mbstring, mysql, redis
          coverage: none

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist

      - name: Run tests
        run: php artisan test
        env:
          DB_CONNECTION: mysql
          DB_HOST: 127.0.0.1
          DB_PORT: 3306
          DB_DATABASE: testing
          DB_USERNAME: root
          DB_PASSWORD: password

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - name: Deploy to Forge
        uses: jbrooksuk/laravel-forge-action@v1
        with:
          trigger_url: ${{ secrets.FORGE_DEPLOY_URL }}

How it works: Every push to any branch runs the test suite. When tests pass and you push to main, it triggers a deployment via Forge's deploy webhook. No manual SSH needed.

8. SSL Certificates & Custom Domains

Every production app needs HTTPS. Here's how to set it up depending on your deployment method:

Laravel Forge

One click. Go to your site in Forge, click "SSL", and choose "Let's Encrypt". Forge installs the certificate and configures auto-renewal. Done.

Manual VPS with Certbot

# Install Certbot
sudo apt install -y certbot python3-certbot-nginx

# Get certificate (automatically configures Nginx)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Verify auto-renewal
sudo certbot renew --dry-run

Certbot adds a cron job that automatically renews your certificate before it expires.

Docker with Traefik

If you're using Docker, consider adding Traefik as a reverse proxy. It handles SSL certificate issuance and renewal automatically with Let's Encrypt:

# Add to docker-compose.production.yml
  traefik:
    image: traefik:v3.0
    command:
      - "--providers.docker=true"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=you@yourdomain.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - letsencrypt:/letsencrypt

9. Running Queues, Cron & Scheduler in Production

Most SaaS apps use background jobs for sending emails, processing webhooks, generating reports, and more. Here's how to run them reliably in production.

Queue Worker with Supervisor

Supervisor keeps your queue worker running and restarts it if it crashes:

# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/yourdomain.com/artisan queue:work redis
    --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=forge
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/yourdomain.com/storage/logs/worker.log
stopwaitsecs=3600
# Apply the configuration
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*

Laravel Scheduler (Cron)

Add a single cron entry that runs every minute:

# Open crontab
crontab -e

# Add this line
* * * * * cd /var/www/yourdomain.com && php artisan schedule:run >> /dev/null 2>&1

All scheduled tasks defined in your routes/console.php or app/Console/Kernel.php will run at their configured intervals. No need for multiple cron entries.

Important: After every deployment, restart queue workers so they pick up the new code:

php artisan queue:restart

10. Zero-Downtime Deployments

Nobody wants their SaaS to show errors during deployment. Zero-downtime deployment means your users never see an interruption.

With Laravel Forge

Forge supports zero-downtime deployments natively. Enable it in your site settings — Forge creates a new release directory, builds everything there, then swaps the symlink atomically.

With Deployer (Manual VPS)

Deployer is a PHP deployment tool that implements the release-directory pattern:

# Install Deployer
composer global require deployer/deployer

# deploy.php
namespace Deployer;

require 'recipe/laravel.php';

host('yourdomain.com')
    ->set('remote_user', 'forge')
    ->set('deploy_path', '/var/www/yourdomain.com');

set('repository', 'git@github.com:you/your-app.git');

after('deploy:failed', 'deploy:unlock');

// Deploy
// dep deploy production

Deployer creates versioned release directories, runs your build process in the new directory, and only switches the current symlink when everything succeeds. If the deployment fails, the old version keeps running.

11. Monitoring, Logging & Error Tracking

You can't fix what you can't see. Set up monitoring from day one:

  • Error tracking: Use Sentry, Flare, or Bugsnag to catch exceptions in real-time. Install with composer require sentry/sentry-laravel and add your DSN to .env.
  • Uptime monitoring: Use Oh Dear, UptimeRobot, or Better Uptime to get alerted when your site goes down.
  • Application performance: Laravel Telescope for development, Laravel Pulse for production. Pulse gives you a real-time dashboard of slow queries, cache hits, queue throughput, and more.
  • Server monitoring: Forge includes basic server metrics. For deeper monitoring, use Netdata or the DigitalOcean/AWS monitoring dashboards.
  • Log aggregation: Ship logs to Papertrail, Logtail, or Datadog for centralized search and alerting.

At minimum, configure structured logging so you can search and filter your logs:

// config/logging.php — use the 'stack' channel
'stack' => [
    'driver'   => 'stack',
    'channels' => ['daily', 'stderr'],
],

'daily' => [
    'driver' => 'daily',
    'path'   => storage_path('logs/laravel.log'),
    'level'  => 'warning',
    'days'   => 14,
],

12. Production Deployment Checklist

Run through this checklist before every launch:

  1. APP_DEBUG=false — Never expose debug info in production.
  2. APP_KEY is set — Run php artisan key:generate if not.
  3. HTTPS is enabled — SSL certificate installed and force-HTTPS configured.
  4. Database migrations runphp artisan migrate --force.
  5. Caches are warm — Config, routes, views, and events are cached.
  6. Queue worker is running — Supervisor is configured and active.
  7. Scheduler cron is setschedule:run runs every minute.
  8. Error tracking is live — Sentry/Flare is configured and tested.
  9. Uptime monitoring is active — You'll get alerts if the site goes down.
  10. Backups are scheduled — Database backups run daily with spatie/laravel-backup.
  11. Stripe webhooks point to production — Update the endpoint URL and webhook secret.
  12. Email sending works — Test transactional emails (welcome, password reset, invoices).
  13. DNS is configured — A record or CNAME points to your server's IP.
  14. Storage symlink existsphp artisan storage:link.

13. Conclusion

You now know how to deploy a Laravel application to production using four different methods: Laravel Forge, Laravel Cloud, a manual VPS, and Docker. You also have CI/CD automation with GitHub Actions, zero-downtime deployments, and a production checklist.

For most SaaS applications, we recommend Laravel Forge + DigitalOcean for simplicity and cost-effectiveness. If you need auto-scaling, go with Laravel Cloud. If you have DevOps expertise, Docker gives you maximum portability.

LaraSpeed is deployment-ready out of the box. It ships with a production Dockerfile, Nginx config, CI/CD workflow, queue worker configuration, and all the optimizations covered in this guide. You just connect your server and deploy.

Ship your SaaS today, not next month

LaraSpeed gives you a production-ready Laravel SaaS with authentication, billing, teams, admin panel, deployment configs — everything wired together and ready to launch.

Get LaraSpeed — Starting at $49

Ready to Ship Your SaaS?

Skip the weeks of boilerplate setup. Get a production-ready Laravel SaaS foundation with full source code, one-time purchase.

Get LaraSpeed — Starting at $49

One-time purchase · 14-day money-back guarantee