DevOps
laravel-deploy
Configures deployment for Forge, Vapor, Docker, Bref
Overview
The laravel-deploy agent configures production deployment. It sets up zero-downtime deployments, creates Docker configurations, generates deployment scripts for various platforms, and configures health checks and monitoring.
Responsibilities
- Zero-Downtime Deployment - Atomic deployments with symlinks
- Platform Configuration - Forge, Vapor, Ploi, custom VPS
- Docker Setup - Production-ready Dockerfiles
- Health Checks - Endpoint monitoring for load balancers
- Environment Config - Production-ready settings
- SSL/HTTPS - Certificate configuration
What It Creates
| Component | File | Purpose |
|---|---|---|
| Dockerfile | Dockerfile |
Container image definition |
| Docker Compose | docker-compose.yml |
Multi-container orchestration |
| Deploy Script | deploy.sh |
Deployment automation |
| Health Check | app/Http/Controllers/HealthController.php |
Load balancer health endpoint |
Generated Dockerfile
# Production Dockerfile
FROM php:8.3-fpm-alpine
# Install dependencies
RUN apk add --no-cache \
nginx \
supervisor \
libpng-dev \
libzip-dev \
&& docker-php-ext-install pdo_mysql gd zip opcache
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Configure PHP
COPY docker/php.ini /usr/local/etc/php/conf.d/custom.ini
# Configure Nginx
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
# Configure Supervisor
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Set working directory
WORKDIR /var/www/html
# Copy application
COPY --chown=www-data:www-data . .
# Install dependencies
RUN composer install --no-dev --optimize-autoloader
# Cache config
RUN php artisan config:cache && \
php artisan route:cache && \
php artisan view:cache
EXPOSE 80
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
Zero-Downtime Deploy Script
#!/bin/bash
set -e
DEPLOY_DIR="/var/www/app"
RELEASES_DIR="$DEPLOY_DIR/releases"
SHARED_DIR="$DEPLOY_DIR/shared"
CURRENT="$DEPLOY_DIR/current"
RELEASE=$(date +%Y%m%d%H%M%S)
echo "Starting deployment: $RELEASE"
# Clone fresh release
git clone --depth 1 git@github.com:user/repo.git "$RELEASES_DIR/$RELEASE"
cd "$RELEASES_DIR/$RELEASE"
# Link shared resources
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
# Health check before switching
if ! curl -sf http://localhost:8080/health > /dev/null; then
echo "Health check failed, aborting deployment"
rm -rf "$RELEASES_DIR/$RELEASE"
exit 1
fi
# Atomic switch
ln -nfs "$RELEASES_DIR/$RELEASE" "$CURRENT"
# Restart services
php artisan queue:restart
sudo systemctl reload php-fpm
# Cleanup old releases (keep 5)
ls -dt "$RELEASES_DIR"/* | tail -n +6 | xargs rm -rf
echo "Deployment complete: $RELEASE"
Laravel Forge Configuration
# 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
Health Check Endpoint
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
class HealthController extends Controller
{
public function __invoke()
{
$checks = [
'status' => 'ok',
'timestamp' => now()->toIso8601String(),
'checks' => [
'database' => $this->checkDatabase(),
'cache' => $this->checkCache(),
'redis' => $this->checkRedis(),
],
];
$healthy = collect($checks['checks'])
->every(fn ($check) => $check['status'] === 'ok');
return response()->json($checks, $healthy ? 200 : 503);
}
private function checkDatabase(): array
{
try {
DB::select('SELECT 1');
return ['status' => 'ok'];
} catch (\Exception $e) {
return ['status' => 'fail', 'error' => $e->getMessage()];
}
}
}
Invoked By Commands
- /laravel-agent:deploy:setup - Configure deployment
Guardrails
The deploy agent follows strict rules:
- ALWAYS backup before deploying
- ALWAYS run
migrate --forcewith --pretend first - ALWAYS include health checks before switching
- ALWAYS keep at least 5 previous releases for rollback
- NEVER deploy with APP_DEBUG=true
- NEVER expose .env files in public
See Also
- laravel-deploy skill - Auto-invoked deployment expertise
- laravel-cicd - CI/CD pipeline setup