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