MidПрактика9 min

Деплой Laravel

Лучшие практики деплоя, Forge, Vapor, оптимизация, php artisan optimize, серверные требования

Требования к серверу

Минимальная серверная конфигурация

Компонент Требование
PHP 8.2+ с необходимыми расширениями
Веб-сервер Nginx (рекомендуется) или Apache
База данных MySQL 8.0+, PostgreSQL 13+, SQLite 3.35+
Composer 2.x
Node.js 18+ (для сборки assets)
Права на запись storage/, bootstrap/cache/

Необходимые PHP-расширения

# Required PHP extensions for production
php -m | grep -E "(bcmath|ctype|curl|dom|fileinfo|json|mbstring|openssl|pcre|pdo|tokenizer|xml)"

# Additional recommended extensions
# - Redis (phpredis) — for cache/sessions/queues
# - Imagick or GD — for image processing
# - Zip — for file compression
# - Intl — for internationalization
# - OPcache — for performance (critical!)

Конфигурация веб-сервера

Nginx (рекомендуемый)

server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    root /var/www/my-app/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.4-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;
    }
}

Для экзамена: Ключевая директива Nginx -- try_files $uri $uri/ /index.php?$query_string;. Она обеспечивает паттерн front controller: все запросы, которые не соответствуют реальным файлам, перенаправляются на index.php.

Apache (.htaccess)

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect Trailing Slashes If Not A Folder
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Send Requests To Front Controller
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

Оптимизация для продакшена

Команда php artisan optimize

# Laravel 11: single optimization command
php artisan optimize

# This command runs:
# 1. config:cache — Cache configuration
# 2. route:cache — Cache routes
# 3. view:cache — Compile and cache views
# 4. event:cache — Cache event-listener mappings

# Clear all caches
php artisan optimize:clear

# This command runs:
# 1. config:clear
# 2. route:clear
# 3. view:clear
# 4. event:clear
# 5. cache:clear

Кэширование конфигурации

# Cache all config files into a single file
php artisan config:cache

# Benefits:
# - Single file load instead of ~15 separate config files
# - No .env file parsing on each request
# - ~50% faster configuration loading

# IMPORTANT: After config:cache, env() returns null
# outside of config/ files

# Clear config cache
php artisan config:clear

Кэширование маршрутов

# Cache all routes into a single file
php artisan route:cache

# Benefits:
# - Routes loaded from a single cached file
# - Significant performance improvement for large apps

# LIMITATION: Route caching does NOT work with Closure routes!
# Only controller-based routes can be cached

# Clear route cache
php artisan route:clear
<?php
declare(strict_types=1);

// WRONG — Closure routes CANNOT be cached
Route::get('/dashboard', function () {
    return view('dashboard');
});

// CORRECT — Controller routes CAN be cached
Route::get('/dashboard', [DashboardController::class, 'index']);

// CORRECT — Invokable controller
Route::get('/dashboard', DashboardController::class);

Ловушка экзамена: Команда php artisan route:cache НЕ работает с маршрутами-замыканиями (Closure). Если в файлах маршрутов есть хотя бы одно замыкание, команда выбросит ошибку. Для кэширования все маршруты должны ссылаться на методы контроллеров.

Кэширование представлений

# Pre-compile all Blade views
php artisan view:cache

# Benefits:
# - All Blade templates compiled ahead of time
# - No compilation overhead on first request
# - Faster time-to-first-byte (TTFB)

# Clear compiled views
php artisan view:clear

Кэширование событий

# Cache the event-listener mapping
php artisan event:cache

# Benefits:
# - Event discovery results cached
# - No directory scanning on each request

# Clear event cache
php artisan event:clear

Оптимизация Composer

# CRITICAL for production
# Generate optimized autoloader
composer install --optimize-autoloader --no-dev

# Flags explained:
# --optimize-autoloader (-o): Generate classmap for PSR-4 autoloading
#   → Converts PSR-4/PSR-0 rules to classmap for faster lookups
# --no-dev: Skip dev dependencies (phpunit, faker, etc.)
#   → Reduces vendor/ size significantly

# For maximum performance:
composer dump-autoload --optimize --classmap-authoritative

# --classmap-authoritative: Only look for classes in classmap
#   → Even faster but classes not in classmap won't be found
#   → Use only if all classes are known at build time

Для Senior: На продакшене ВСЕГДА используйте --optimize-autoloader --no-dev. Флаг --classmap-authoritative ещё быстрее, но несовместим с пакетами, которые генерируют классы в рантайме (например, некоторые ORM прокси-объекты).

OPcache -- критически важно

; php.ini — OPcache settings for production
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0         ; CRITICAL: disable in production
opcache.save_comments=1               ; Required for annotations/attributes
opcache.enable_file_override=1

