Despliega tu proyecto
Qué necesita el servidor, qué corre una versión, y los tres ajustes que la gente olvida.
Esta guía es el procedimiento de publicación: qué necesita el servidor antes del primer despliegue, los comandos que corre cada despliegue, los valores de producción del entorno, y los procesos que tienen que seguir corriendo después. La necesitas una vez para el primer servidor y otra cada vez que una versión se comporta distinto que en tu máquina.
Lo que necesita el servidor
- PHP 8.2 o más nuevo con las extensiones que Laravel exige (
ctype,curl,dom,fileinfo,filter,hash,mbstring,openssl,pcre,pdo,session,tokenizer,xml), el driver de tu base de datos (pdo_mysql,pdo_pgsqlopdo_sqlite),simplexmlyjsonpara el SDK de AWS, ygdpara las miniaturas de imágenes ypwa:generate-assets.bcmathogmpes opcional y acelera las firmas de web push. - Composer, y Node con npm para compilar los assets. La compilación usa Vite 7, que pide un Node LTS actual (20.19 o más nuevo).
- MySQL, PostgreSQL o SQLite. La cola, el caché y las sesiones usan por omisión tablas de la base de datos, así que una sola base de datos basta para empezar.
- Un servidor web cuya raíz de documentos sea
public/, detrás de HTTPS con un certificado real. Web push, la PWA instalable y la cookie de sesiónSameSite=Noneque la PWA activa se niegan a funcionar sobre HTTP plano.bootstrap/app.phpconfía en todos los proxies (trustProxies(at: '*')), así que un balanceador que termina TLS se detecta correctamente. - Cron, para correr el programador una vez por minuto, y Supervisor o un equivalente para mantener vivo el worker de la cola. Ambos se configuran más abajo.
- Almacenamiento compatible con S3 cuando
FILESYSTEM_DISKse queda en su valor por omisión: el disco por omisión enconfig/filesystems.phpess3, nolocal.
La publicación
Corre esto en cada despliegue, desde la raíz del proyecto, en este orden:
git reset --hard
git pull
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan storage:link
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan queue:restart
| Comando | Qué hace |
|---|---|
git reset --hard && git pull |
Descarta cualquier cosa editada en el servidor y toma el código nuevo. Lo que hayas cambiado a mano en el servidor se pierde, que es justo la idea |
composer install --no-dev --optimize-autoloader |
Instala las dependencias PHP sin las de desarrollo (debugbar, IDE helper, Dusk, PHPUnit, el estándar de código). Corre package:discover y apunta core.hooksPath a scripts/git-hooks, lo cual es inofensivo en un servidor |
npm ci && npm run build |
Instala las dependencias JavaScript exactas de package-lock.json y compila resources/css y resources/js en public/build/. Los archivos compilados no están committeados |
php artisan migrate --force |
Corre las migraciones nuevas. --force se salta la confirmación que Laravel pide en producción |
php artisan storage:link |
Crea public/storage apuntando a storage/app/public. Idempotente |
php artisan optimize:clear |
Tira todos los cachés: configuración, rutas, vistas, eventos, clases compiladas. Hazlo antes de cachear otra vez para que nada viejo sobreviva |
php artisan config:cache |
Fusiona config/*.php y .env en un solo archivo. Después de esto .env no se lee en absoluto: una variable cambiada sin volver a correr este comando se ignora |
php artisan route:cache |
Cachea la tabla de rutas, closures incluidos, para que los archivos de rutas no se analicen en cada petición |
php artisan view:cache |
Precompila las plantillas Blade |
php artisan event:cache |
Cachea el descubrimiento de listeners. Los listeners de los registros de comunicación y de cobro viven en app/Listeners y se descubren, no se registran a mano |
php artisan queue:restart |
Le dice a cada worker en marcha que termine su job actual y salga, para que Supervisor lo arranque otra vez con el código nuevo. Los workers conservan el código viejo en memoria hasta que esto corre |
php artisan optimize corre los cuatro comandos de caché de una vez si lo prefieres.
Dos comandos del procedimiento anterior merecen una palabra. php artisan db:seed --force se puede repetir sin riesgo, porque cada seeder usa firstOrCreate, pero crea las cuentas listadas en sudo y default_users de config/app.php, y el default_users que viene de fábrica es [email protected] con una contraseña predecible. Quita esa entrada antes de sembrar una base de datos de producción. php artisan lang:search y php artisan lang:sync reescriben lang/*.json a través del proveedor de IA de config/ai.php; córrelos en tu máquina y committea el resultado, porque en el servidor el siguiente git reset --hard tira su salida.
La DevZone (/help/the-devzone) tiene un botón "Ejecutar despliegue" para versiones pequeñas: hace pull de la rama actual desde el remoto HTTPS con las credenciales de GIT_USER y GIT_PASSWORD, y luego corre los comandos que escribas en la caja, unidos con &&. Cada uno de los dos procesos se mata a los diez segundos, así que le cabe un pull más config:cache, no composer install ni npm run build. Una versión que toca dependencias o assets pasa por el script de arriba.
php artisan help:stamp --check corre en el hook pre-push y detiene el push cuando falta o está desactualizada una traducción de la ayuda. Es una revisión del repositorio, no un paso del despliegue; córrela en CI si tu pipeline hace push sin el hook.
El entorno de producción
.env.example está escrito para una máquina de desarrollo. Estos son los valores que cambian:
| Variable | Valor en producción | Por qué |
|---|---|---|
APP_ENV |
production |
Apaga el comportamiento exclusivo de local: el debugbar y el detector de consultas, y reportar cada excepción sin el límite de frecuencia |
APP_DEBUG |
false |
Oculta las trazas a los visitantes. También es el interruptor que hace segura la cookie de sesión cuando APP_PWA está encendida |
APP_URL |
https://your-project.com |
Se usa en cada enlace generado, el alcance del manifiesto, el sujeto VAPID y el dominio EHLO del correo por omisión |
APP_KEY |
la que se generó una vez | Cifra las sesiones y todo lo cifrado. Génerala una vez con php artisan key:generate --show, guárdala, nunca la regeneres en un sitio en marcha |
QUEUE_CONNECTION |
database (por omisión) o redis |
Cualquier cosa menos sync. Con sync cada job corre dentro de la petición, y un aviso a mil usuarios bloquea al administrador que lo publicó |
CACHE_STORE |
database (por omisión) o redis |
Nunca array en producción: queue:restart y el limitador de excepciones necesitan un caché que compartan el worker y el proceso web |
SESSION_DRIVER |
database |
Lo que pone .env.example; la tabla sessions existe. El valor por omisión de la configuración es file |
FILESYSTEM_DISK |
s3 con las variables AWS_*, o public |
El valor por omisión de la configuración es s3. Usa public para dejar las subidas en el disco del servidor |
LOG_CHANNEL |
daily (por omisión) |
Un archivo por día en storage/logs/. Define SLACK_BOT_TOKEN y SLACK_LOG_CHANNEL y las excepciones también se publican en Slack, con límite de frecuencia: un error repetido se publica en la ocurrencia 1, 10, 25, 50, 100, 300, 500 y 1000 y cada mil después |
MAIL_MAILER |
failover o un solo transporte |
failover intenta mailgun y luego ses, así que ambos necesitan sus llaves. Revisa /help/email-and-sms-delivery |
MAIL_FROM_ADDRESS |
una dirección de tu dominio | El remitente de cada correo; el valor por omisión de .env.example es de Weblabor |
APP_PWA |
true cuando quieres la aplicación instalable y push |
Apagada, el manifiesto y el service worker no se inyectan. Revisa /help/web-push-and-the-pwa |
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY |
el par de php artisan webpush:vapid --show |
Obligatorias para web push. El mismo par en todos los lugares donde se suscriban los mismos usuarios |
FEATURE_* |
las funciones que vendas | Toda bandera viene apagada. Revisa /help/configure-your-project |
STRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET |
llaves live | Sólo cuando planes o complementos están encendidos. El manejador de webhooks del proyecto responde en POST /api/stripe/webhook; registra esa URL en Stripe. Revisa /help/turn-on-plans-and-billing |
Quita los marcadores con los que viene .env.example (your-aws-access-key-id, your-stripe-key, los ids de Telnyx): un marcador no está vacío, y el código que pregunta "¿esto está configurado?" creerá que sí.
El worker de la cola
Los avisos, las migraciones de plan y toda notificación que marques ShouldQueue corren en un worker. Arranca uno con Supervisor para que sobreviva caídas y reinicios. Crea /etc/supervisor/conf.d/your-project-worker.conf:
[program:your-project-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-project/artisan queue:work --sleep=3 --tries=3 --max-time=3600
directory=/var/www/your-project
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/your-project/storage/logs/worker.log
stopwaitsecs=3600
Luego:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start your-project-worker:*
--sleep=3 espera tres segundos cuando la cola está vacía, --tries=3 manda un job a failed_jobs tras su tercer fallo, y --max-time=3600 hace que el worker salga después de una hora para que Supervisor lo reinicie con memoria fresca. stopwaitsecs debe ser al menos tan largo como tu job más largo. Sube numprocs cuando un solo worker se quede atrás. Qué se encola, cómo vigilarlo y cómo reintentar fallos está en /help/queues-and-scheduled-work.
El programador
Dos tareas vienen programadas de fábrica: las estadísticas diarias de inicio de sesión a medianoche y la captura del tipo de cambio a mediodía (UTC, la zona horaria de la aplicación). Sólo corren si cron llama al programador cada minuto:
* * * * * cd /var/www/your-project && php artisan schedule:run >> /dev/null 2>&1
Agrégala al crontab del mismo usuario que es dueño de los archivos, para que los archivos de log que crea sigan siendo escribibles por el servidor web.
Los tres errores
Una bandera que nadie puso en el servidor. Toda función opcional es una variable de entorno que viene apagada. Una sección que funciona en tu máquina y falta en producción es una variable FEATURE_*, o APP_PWA, que se puso en tu .env y nunca en el del servidor. El panel de administración también pierde la sección, así que no es un problema de permisos.
Assets que nunca se compilaron. public/build/ no está committeado. Si la página carga sin estilos, o la consola del navegador reporta que falta /build/manifest.json, npm run build no corrió donde se sirve el sitio, o corrió con una versión de Node que Vite rechazó.
Un caché de configuración viejo. Una vez que corrió config:cache, .env es un archivo que nadie lee. Una variable que cambiaste y que la aplicación ignora es un caché construido antes del cambio: corre php artisan config:cache otra vez. Lo mismo aplica a las rutas después de agregar una, y a queue:restart después de cualquier despliegue, porque un worker que no se reinició corre el código de la semana pasada.