Skip to content

Deploy del backend a cPanel (develop)

Análogo al deploy del frontend — empaquetas en local con un script y descomprimes en el servidor con otro script. La diferencia clave: el backend solo se zipea el código de la app (sin vendor/, sin .env, sin storage/logs/); el vendor/ ya vive en el servidor y solo se reinstala si composer.lock cambió.

TL;DR — 3 comandos por deploy

bash
# 1. En tu Mac
cd humae_backend && composer deploy:zip

# 2. Subir el zip por File Manager a:
#    /home2/humaecom/develop.backend-v1.humae.com.mx/

# 3. En SSH
bash ~/develop.backend-v1.humae.com.mx/deploy.sh

Por qué este flujo

A diferencia del frontend (donde Next standalone trae su propio node_modules), Laravel necesita vendor/ instalado en el servidor. Pero subir vendor/ cada deploy serían ~50 MB, hace lentísimo el upload por File Manager y arriesga corrupción. Mejor:

  1. Local: zip con todo MENOS vendor/, .env* y caches → ~400 KB.
  2. Server: preserva el .env y el vendor/. Si composer.lock cambió → corre composer install --no-dev solo cuando hace falta.

Hay dos scripts que automatizan todo:

ScriptDónde correQué hace
humae_backend/scripts/build-deploy-zip.shTu MacEmpaqueta app/, bootstrap/, config/, database/, lang/, public/, resources/, routes/, storage/ (vacío), artisan, composer.json/lock, .htaccess. Excluye vendor/, .env*, node_modules/, tests/, .git/, storage/logs/*, caches.
humae_backend/scripts/server-deploy.shSSH cPanelRespalda .env, extrae el zip, restaura .env, detecta si composer.lock cambió y solo entonces reinstala vendor, limpia caches, re-cachea config/routes/views/events, reinicia queue, corre smoke test.

Setup inicial (una sola vez)

1. Subir el script de deploy del server

Vía cPanel File Manager, sube humae_backend/scripts/server-deploy.sh a:

/home2/humaecom/develop.backend-v1.humae.com.mx/deploy.sh

Dale permisos 755 desde Right click → Permissions.

2. Asegurarte de que .env y vendor/ ya estén en el server

Si es la primera vez, sigue el primer deploy del backend original — necesitas:

  • .env en ~/develop.backend-v1.humae.com.mx/.env con todos los secrets
  • vendor/ instalado: composer install --no-dev --optimize-autoloader
  • DB migrada: php artisan migrate --force

A partir de ahí, los deploys subsecuentes son los 3 comandos del TL;DR.

3. Configurar los cronjobs

cPanel no tiene Supervisor, así que el cron de cPanel es lo que mantiene vivos tanto el Laravel Scheduler como el queue worker. Sin esto, jobs como ExpireMembershipsJob nunca corren y las notificaciones encoladas se quedan dormidas para siempre.

Necesitas dos entries de cron, ambas cada minuto:

cron
# 1) Laravel scheduler — dispara los Schedule::job() registrados en routes/console.php
* * * * * /opt/cpanel/ea-php83/root/usr/bin/php /home2/humaecom/develop.backend-v1.humae.com.mx/artisan schedule:run >> /dev/null 2>&1

# 2) Queue worker — procesa la cola y se autoapaga antes del próximo tick
* * * * * /opt/cpanel/ea-php83/root/usr/bin/php /home2/humaecom/develop.backend-v1.humae.com.mx/artisan queue:work --stop-when-empty --tries=3 --max-time=50 --sleep=3 >> /dev/null 2>&1

Por qué el segundo así: --stop-when-empty hace que termine cuando se vacía la cola; --max-time=50 lo mata si pasa de 50 s (antes de que el cron lo dispare otra vez). Resultado: nunca corren dos workers simultáneos y no se necesita Supervisor.

Alternativa de una sola entry

Si prefieres una única línea de cron, registra el worker desde el Scheduler en routes/console.php:

php
Schedule::command('queue:work --stop-when-empty --max-time=50')
    ->everyMinute()
    ->withoutOverlapping()
    ->onOneServer();

Y elimina la entry #2 del crontab. Solo te queda la del schedule:run. Requiere CACHE_STORE=redis o database para que onOneServer()/withoutOverlapping() tomen lock.

Cómo agregarlas en cPanel

Vía UI: Home → Advanced → Cron Jobs → "Add New Cron Job":

  • Common Settings: Every Minute (* * * * *)
  • Command: pega la línea completa (PHP path absoluto + artisan + flags)
  • Add New Cron Job → repite para la segunda

Vía SSH: crontab -e, pega ambas líneas, guarda. Verifica con crontab -l.

No uses php pelado

En cPanel php puede apuntar a una versión vieja (5.6, 7.x) según el alias de la cuenta. Siempre usa el path absoluto /opt/cpanel/ea-php83/root/usr/bin/php. Si la cuenta corre otra versión de PHP, ajusta ea-php83 — verifica con ls /opt/cpanel/ | grep ea-php. Una entry de cron con la versión equivocada falla silenciosa: el log de Laravel queda en blanco.

Verificar que están vivos

bash
# 1. El crontab tiene las dos entries
crontab -l | grep -E 'schedule:run|queue:work'

# 2. Laravel ve los schedules registrados
cd ~/develop.backend-v1.humae.com.mx
/opt/cpanel/ea-php83/root/usr/bin/php artisan schedule:list
# → debe listar al menos: memberships:expire   00:15  America/Mexico_City

# 3. Forzar un tick manual con verbose
/opt/cpanel/ea-php83/root/usr/bin/php artisan schedule:run -v

# 4. El worker está procesando — últimas líneas del log de hoy
tail -n 50 storage/logs/laravel-$(date +%F).log | grep -E 'Processing|Processed|Failed'

# 5. Jobs fallidos
/opt/cpanel/ea-php83/root/usr/bin/php artisan queue:failed

Si schedule:list muestra los jobs pero ninguno ha corrido en 24 h, casi seguro el cron de cPanel no está disparando: revisa la entry, el path absoluto a PHP y que el cPanel no tenga el cron de la cuenta deshabilitado (Hosting → Cron Jobs muestra un warning si está apagado).

Para el catálogo completo de jobs (frecuencias, qué hace cada uno, troubleshooting específico de cada job) ver Cronjobs y tareas programadas.

Deploys subsecuentes (flujo rápido)

En tu Mac

bash
cd humae_backend
composer deploy:zip

El script imprime al final:

✅ Zip listo: /Users/.../Desktop/humae-backend.zip (375K)

Sube el zip

cPanel File Manager/home2/humaecom/develop.backend-v1.humae.com.mx/Upload → arrastra ~/Desktop/humae-backend.zip. Sobrescribe si ya hay uno.

En SSH

bash
bash ~/develop.backend-v1.humae.com.mx/deploy.sh

Lo que hace:

  1. Verifica que el zip existe.
  2. Calcula MD5 de composer.lock actual (antes de extraer).
  3. Respalda .env a ~/_humae_backend_env_backup.
  4. Extrae el zip — NO toca vendor/, .env, ni storage/{logs,framework,app/public,app/private}.
  5. Restaura .env.
  6. Aplica permisos 775 a storage/ y bootstrap/cache/.
  7. Calcula MD5 de composer.lock nuevo. Si cambió → corre composer install --no-dev --optimize-autoloader. Si no → mantiene el vendor/ actual.
  8. view:clear, config:clear, route:clear, event:clear, luego config:cache, route:cache, view:cache, event:cache.
  9. queue:restart.
  10. Smoke test sobre 3 endpoints:
GET  /up                                       → 200
POST /api/v1/auth/login                        → 422 (esperado, body inválido)
POST /api/v1/webhooks/stripe (invalid sig)     → 400 (esperado, firma mala)

Si /up no devuelve 200, el script te muestra cómo ver los logs.

Qué incluye y qué NO incluye el zip

✅ Incluye

app/                       Toda la lógica de la app (Controllers, Services, Models, etc.)
bootstrap/                 Config bootstrap
config/                    Configuración de Laravel
database/                  Migraciones, factories, seeders
lang/                      Traducciones (lang/es/, lang/es.json)
public/                    Document root (index.php, .htaccess, /img/, /storage/)
resources/                 Vistas, plantillas Blade (incluye plantillas de email customizadas)
routes/                    Definición de rutas API/web/console
storage/                   Estructura vacía con .gitkeep (no logs, no caches)
artisan                    CLI de Laravel
composer.json              Lista de dependencias (para detectar cambios)
composer.lock              Lock file (para `composer install` reproducible)
.htaccess                  Apache rewrite rules

❌ NO incluye (y por qué)

ExcluidoRazón
vendor/Ya está en el server. Solo se reinstala si composer.lock cambió.
.env y .env.*El .env de producción tiene secrets que NO deben pisarse en cada deploy.
node_modules/Backend Laravel no usa Node en runtime.
storage/logs/*Los logs del server no deben sobrescribirse.
storage/framework/{cache,sessions,views}/*Caches que se regeneran solas.
storage/app/{public,private}/*Avatares y documentos subidos por usuarios.
bootstrap/cache/*.phpConfigs cacheadas, se regeneran con config:cache.
tests/No se necesitan en producción.
.git/, .phpunit.cache/Metadata de desarrollo.

Estructura final en el server

/home2/humaecom/develop.backend-v1.humae.com.mx/
├── .env                           ← preservado entre deploys
├── .htaccess
├── deploy.sh                      ← script de deploy (subido una vez)
├── humae-backend.zip              ← último zip subido
├── app/                           ← reemplazado en cada deploy
├── bootstrap/
│   └── cache/                     ← regenerado por config:cache
├── config/
├── database/
├── lang/
├── public/                        ← document root del dominio
│   ├── index.php
│   ├── .htaccess
│   ├── img/
│   │   └── humae-logo.png
│   └── storage/                   ← symlink a ../storage/app/public
├── resources/
├── routes/
├── storage/                       ← preservado entre deploys
│   ├── app/
│   │   ├── public/                ← avatares (preservado)
│   │   └── private/               ← documentos (preservado)
│   ├── framework/                 ← caches (preservado, se autoregeneran)
│   └── logs/                      ← preservado
├── vendor/                        ← solo se reinstala si composer.lock cambió
├── artisan
├── composer.json
└── composer.lock

Cuándo composer install se dispara

Solo cuando agregas/actualizas/quitas paquetes:

bash
# En local
composer require nuevo/paquete
# o
composer update vendor/paquete

Esto modifica composer.lock. Cuando hagas composer deploy:zip, el zip incluye el composer.lock nuevo. El script en server detecta que el hash MD5 cambió y corre:

composer install --no-dev --optimize-autoloader --no-interaction

Si solo cambias código (Controllers, Services, Models, vistas, plantillas de email, traducciones), composer.lock no cambia y composer install se salta — el deploy es instantáneo.

Troubleshooting

SíntomaCausaFix
/up da 503 después de deployDB inalcanzable o config corruptatail -n 100 storage/logs/laravel-$(date +%Y-%m-%d).log y revisar .env
Cambios no se reflejanConfig cacheadoEl script ya hace config:clear && config:cache. Si persiste, borra manualmente bootstrap/cache/config.php y bootstrap/cache/routes-v7.php.
composer install fallaMemoria insuficienteExportar COMPOSER_MEMORY_LIMIT=-1 antes de correr bash deploy.sh
.env desapareció después de extractEl backup quedó en otro pathRestaurar desde ~/_humae_backend_env_backup (el script siempre genera este)
Plantillas de email no cambianVista cacheadaEl script ya hace view:clear. Si persiste: rm -rf storage/framework/views/*
Webhook Stripe da "Invalid signature" después de deployConfig cacheada con secret viejophp artisan config:clear && php artisan config:cache && php artisan queue:restart
Permisos 500 en logo / storagechmod no se aplicóRe-ejecutar manualmente: chmod -R 775 storage bootstrap/cache
Membresías no se marcan expired / correos encolados nunca lleganCron de cPanel no configurado, o usando php viejo en vez de /opt/cpanel/ea-php83/root/usr/bin/phpVer §Configurar los cronjobs

Apéndice: contenido de los scripts

build-deploy-zip.sh (corre en local)

Lo invocas con composer deploy:zip (registrado en composer.json). El script:

  1. Verifica que estás en el root de humae_backend/.
  2. Borra el zip anterior si existe.
  3. Empaqueta los directorios incluidos, excluyendo todo lo de la sección "NO incluye".
  4. Inyecta .gitkeep en las carpetas vacías de storage/ y bootstrap/cache/ (porque zip no preserva carpetas vacías).
  5. Imprime el path final y los siguientes pasos.

server-deploy.sh (renombrado a deploy.sh en el server)

Maneja todo el ciclo de extracción + restore + composer condicional + cache + restart + smoke test.

Idempotente: si lo corres dos veces sin un zip nuevo, simplemente vuelve a extraer el mismo zip y re-cachear (no rompe nada).

Referencias

Manual de usuario HUMAE · Uso interno