; JIT (PHP 8.4+)
opcache.jit=1255
opcache.jit_buffer_size=128M
<?php
declare(strict_types=1);

// OPcache preloading (PHP 8.0+)
// config/opcache.php or preload.php

// In php.ini:
// opcache.preload=/var/www/my-app/preload.php
// opcache.preload_user=www-data

// preload.php
require __DIR__ . '/vendor/autoload.php';

// Preload frequently used classes
// Laravel provides a built-in preload script in some configs
// This loads framework classes into OPcache at startup

Важно: opcache.validate_timestamps=0 означает, что PHP не будет проверять изменения файлов. После деплоя нужно перезагрузить PHP-FPM (systemctl reload php8.4-fpm) или вызвать opcache_reset().

Процесс деплоя

Типичный сценарий деплоя

#!/bin/bash
# deploy.sh — Standard Laravel deployment script

set -e

APP_DIR="/var/www/my-app"

echo "Starting deployment..."

# 1. Pull latest code
cd $APP_DIR
git pull origin main

# 2. Install PHP dependencies (production)
composer install --no-dev --optimize-autoloader --no-interaction

# 3. Install and build frontend assets
npm ci --production
npm run build

# 4. Run database migrations
php artisan migrate --force

# 5. Clear and rebuild caches
php artisan optimize:clear
php artisan optimize

# 6. Restart queue workers (if using)
php artisan queue:restart

# 7. Restart PHP-FPM to clear OPcache
sudo systemctl reload php8.4-fpm

echo "Deployment complete!"

Zero-downtime деплой

#!/bin/bash
# Zero-downtime deployment with symlinks

RELEASE_DIR="/var/www/releases/$(date +%Y%m%d%H%M%S)"
SHARED_DIR="/var/www/shared"
CURRENT_LINK="/var/www/current"

# 1. Create new release directory
mkdir -p $RELEASE_DIR
git clone --depth 1 --branch main [email protected]:user/repo.git $RELEASE_DIR

# 2. Link shared resources
ln -sf $SHARED_DIR/.env $RELEASE_DIR/.env
ln -sf $SHARED_DIR/storage $RELEASE_DIR/storage

# 3. Install dependencies
cd $RELEASE_DIR
composer install --no-dev --optimize-autoloader --no-interaction
npm ci && npm run build

# 4. Run migrations
php artisan migrate --force

# 5. Cache everything
php artisan optimize

# 6. Swap symlink (atomic operation)
ln -sfn $RELEASE_DIR $CURRENT_LINK

# 7. Restart workers and PHP-FPM
php artisan queue:restart
sudo systemctl reload php8.4-fpm

# 8. Clean up old releases (keep last 5)
cd /var/www/releases
ls -t | tail -n +6 | xargs rm -rf

Для Senior: Zero-downtime деплой с симлинками -- это стандартная практика для продакшен-серверов. Операция ln -sfn атомарна на уровне файловой системы, что обеспечивает мгновенное переключение без простоя. Инструменты вроде Deployer, Envoyer и Capistrano автоматизируют этот процесс.

Laravel Forge

<?php
declare(strict_types=1);

// Laravel Forge — server management platform
// https://forge.laravel.com

// Key features:
// - Automated server provisioning (DigitalOcean, AWS, Linode, etc.)
// - Nginx configuration
// - SSL certificates (Let's Encrypt)
// - PHP version management
// - Database management
// - Queue worker management (Supervisor)
// - Scheduled tasks (cron)
// - Zero-downtime deployments
// - Server monitoring

// Forge deploy script (configured in web UI):
// cd /home/forge/example.com
// git pull origin $FORGE_SITE_BRANCH
// $FORGE_COMPOSER install --no-dev --no-interaction --prefer-dist --optimize-autoloader
// npm ci && npm run build
// $FORGE_PHP artisan migrate --force
// $FORGE_PHP artisan optimize
// $FORGE_PHP artisan queue:restart

Laravel Vapor

<?php
declare(strict_types=1);

// Laravel Vapor — serverless deployment on AWS Lambda
// https://vapor.laravel.com

// Key features:
// - Serverless (no server management)
// - Auto-scaling (handles traffic spikes automatically)
// - AWS Lambda for compute
// - AWS RDS for databases
// - AWS S3 for file storage
// - AWS SQS for queues
// - AWS CloudFront for CDN
// - Environment management

// vapor.yml — deployment configuration
// id: 12345
// name: my-app
// environments:
//   production:
//     memory: 1024
//     cli-memory: 512
//     runtime: php-8.4
//     build:
//       - 'composer install --no-dev'
//       - 'npm ci && npm run build'
//     deploy:
//       - 'php artisan migrate --force'
//       - 'php artisan optimize'
//   staging:
//     memory: 512
//     runtime: php-8.4
//     build:
//       - 'composer install'
//       - 'npm ci && npm run build'

