Laravel Envoy es la herramienta oficial de Laravel para automatizar tareas en servidores remotos. Si estás cansado de conectarte por SSH para hacer deploys manuales, ejecutar migraciones o reiniciar colas, Envoy te permite hacerlo todo con un solo comando desde tu terminal local.
En esta guía completa aprenderás desde la instalación básica hasta estrategias avanzadas de deploy con zero-downtime, rollback automático y notificaciones a tu equipo.
¿Qué es Laravel Envoy y por qué usarlo?
Envoy es un task runner que utiliza sintaxis Blade para definir comandos que se ejecutan en servidores remotos vía SSH. A diferencia de otras herramientas de automatización, Envoy está diseñado específicamente para desarrolladores Laravel y ofrece una curva de aprendizaje mínima.
Características principales
Laravel Envoy ofrece un conjunto de funcionalidades que lo hacen ideal para equipos de desarrollo Laravel:
La sintaxis Blade permite definir tareas usando el mismo sistema de templates que ya conoces de Laravel. No necesitas aprender un nuevo DSL ni configuraciones complejas en YAML.
El soporte para múltiples servidores te permite ejecutar comandos en varios servidores simultáneamente, ya sea de forma secuencial o en paralelo.
Las Stories agrupan tareas relacionadas para ejecutarlas en secuencia con un solo comando, ideal para flujos de deploy complejos.
Los Hooks (@before, @after, @error, @success, @finished) permiten ejecutar código PHP antes y después de las tareas, perfecto para validaciones y limpieza.
Las notificaciones integradas envían alertas a Slack, Discord, Telegram o Microsoft Teams cuando las tareas terminan.
Envoy vs otras alternativas
Antes de elegir Envoy, es importante entender cómo se compara con otras soluciones:
Envoy vs Deploy manual por SSH: El deploy manual es propenso a errores humanos. Olvidar un comando, ejecutar en el orden incorrecto o en el servidor equivocado puede causar downtime. Envoy garantiza consistencia en cada deploy.
Envoy vs GitHub Actions / GitLab CI: Las pipelines de CI/CD son potentes pero requieren configuración compleja. Envoy es ideal para proyectos pequeños y medianos donde necesitas control inmediato desde tu máquina local sin esperar a que termine una pipeline.
Envoy vs Ansible: Ansible es una herramienta de configuración de infraestructura completa. Si solo necesitas automatizar deploys y tareas de mantenimiento en servidores ya configurados, Envoy es mucho más simple y directo.
Envoy vs Deployer: Deployer (deployer.org) es similar a Envoy pero con sintaxis PHP pura. Envoy tiene la ventaja de usar Blade y estar mantenido oficialmente por el equipo de Laravel.
Envoy vs Laravel Forge / Envoyer: Forge y Envoyer son servicios de pago que ofrecen UI web, zero-downtime deploys y más. Envoy es gratuito y te da control total desde la terminal, ideal si prefieres no depender de servicios externos.
Cuándo usar Laravel Envoy
Envoy es la elección correcta cuando:
Trabajas con proyectos Laravel de cualquier tamaño
Tu equipo es pequeño o mediano y busca simplicidad
Necesitas deploys frecuentes con un flujo consistente
Prefieres control directo desde tu terminal
No quieres pagar por servicios de deploy
Ya tienes servidores configurados y solo necesitas automatizar tareas
Considera otras alternativas si:
Necesitas pipelines complejos con múltiples stages y aprobaciones
Requieres integración profunda con herramientas de testing en cada commit
Tu equipo ya domina otras soluciones de DevOps como Ansible o Kubernetes
Necesitas gestión de infraestructura, no solo deploys
Requisitos previos
Antes de empezar con Envoy, asegúrate de tener:
Sistema operativo compatible: Envoy funciona en Mac y Linux de forma nativa. En Windows puedes usarlo a través de WSL2 (Windows Subsystem for Linux).
Acceso SSH configurado: Necesitas poder conectarte a tus servidores sin contraseña usando llaves SSH. Si al ejecutar ssh user@tu-servidor te pide contraseña, primero configura el acceso con llaves.
PHP instalado localmente: Envoy se ejecuta desde tu máquina local, así que necesitas PHP instalado (el mismo que usas para desarrollo).
Proyecto Laravel: Aunque Envoy puede usarse en cualquier proyecto PHP, está diseñado para Laravel y aprovecha mejor sus capacidades en este contexto.
Configurar acceso SSH sin contraseña
Si aún no tienes configurado el acceso SSH con llaves, estos son los pasos:
# Generar llave SSH si no tienes una
ssh-keygen -t ed25519 -C "[email protected]"
# Copiar la llave pública al servidor
ssh-copy-id user@tu-servidor-ip
# Verificar que funciona sin pedir contraseña
ssh user@tu-servidor-ip
Si usas múltiples llaves SSH para diferentes servidores, puedes configurarlas en ~/.ssh/config:
Host produccion
HostName 192.168.1.100
User deploy
IdentityFile ~/.ssh/id_produccion
Host staging
HostName 192.168.1.101
User deploy
IdentityFile ~/.ssh/id_staging
Con esta configuración, puedes usar los alias directamente en Envoy.
Instalación de Laravel Envoy
La instalación de Envoy es sencilla. Desde el directorio raíz de tu proyecto Laravel:
composer require laravel/envoy --dev
El flag --dev es importante porque Envoy solo se necesita en desarrollo, no en producción.
Una vez instalado, el binario está disponible en vendor/bin/envoy. Puedes verificar la instalación ejecutando:
php vendor/bin/envoy --version
Crear el archivo Envoy.blade.php
Todas las tareas de Envoy se definen en un archivo llamado Envoy.blade.php en la raíz de tu proyecto:
touch Envoy.blade.php
Este archivo usa sintaxis Blade pero no es un template HTML, sino un script de tareas.
Anatomía de Envoy.blade.php
El archivo Envoy.blade.php tiene una estructura específica con directivas que definen servidores, configuración, tareas y hooks.
@servers: Definir servidores
La directiva @servers define los servidores donde se ejecutarán las tareas. Debe estar en una sola línea:
@servers(['web' => '[email protected]', 'worker' => '[email protected]'])
Puedes definir múltiples servidores y agruparlos:
@servers([
'web-1' => '[email protected]',
'web-2' => '[email protected]',
'worker' => '[email protected]',
'localhost' => '127.0.0.1'
])
El servidor localhost con IP 127.0.0.1 ejecuta comandos en tu máquina local, útil para tareas de preparación.
@setup: Configuración inicial
La directiva @setup permite definir variables PHP que estarán disponibles en todas las tareas:
@setup
$repository = '[email protected]:tu-usuario/tu-proyecto.git';
$appDir = '/var/www/tu-proyecto';
$branch = $branch ?? 'main';
$releaseDir = $appDir . '/releases/' . date('YmdHis');
$currentDir = $appDir . '/current';
@endsetup
Aquí puedes usar cualquier código PHP: variables, condicionales, funciones, etc.
@include: Cargar archivos PHP
Si necesitas usar clases o funciones externas, puedes incluir el autoloader de Composer:
@include('vendor/autoload.php')
@setup
$config = json_decode(file_get_contents('deploy-config.json'), true);
@endsetup
@import: Importar otros archivos Envoy
Puedes modularizar tus tareas importando otros archivos Envoy:
@import('envoy/tasks.blade.php')
@import('envoy/notifications.blade.php')
@task: Definir tareas
Las tareas son el núcleo de Envoy. Cada tarea define comandos shell que se ejecutan en los servidores especificados:
@task('deploy', ['on' => 'web'])
cd {{ $appDir }}
git pull origin {{ $branch }}
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
@endtask
Las opciones disponibles para @task son:
on: Servidor o array de servidores donde ejecutarparallel: Ejecutar en paralelo en múltiples servidores (default: false)confirm: Pedir confirmación antes de ejecutar (default: false)
@story: Agrupar tareas
Las stories permiten ejecutar múltiples tareas en secuencia con un solo comando:
@story('deploy')
git-pull
composer-install
migrate
cache-clear
optimize
@endstory
@task('git-pull', ['on' => 'web'])
cd {{ $appDir }}
git pull origin {{ $branch }}
@endtask
@task('composer-install', ['on' => 'web'])
cd {{ $appDir }}
composer install --no-dev --optimize-autoloader
@endtask
@task('migrate', ['on' => 'web'])
cd {{ $appDir }}
php artisan migrate --force
@endtask
@task('cache-clear', ['on' => 'web'])
cd {{ $appDir }}
php artisan optimize:clear
@endtask
@task('optimize', ['on' => 'web'])
cd {{ $appDir }}
php artisan config:cache
php artisan route:cache
php artisan view:cache
@endtask
Las stories mejoran la organización y permiten ejecutar tareas individuales cuando lo necesites.
Hooks: @before, @after, @error, @success, @finished
Los hooks ejecutan código PHP en momentos específicos del ciclo de vida de las tareas:
@before
if ($task === 'deploy') {
// Validaciones antes del deploy
}
@endbefore
@after
if ($task === 'deploy') {
// Limpieza después del deploy
}
@endafter
@error
// Se ejecuta si una tarea falla
// $task contiene el nombre de la tarea que falló
@enderror
@success
// Se ejecuta si TODAS las tareas terminan sin errores
@endsuccess
@finished
// Se ejecuta siempre, haya o no errores
// $exitCode contiene el código de salida (0 = éxito)
if ($exitCode > 0) {
// Hubo errores
}
@endfinished
Los hooks se ejecutan localmente, no en los servidores remotos.
Ejecutar tareas
Para ejecutar una tarea o story:
php vendor/bin/envoy run deploy
Pasar variables
Puedes pasar variables desde la línea de comandos:
php vendor/bin/envoy run deploy --branch=feature/nueva-funcionalidad
Y usarlas en tus tareas:
@task('deploy', ['on' => 'web'])
cd {{ $appDir }}
git pull origin {{ $branch }}
@endtask
Confirmar antes de ejecutar
Para tareas peligrosas, añade la opción confirm:
@task('migrate-fresh', ['on' => 'web', 'confirm' => true])
cd {{ $appDir }}
php artisan migrate:fresh --seed
@endtask
Envoy pedirá confirmación antes de ejecutar.
Ejemplos prácticos
Deploy básico
El ejemplo más simple de deploy:
@servers(['web' => 'deploy@tu-servidor'])
@setup
$appDir = '/var/www/tu-proyecto';
$branch = $branch ?? 'main';
@endsetup
@task('deploy', ['on' => 'web'])
echo "Iniciando deploy..."
cd {{ $appDir }}
echo "Activando modo mantenimiento..."
php artisan down --retry=60
echo "Actualizando código..."
git pull origin {{ $branch }}
echo "Instalando dependencias..."
composer install --no-dev --optimize-autoloader
echo "Ejecutando migraciones..."
php artisan migrate --force
echo "Limpiando y optimizando caché..."
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
echo "Desactivando modo mantenimiento..."
php artisan up
echo "Deploy completado!"
@endtask
Ejecutar:
php vendor/bin/envoy run deploy
Deploy en múltiples servidores
Para aplicaciones con varios servidores web:
@servers([
'web-1' => '[email protected]',
'web-2' => '[email protected]',
'web-3' => '[email protected]'
])
@setup
$appDir = '/var/www/tu-proyecto';
$branch = $branch ?? 'main';
@endsetup
@task('deploy', ['on' => ['web-1', 'web-2', 'web-3'], 'parallel' => true])
cd {{ $appDir }}
git pull origin {{ $branch }}
composer install --no-dev --optimize-autoloader
php artisan optimize:clear
php artisan config:cache
@endtask
Con parallel => true, el deploy se ejecuta simultáneamente en los tres servidores.
Deploy con múltiples entornos
Para manejar desarrollo, staging y producción:
@servers([
'dev' => '[email protected]',
'staging' => '[email protected]',
'production' => '[email protected]'
])
@setup
$appDir = '/var/www/tu-proyecto';
// Mapeo de entorno a rama
$branches = [
'dev' => 'develop',
'staging' => 'staging',
'production' => 'main'
];
// Validar que se especificó un entorno
if (!isset($env)) {
throw new Exception('Debes especificar el entorno: --env=dev|staging|production');
}
if (!isset($branches[$env])) {
throw new Exception("Entorno '$env' no válido");
}
$branch = $branches[$env];
@endsetup
@task('deploy', ['on' => $env, 'confirm' => $env === 'production'])
echo "Desplegando en {{ $env }} (rama: {{ $branch }})..."
cd {{ $appDir }}
@if($env === 'production')
php artisan down --retry=60
@endif
git pull origin {{ $branch }}
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
@if($env === 'production')
php artisan up
@endif
echo "Deploy en {{ $env }} completado!"
@endtask
Ejecutar:
# Deploy en desarrollo
php vendor/bin/envoy run deploy --env=dev
# Deploy en staging
php vendor/bin/envoy run deploy --env=staging
# Deploy en producción (pedirá confirmación)
php vendor/bin/envoy run deploy --env=production
Reiniciar workers y colas
Tarea específica para reiniciar procesos de cola:
@servers([
'worker-1' => '[email protected]',
'worker-2' => '[email protected]'
])
@setup
$appDir = '/var/www/tu-proyecto';
@endsetup
@task('restart-queues', ['on' => ['worker-1', 'worker-2'], 'parallel' => true])
cd {{ $appDir }}
php artisan queue:restart
echo "Colas reiniciadas en este servidor"
@endtask
@task('restart-horizon', ['on' => ['worker-1', 'worker-2'], 'parallel' => true])
cd {{ $appDir }}
php artisan horizon:terminate
echo "Horizon reiniciado en este servidor"
@endtask
Ejecutar comandos Artisan remotamente
A veces necesitas ejecutar comandos específicos:
@task('tinker', ['on' => $env ?? 'production'])
cd {{ $appDir }}
php artisan tinker
@endtask
@task('logs', ['on' => $env ?? 'production'])
cd {{ $appDir }}
tail -f storage/logs/laravel.log
@endtask
@task('cache-clear', ['on' => $env ?? 'production'])
cd {{ $appDir }}
php artisan optimize:clear
echo "Caché limpiada"
@endtask
Backup de base de datos
Automatizar backups antes del deploy:
@setup
$appDir = '/var/www/tu-proyecto';
$backupDir = '/var/backups/mysql';
$timestamp = date('Y-m-d_H-i-s');
@endsetup
@task('backup-db', ['on' => 'web'])
echo "Creando backup de base de datos..."
cd {{ $appDir }}
# Obtener credenciales del .env
DB_DATABASE=$(grep DB_DATABASE .env | cut -d '=' -f2)
DB_USERNAME=$(grep DB_USERNAME .env | cut -d '=' -f2)
DB_PASSWORD=$(grep DB_PASSWORD .env | cut -d '=' -f2)
# Crear directorio si no existe
mkdir -p {{ $backupDir }}
# Crear backup
mysqldump -u$DB_USERNAME -p$DB_PASSWORD $DB_DATABASE > {{ $backupDir }}/backup_{{ $timestamp }}.sql
# Comprimir
gzip {{ $backupDir }}/backup_{{ $timestamp }}.sql
echo "Backup creado: {{ $backupDir }}/backup_{{ $timestamp }}.sql.gz"
# Eliminar backups de más de 7 días
find {{ $backupDir }} -name "*.sql.gz" -mtime +7 -delete
echo "Backups antiguos eliminados"
@endtask
@story('deploy-with-backup')
backup-db
deploy
@endstory
Deploy Zero-Downtime
El deploy con zero-downtime usa enlaces simbólicos para cambiar de versión instantáneamente. La estructura de directorios es:
/var/www/tu-proyecto/
├── current -> releases/20241206120000 (symlink)
├── releases/
│ ├── 20241206120000/
│ ├── 20241206100000/
│ └── 20241206080000/
└── shared/
├── .env
└── storage/
Implementación completa:
@servers(['web' => 'deploy@tu-servidor'])
@setup
$repository = '[email protected]:tu-usuario/tu-proyecto.git';
$baseDir = '/var/www/tu-proyecto';
$releasesDir = $baseDir . '/releases';
$sharedDir = $baseDir . '/shared';
$currentDir = $baseDir . '/current';
$branch = $branch ?? 'main';
$release = date('YmdHis');
$releaseDir = $releasesDir . '/' . $release;
// Número de releases a mantener
$keepReleases = 5;
@endsetup
@story('deploy')
prepare
clone
composer
shared
migrate
optimize
activate
cleanup
@endstory
@task('prepare', ['on' => 'web'])
echo "Preparando directorios..."
# Crear estructura de directorios si no existe
mkdir -p {{ $releasesDir }}
mkdir -p {{ $sharedDir }}/storage/app/public
mkdir -p {{ $sharedDir }}/storage/framework/cache
mkdir -p {{ $sharedDir }}/storage/framework/sessions
mkdir -p {{ $sharedDir }}/storage/framework/views
mkdir -p {{ $sharedDir }}/storage/logs
# Verificar que existe .env en shared
if [ ! -f {{ $sharedDir }}/.env ]; then
echo "ERROR: No existe {{ $sharedDir }}/.env"
echo "Crea el archivo .env en {{ $sharedDir }} antes de continuar"
exit 1
fi
@endtask
@task('clone', ['on' => 'web'])
echo "Clonando repositorio (release: {{ $release }})..."
git clone --depth 1 --branch {{ $branch }} {{ $repository }} {{ $releaseDir }}
echo "Código clonado correctamente"
@endtask
@task('composer', ['on' => 'web'])
echo "Instalando dependencias..."
cd {{ $releaseDir }}
composer install --no-dev --optimize-autoloader --no-interaction
@endtask
@task('shared', ['on' => 'web'])
echo "Enlazando archivos compartidos..."
cd {{ $releaseDir }}
# Eliminar storage y enlazar al compartido
rm -rf storage
ln -s {{ $sharedDir }}/storage storage
# Enlazar .env
ln -s {{ $sharedDir }}/.env .env
echo "Enlaces simbólicos creados"
@endtask
@task('migrate', ['on' => 'web'])
echo "Ejecutando migraciones..."
cd {{ $releaseDir }}
php artisan migrate --force
@endtask
@task('optimize', ['on' => 'web'])
echo "Optimizando aplicación..."
cd {{ $releaseDir }}
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan storage:link
@endtask
@task('activate', ['on' => 'web'])
echo "Activando nueva versión..."
# Cambiar el enlace current a la nueva release
ln -sfn {{ $releaseDir }} {{ $currentDir }}
# Reiniciar PHP-FPM para limpiar opcache
sudo systemctl reload php8.3-fpm
echo "Nueva versión activada: {{ $release }}"
@endtask
@task('cleanup', ['on' => 'web'])
echo "Limpiando releases antiguas..."
cd {{ $releasesDir }}
# Mantener solo las últimas N releases
ls -dt */ | tail -n +{{ $keepReleases + 1 }} | xargs -r rm -rf
echo "Limpieza completada. Releases actuales:"
ls -la {{ $releasesDir }}
@endtask
Rollback
Si algo sale mal, puedes volver a la versión anterior:
@task('rollback', ['on' => 'web', 'confirm' => true])
echo "Iniciando rollback..."
cd {{ $releasesDir }}
# Obtener la release anterior
PREVIOUS=$(ls -dt */ | sed -n '2p' | tr -d '/')
if [ -z "$PREVIOUS" ]; then
echo "ERROR: No hay release anterior para rollback"
exit 1
fi
echo "Volviendo a release: $PREVIOUS"
# Cambiar el enlace
ln -sfn {{ $releasesDir }}/$PREVIOUS {{ $currentDir }}
# Reiniciar PHP-FPM
sudo systemctl reload php8.3-fpm
echo "Rollback completado a: $PREVIOUS"
@endtask
@task('list-releases', ['on' => 'web'])
echo "Releases disponibles:"
ls -la {{ $releasesDir }}
echo ""
echo "Release actual:"
readlink {{ $currentDir }}
@endtask
Notificaciones
Envoy puede enviar notificaciones cuando las tareas terminan.
Slack
@setup
$slackWebhook = 'https://hooks.slack.com/services/XXX/YYY/ZZZ';
@endsetup
@finished
@slack($slackWebhook, '#deploys', "Deploy completado en {$env}")
@endfinished
Con mensaje personalizado según el resultado:
@finished
@if($exitCode === 0)
@slack($slackWebhook, '#deploys', "✅ Deploy exitoso en producción")
@else
@slack($slackWebhook, '#deploys', "❌ Error en deploy de producción")
@endif
@endfinished
Discord
@setup
$discordWebhook = 'https://discord.com/api/webhooks/XXX/YYY';
@endsetup
@finished
@discord($discordWebhook, "Deploy completado con código: {$exitCode}")
@endfinished
Telegram
@setup
$telegramBotId = 'tu-bot-id';
$telegramChatId = 'tu-chat-id';
@endsetup
@finished
@telegram($telegramBotId, $telegramChatId)
@endfinished
Microsoft Teams
@setup
$teamsWebhook = 'https://outlook.office.com/webhook/XXX';
@endsetup
@finished
@microsoftTeams($teamsWebhook)
@endfinished
Buenas prácticas
Seguridad
Usa un usuario específico para deploys con permisos limitados. No uses root nunca.
# En el servidor, crear usuario deploy
sudo adduser deploy
sudo usermod -aG www-data deploy
# Dar permisos de reinicio a PHP-FPM sin contraseña
echo "deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload php8.3-fpm" | sudo tee /etc/sudoers.d/deploy
Estructura de directorios
Mantén los archivos sensibles fuera del repositorio:
/var/www/proyecto/
├── current/ # Symlink a release actual
├── releases/ # Versiones del código
├── shared/ # Archivos persistentes
│ ├── .env
│ └── storage/
└── backups/ # Backups de BD
Evitar errores comunes
No ejecutes composer con root: Composer muestra warnings y puede causar problemas de permisos.
No olvides --force en migraciones: En producción, Laravel pide confirmación para migraciones. Usa --force para automatizar.
Limpia el opcache: Después de actualizar código, PHP puede seguir sirviendo código cacheado. Reinicia PHP-FPM o limpia opcache.
Verifica antes de desplegar: Usa un hook @before para verificar que el servidor está accesible:
@before
if ($task === 'deploy') {
exec("ssh -o ConnectTimeout=5 deploy@tu-servidor 'echo ok' 2>/dev/null", $output, $code);
if ($code !== 0) {
throw new Exception("No se puede conectar al servidor");
}
}
@endbefore
Testing local de tareas
Puedes probar tareas en localhost antes de ejecutar en producción:
@servers(['localhost' => '127.0.0.1'])
@task('test', ['on' => 'localhost'])
echo "Probando tarea..."
pwd
ls -la
@endtask
Solución de problemas
Error: Permission denied
Verifica que tu llave SSH tiene acceso al servidor:
ssh -v deploy@tu-servidor
Error: Host key verification failed
Añade el servidor a known_hosts:
ssh-keyscan -H tu-servidor >> ~/.ssh/known_hosts
Las tareas no muestran output
Envoy muestra el output en tiempo real. Si no ves nada, verifica que los comandos producen salida con echo.
El deploy funciona pero la web no actualiza
PHP puede estar cacheando el código. Reinicia PHP-FPM:
sudo systemctl reload php8.3-fpm
O limpia opcache desde PHP:
@task('clear-opcache', ['on' => 'web'])
cd {{ $appDir }}
php -r "opcache_reset();"
@endtask
Ejemplo completo de Envoy.blade.php
Este es un ejemplo completo listo para usar en producción:
@servers([
'production' => '[email protected]',
'staging' => '[email protected]'
])
@setup
$repository = '[email protected]:tu-usuario/tu-proyecto.git';
$baseDir = '/var/www/tu-proyecto';
$releasesDir = $baseDir . '/releases';
$sharedDir = $baseDir . '/shared';
$currentDir = $baseDir . '/current';
$branch = $branch ?? 'main';
$release = date('YmdHis');
$releaseDir = $releasesDir . '/' . $release;
$keepReleases = 5;
$slackWebhook = 'https://hooks.slack.com/services/XXX/YYY/ZZZ';
// Determinar servidor
$server = $on ?? 'staging';
if (!in_array($server, ['production', 'staging'])) {
throw new Exception("Servidor no válido: $server");
}
@endsetup
@story('deploy', ['on' => $server])
clone-release
install-dependencies
link-shared
run-migrations
optimize-app
activate-release
cleanup-old
@endstory
@task('clone-release')
echo "📦 Clonando {{ $branch }} en {{ $releaseDir }}..."
git clone --depth 1 --branch {{ $branch }} {{ $repository }} {{ $releaseDir }}
@endtask
@task('install-dependencies')
echo "📥 Instalando dependencias..."
cd {{ $releaseDir }}
composer install --no-dev --optimize-autoloader --no-interaction
@endtask
@task('link-shared')
echo "🔗 Enlazando archivos compartidos..."
cd {{ $releaseDir }}
rm -rf storage
ln -s {{ $sharedDir }}/storage storage
ln -s {{ $sharedDir }}/.env .env
@endtask
@task('run-migrations')
echo "🗄️ Ejecutando migraciones..."
cd {{ $releaseDir }}
php artisan migrate --force
@endtask
@task('optimize-app')
echo "⚡ Optimizando aplicación..."
cd {{ $releaseDir }}
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan storage:link
@endtask
@task('activate-release')
echo "🚀 Activando release {{ $release }}..."
ln -sfn {{ $releaseDir }} {{ $currentDir }}
sudo systemctl reload php8.3-fpm
@endtask
@task('cleanup-old')
echo "🧹 Limpiando releases antiguas..."
cd {{ $releasesDir }}
ls -dt */ | tail -n +{{ $keepReleases + 1 }} | xargs -r rm -rf
@endtask
@task('rollback', ['on' => $server, 'confirm' => true])
echo "⏪ Iniciando rollback..."
cd {{ $releasesDir }}
PREVIOUS=$(ls -dt */ | sed -n '2p' | tr -d '/')
if [ -z "$PREVIOUS" ]; then
echo "❌ No hay release anterior"
exit 1
fi
ln -sfn {{ $releasesDir }}/$PREVIOUS {{ $currentDir }}
sudo systemctl reload php8.3-fpm
echo "✅ Rollback a $PREVIOUS completado"
@endtask
@task('restart-queues', ['on' => $server])
echo "🔄 Reiniciando colas..."
cd {{ $currentDir }}
php artisan queue:restart
@endtask
@task('logs', ['on' => $server])
cd {{ $currentDir }}
tail -f storage/logs/laravel.log
@endtask
@success
@slack($slackWebhook, '#deploys', "✅ Deploy exitoso en {$server}")
@endsuccess
@error
@slack($slackWebhook, '#deploys', "❌ Error en deploy de {$server}")
@enderror
Uso:
# Deploy en staging
php vendor/bin/envoy run deploy --on=staging
# Deploy en producción
php vendor/bin/envoy run deploy --on=production
# Rollback
php vendor/bin/envoy run rollback --on=production
# Ver logs
php vendor/bin/envoy run logs --on=production
Conclusión
Laravel Envoy es la herramienta perfecta para equipos Laravel que buscan automatizar sus deploys sin la complejidad de soluciones enterprise. Con una sintaxis familiar y capacidades potentes como ejecución paralela, hooks y notificaciones, puedes crear flujos de deploy profesionales en minutos.
La estrategia de zero-downtime con releases y symlinks te permite desplegar con confianza, sabiendo que puedes hacer rollback instantáneamente si algo sale mal.
Si quieres profundizar más en deploys automatizados con Envoy, incluyendo configuración completa de servidores Ubuntu, SSL con Let's Encrypt y deploy en EC2, puedes consultar nuestro curso completo de Laravel Envoy.
Laravel Envoy representa la filosofía Laravel: herramientas potentes con sintaxis elegante que mejoran tu productividad sin añadir complejidad innecesaria.
Curso Laravel 12
Completo 2026
El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.
star Incluido en cualquier suscripción