laravel-deploy

Auto-invoked skill

Configure deployment with zero-downtime, health checks

Trigger Keywords

This skill automatically activates when Claude detects these keywords:

deploy production forge vapor envoy zero-downtime

Overview

The laravel-deploy skill provides expertise for deploying Laravel applications to production. It covers zero-downtime deployments, health checks, environment configuration, and platform-specific setups for Forge, Vapor, and custom servers.

What This Skill Provides

  • Zero-Downtime - Atomic deployments with symlinks
  • Health Checks - Endpoint monitoring and rollback triggers
  • Platform Setup - Forge, Vapor, Ploi, custom VPS
  • Environment Config - Production-ready settings
  • Caching - Config, route, view, and event caching
  • Queue Management - Horizon/worker restart strategies

Example Conversations

# Zero-downtime setup
"Set up zero-downtime deployment with Envoy"

# Health checks
"Add a health check endpoint for my load balancer"

# Production optimization
"What caching commands should I run after deployment?"

# Platform-specific
"Configure my Laravel Forge deployment script"

Health Check Endpoint

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class HealthController extends Controller
{
    public function __invoke()
    {
        $checks = [
            'status' => 'ok',
            'timestamp' => now()->toISOString(),
            'checks' => [
                'database' => $this->checkDatabase(),
                'cache' => $this->checkCache(),
                'redis' => $this->checkRedis(),
            ],
        ];

        $allPassing = collect($checks['checks'])
            ->every(fn ($check) => $check['status'] === 'ok');

        return response()->json($checks, $allPassing ? 200 : 503);
    }

    private function checkDatabase(): array
    {
        try {
            DB::select('SELECT 1');
            return ['status' => 'ok'];
        } catch (\Exception $e) {
            return ['status' => 'fail', 'error' => $e->getMessage()];
        }
    }

    private function checkCache(): array
    {
        try {
            Cache::put('health-check', true, 10);
            Cache::forget('health-check');
            return ['status' => 'ok'];
        } catch (\Exception $e) {
            return ['status' => 'fail', 'error' => $e->getMessage()];
        }
    }

    private function checkRedis(): array
    {
        try {
            Redis::ping();
            return ['status' => 'ok'];
        } catch (\Exception $e) {
            return ['status' => 'fail', 'error' => $e->getMessage()];
        }
    }
}

// routes/web.php
Route::get('/health', HealthController::class);

Zero-Downtime Deploy Script

#!/bin/bash
# deploy.sh - Zero-downtime deployment

set -e

REPO="git@github.com:user/repo.git"
RELEASES_DIR="/var/www/app/releases"
SHARED_DIR="/var/www/app/shared"
CURRENT_LINK="/var/www/app/current"
RELEASE=$(date +%Y%m%d%H%M%S)

# Clone fresh release
git clone --depth 1 $REPO "$RELEASES_DIR/$RELEASE"
cd "$RELEASES_DIR/$RELEASE"

# Link shared files
ln -nfs "$SHARED_DIR/.env" .env
ln -nfs "$SHARED_DIR/storage" storage

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

# Build assets
npm ci && npm run build

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

# Run migrations
php artisan migrate --force

# Atomic switch (zero-downtime moment)
ln -nfs "$RELEASES_DIR/$RELEASE" "$CURRENT_LINK"

# Restart workers
php artisan queue:restart
php artisan horizon:terminate 2>/dev/null || true

# Cleanup old releases (keep last 5)
ls -dt "$RELEASES_DIR"/* | tail -n +6 | xargs rm -rf

echo "Deployed release: $RELEASE"

Laravel Forge Deploy Script

# Forge deployment script
cd /home/forge/example.com

git pull origin $FORGE_SITE_BRANCH

$FORGE_COMPOSER install --no-dev --no-interaction --prefer-dist --optimize-autoloader

( flock -w 10 9 || exit 1
    echo 'Restarting FPM...'; sudo -S service $FORGE_PHP_FPM reload ) 9>/tmp/fpmlock

if [ -f artisan ]; then
    $FORGE_PHP artisan migrate --force
    $FORGE_PHP artisan config:cache
    $FORGE_PHP artisan route:cache
    $FORGE_PHP artisan view:cache
    $FORGE_PHP artisan queue:restart
fi

npm ci
npm run build

Production Environment

# .env.production essentials
APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com

# Performance
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

# Security
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE_COOKIE=strict

# Logging
LOG_CHANNEL=stack
LOG_LEVEL=error

# Database pooling
DB_POOL_MIN=2
DB_POOL_MAX=10

Optimization Commands

Command Purpose
config:cache Cache all config files into one
route:cache Cache route registrations
view:cache Pre-compile all Blade templates
event:cache Cache event-listener mappings
optimize Run all cache commands at once

Common Pitfalls

  • Forgetting queue:restart - Workers cache old code
  • Missing --force - Migrations won't run in production without it
  • APP_DEBUG=true - Exposes sensitive error details
  • No health checks - Load balancers need endpoints to verify health

Rollback Strategy

#!/bin/bash
# rollback.sh - Quick rollback to previous release

RELEASES_DIR="/var/www/app/releases"
CURRENT_LINK="/var/www/app/current"

# Get previous release
PREVIOUS=$(ls -t "$RELEASES_DIR" | sed -n '2p')

if [ -z "$PREVIOUS" ]; then
    echo "No previous release found"
    exit 1
fi

# Atomic switch back
ln -nfs "$RELEASES_DIR/$PREVIOUS" "$CURRENT_LINK"

php artisan queue:restart

echo "Rolled back to: $PREVIOUS"

Related Commands

Related Agent