Переменные окружения для продакшена

# Production .env — critical settings
APP_ENV=production
APP_DEBUG=false                # NEVER true in production!
APP_URL=https://example.com

# Logging — stack with daily rotation
LOG_CHANNEL=stack
LOG_LEVEL=warning             # Don't log debug/info in production

# Cache — use Redis for performance
CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

# Database
DB_CONNECTION=pgsql
DB_HOST=db-server.example.com
DB_PORT=5432
DB_DATABASE=myapp_production
DB_USERNAME=myapp
DB_PASSWORD=secure_password_here

# Redis
REDIS_HOST=redis-server.example.com
REDIS_PASSWORD=redis_password_here

# Mail — production mail service
MAIL_MAILER=ses              # Amazon SES, Postmark, etc.

Критически важно: APP_DEBUG=false на продакшене. Если APP_DEBUG=true, пользователи увидят стектрейсы с путями к файлам, переменными окружения и данными подключения к БД. Это серьёзная уязвимость безопасности.

Очереди на продакшене

Supervisor для queue workers

; /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/my-app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopastype=KILL
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/my-app/storage/logs/worker.log
stopwaitsecs=3600
# Start Supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start "laravel-worker:*"

# After deployment, restart queue workers
php artisan queue:restart
# This signals workers to finish current job and restart
# Workers will pick up new code after restart

Планировщик задач (Cron)

# Add to crontab (crontab -e)
* * * * * cd /var/www/my-app && php artisan schedule:run >> /dev/null 2>&1
<?php
declare(strict_types=1);

// routes/console.php (Laravel 11)
use Illuminate\Support\Facades\Schedule;

// Schedule tasks
Schedule::command('telescope:prune --hours=48')->daily();
Schedule::command('queue:prune-failed --hours=72')->daily();
Schedule::command('backup:run')->dailyAt('02:00');
Schedule::command('sitemap:generate')->weekly();

// With output logging
Schedule::command('reports:generate')
    ->dailyAt('06:00')
    ->appendOutputTo(storage_path('logs/reports.log'))
    ->emailOutputOnFailure('[email protected]');

Проверка здоровья (Health Check)

<?php
declare(strict_types=1);

// Laravel 11 includes a built-in health check endpoint
// Configured in bootstrap/app.php:
// ->withRouting(health: '/up')

// GET /up returns 200 if application is running
// Returns 500 if application fails to boot

// Custom health check
use Illuminate\Support\Facades\Route;

Route::get('/health', function () {
    // Check database
    try {
        \DB::connection()->getPdo();
    } catch (\Exception $e) {
        return response()->json(['status' => 'error', 'db' => false], 503);
    }

    // Check Redis
    try {
        \Illuminate\Support\Facades\Redis::ping();
    } catch (\Exception $e) {
        return response()->json(['status' => 'error', 'redis' => false], 503);
    }

    // Check storage
    $storageWritable = is_writable(storage_path());

    return response()->json([
        'status' => 'ok',
        'db' => true,
        'redis' => true,
        'storage' => $storageWritable,
        'version' => app()->version(),
    ]);
});

Чеклист деплоя

<?php
declare(strict_types=1);

// Production deployment checklist:
//
// PRE-DEPLOY:
// [ ] APP_DEBUG=false
// [ ] APP_ENV=production
// [ ] Error pages configured (403, 404, 500, 503)
// [ ] HTTPS enforced (TrustProxies middleware, APP_URL with https)
// [ ] Database backups configured
// [ ] Log rotation configured
//
// DEPLOY:
// [ ] composer install --no-dev --optimize-autoloader
// [ ] npm ci && npm run build
// [ ] php artisan migrate --force
// [ ] php artisan optimize (config:cache, route:cache, view:cache, event:cache)
// [ ] php artisan storage:link
// [ ] php artisan queue:restart
// [ ] Restart PHP-FPM (reload OPcache)
//
// POST-DEPLOY:
// [ ] Health check endpoint responds 200
// [ ] Monitor error logs
// [ ] Monitor queue workers
// [ ] Check scheduled tasks are running
//
// SECURITY:
// [ ] .env not accessible via web
// [ ] Document root points to public/
// [ ] Directory listing disabled
// [ ] Sensitive headers hidden (X-Powered-By)
// [ ] CORS configured properly
// [ ] Rate limiting active
// [ ] CSP headers set

Проверь себя

Какой флаг Composer КРИТИЧЕСКИ важен для продакшен-деплоя?

Какие команды выполняет `php artisan optimize` в Laravel 11?

После деплоя нового кода при `opcache.validate_timestamps=0`, что нужно сделать?

Что произойдёт, если на продакшен-сервере `APP_DEBUG=true`?

Почему команда `php artisan route:cache` может завершиться с ошибкой?