Ir al contenido principal
Laravel, shipping fast.

La verdad incómoda sobre un script de despliegue ingenuo: git pull && composer install && php artisan migrate --force suena simple, y lo es, pero durante unos segundos en mitad de eso tu directorio de aplicación contiene la mitad del código viejo y la mitad del nuevo. Una petición que aterrice en esa ventana puede dar contra un controlador de la versión nueva llamando a una clase que aún no ha terminado de descargarse. Eso no es hipotético: es lo que pasa en una máquina sirviendo tráfico real mientras composer install sigue escribiendo ficheros en disco.

El despliegue sin caídas lo arregla con una versión, no con una sobrescritura. El commit nuevo se descarga en su propio directorio. Las dependencias se instalan ahí. La configuración se cachea ahí. Sólo cuando esa versión entera está construida y verificada se cambia un enlace simbólico, de forma atómica, del directorio antiguo al nuevo. El servidor web siempre estuvo mirando un enlace simbólico, así que desde su punto de vista la aplicación cambió al instante, y nunca sirvió un directorio a medio construir.

Por eso esta flota modela su aprovisionador de infraestructura como una dependencia de primera clase en lugar de como un script que alguien ejecuta a mano:

// app/Enums/Dependency.php
enum Dependency: string
{
    case GitHub = 'github';   // repos + commits (via api-git)
    case Forge = 'forge';     // server provisioning + deploy (fronts DigitalOcean)
    case Stripe = 'stripe';   // billing + subscriptions
    ...
}

Forge está en el mismo enum que GitHub y Stripe. Para el resto de la API, un destino de despliegue tiene exactamente la misma forma que una pasarela de pago: un sistema externo del que dependes, cuyos fallos deben ser visibles en lugar de quedar enterrados dentro de un cliente HTTP.

Dos cosas tienen que ocurrir correctamente dentro de ese intercambio atómico.

Los health checks tienen que controlar el tráfico, no seguirlo. Todas las aplicaciones actuales arrancan con el mismo esqueleto ligero, y ese esqueleto ya declara una ruta de salud como parte del enrutado:

// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ...

Esa ruta es lo que tu herramienta de despliegue debería consultar antes de decidir que la versión nueva merece recibir tráfico, y es una preocupación distinta del endpoint de salud más rico del capítulo 1. Una responde «¿arrancó el proceso?»; la otra responde «¿son alcanzables las cosas de las que depende este proceso?». Un despliegue necesita la primera respuesta antes de cambiar el enlace. Sáltate esa comprobación y habrás automatizado publicar código roto con la misma eficiencia con la que automatizaste publicar código que funciona.

Los workers de cola no se recargan solos. Un proceso queue:work es un proceso PHP de larga duración que cargó tus clases de job en memoria una vez, al arrancar. Cambiar el enlace simbólico no toca esa memoria. Un worker que lleva corriendo desde antes de tu despliegue seguirá tan contento procesando jobs con el código que cargó hace una hora, lo que significa que un job despachado bajo la versión nueva puede recogerlo un worker que aún corre la vieja. php artisan queue:restart no mata nada directamente: fija una señal de reinicio que cada worker comprueba entre jobs y con la que sale limpiamente, para que tu gestor de procesos arranque uno nuevo con el código nuevo. Deja este paso fuera de tu despliegue y tendrás desajuste de versiones: código nuevo escribiendo datos, código viejo leyéndolos, y un informe de fallo que no tiene sentido hasta que alguien recuerda que los workers nunca se reiniciaron.