Skip to content

Infraestructura

Requisitos de servidor, base de datos, Redis, queue worker, scheduler y backups. Esta página es neutra al proveedor — funciona igual en VPS propio, Render, Railway, Laravel Forge o AWS.

Stack mínimo

ComponenteVersiónMemoriaUso
PHP8.3+512 MBBackend Laravel
MySQL8.0+1 GBDB principal
Redis6.0+256 MBCache + queue + sesiones
Node.js20+256 MBBuild del frontend
Nginx1.22+64 MBReverse proxy + static files

Stack recomendado para MVP (hasta 1000 usuarios activos): 1 VPS de 2 vCPU + 4 GB RAM ($20–$40/mes en DigitalOcean, Linode, Hetzner).


Backend — servidor PHP

Instalación de PHP 8.3 (Ubuntu 24.04)

bash
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y \
  php8.3 php8.3-fpm php8.3-cli \
  php8.3-mysql php8.3-mbstring php8.3-xml \
  php8.3-curl php8.3-zip php8.3-gd php8.3-bcmath \
  php8.3-intl php8.3-redis php8.3-opcache

# Verificar
php -v          # → PHP 8.3.x

Extensiones requeridas

mbstring, intl, pdo_mysql, bcmath, zip, gd, curl, xml, opcache, redis

Composer

bash
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

Deploy del backend

bash
cd /var/www
sudo git clone https://github.com/tu-org/humae-backend humae_backend
cd humae_backend

# Composer
composer install --optimize-autoloader --no-dev

# Env
cp .env.example .env
# ... editar .env con los valores de producción ...
php artisan key:generate

# Migrar + seedear
php artisan migrate --force
php artisan db:seed --force

# Cachear config/rutas/views/events
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

# Permisos
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache

Nginx para Laravel

nginx
server {
    listen 443 ssl http2;
    server_name api.humae.com.mx;

    ssl_certificate     /etc/letsencrypt/live/api.humae.com.mx/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.humae.com.mx/privkey.pem;

    root /var/www/humae_backend/public;
    index index.php;

    client_max_body_size 12M;  # 10MB docs + overhead

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\. { deny all; }

    access_log /var/log/nginx/humae_api_access.log;
    error_log  /var/log/nginx/humae_api_error.log;
}

PHP-FPM pool tuning

/etc/php/8.3/fpm/pool.d/www.conf:

ini
pm = dynamic
pm.max_children = 10        ; ~50 MB por child → 500 MB max
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 4
pm.max_requests = 500       ; reciclar workers para evitar leaks

OPcache (obligatorio en prod)

/etc/php/8.3/fpm/conf.d/10-opcache.ini:

ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0   ; en prod: 0. En dev: 1.
opcache.revalidate_freq=0
opcache.fast_shutdown=1

opcache.validate_timestamps=0 significa que tras cada deploy debes ejecutar sudo service php8.3-fpm reload para que OPcache lea el código nuevo.


Base de datos

MySQL 8

Instalar:

bash
sudo apt install mysql-server-8.0
sudo mysql_secure_installation

Crear DB y usuario:

sql
CREATE DATABASE humae_production
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

CREATE USER 'humae_app'@'localhost' IDENTIFIED BY 'password_fuerte_y_largo';
GRANT ALL PRIVILEGES ON humae_production.* TO 'humae_app'@'localhost';
FLUSH PRIVILEGES;

Config mínima /etc/mysql/mysql.conf.d/mysqld.cnf

ini
[mysqld]
# Conexiones
max_connections = 150
max_allowed_packet = 64M

# InnoDB
innodb_buffer_pool_size = 1G       # ~50% de la RAM del servidor
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 1

# Slow query log (útil para debugging)
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

# Character set
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

Opciones managed (sin servidor propio)

  • DigitalOcean Managed MySQL — $15/mes para 1 GB
  • AWS RDS MySQL — desde $13/mes (db.t4g.micro)
  • PlanetScale — free tier 5 GB (usa branches de DB, requiere planet-scale CLI)

Si usas managed, asegúrate de:

  • Configurar allowlist de IP del servidor backend
  • Activar SSL/TLS para la conexión
  • Habilitar backups automáticos

Backups

bash
# Crear script /usr/local/bin/humae-db-backup.sh
#!/bin/bash
DATE=$(date +%F)
DUMP_FILE=/tmp/humae_$DATE.sql.gz
mysqldump -u humae_app -p"$DB_PASS" humae_production | gzip > $DUMP_FILE
aws s3 cp $DUMP_FILE s3://humae-backups/db/
rm $DUMP_FILE
bash
# Crontab del root: daily 3am UTC
0 3 * * * /usr/local/bin/humae-db-backup.sh

Retención: 30 días en S3 con lifecycle rule, 7 días locales.


Redis

Self-hosted

bash
sudo apt install redis-server

/etc/redis/redis.conf:

conf
bind 127.0.0.1 ::1          # solo localhost si el backend está en el mismo server
requirepass <password>
maxmemory 256mb
maxmemory-policy allkeys-lru
save 900 1                  # persist snapshot cada 15 min
save 300 10
save 60 10000
bash
sudo systemctl enable redis-server
sudo systemctl start redis-server

Opciones managed

  • Upstash — free tier generoso (10k commands/día serverless)
  • Redis Cloud — free 30 MB

Verificación

bash
redis-cli -h localhost -a $REDIS_PASSWORD ping
# → PONG

Almacenamiento local

Los archivos subidos por los usuarios (avatares + documentos) se guardan en humae_backend/storage/app. No se usa ningún blob storage externo.

Dimensionamiento

  • ~2.5 MB por candidato activo (avatar WebP + 5 documentos PDF promedio).
  • Provisión inicial ~50 GB en el volumen que hospeda storage/app.
  • Alertar al 70 % de uso para escalar el volumen antes de llegar a 90 %.

Volumen persistente

Si se usa Docker / Kubernetes, montar storage/app como volumen persistente (no en el filesystem efímero del container).

En VPS tradicional basta con que storage/ esté en el disco principal y el disco tenga espacio.

Permisos

bash
sudo chown -R www-data:www-data /var/www/humae_backend/storage
sudo chmod -R 775 /var/www/humae_backend/storage
cd /var/www/humae_backend && sudo -u www-data php artisan storage:link

Backup offsite con restic

bash
sudo apt install -y restic
restic -r s3:s3.amazonaws.com/humae-storage-backup init

# Script /usr/local/bin/humae-storage-backup.sh
#!/bin/bash
export RESTIC_PASSWORD_FILE=/root/.restic-pass
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
restic -r s3:s3.amazonaws.com/humae-storage-backup backup /var/www/humae_backend/storage/app
restic -r s3:s3.amazonaws.com/humae-storage-backup forget --keep-daily 14 --keep-weekly 12 --keep-monthly 12 --prune
# Cron: diario 4am UTC
0 4 * * * /usr/local/bin/humae-storage-backup.sh

Restauración de emergencia

bash
restic -r s3:s3.amazonaws.com/humae-storage-backup snapshots
restic -r s3:s3.amazonaws.com/humae-storage-backup restore <snapshot-id> --target /var/www/humae_backend
sudo chown -R www-data:www-data /var/www/humae_backend/storage

Ver contrato de storage en Storage local.


SMTP (Postfix)

Los correos transaccionales se envían desde un Postfix corriendo en el mismo servidor del backend. Laravel habla SMTP a 127.0.0.1:25; Postfix entrega saliente por puerto 25.

Instalación

bash
sudo apt install -y postfix opendkim opendkim-tools mailutils
# Setup: "Internet Site", mail name = mail.humae.com.mx

/etc/postfix/main.cf mínimo

conf
myhostname = mail.humae.com.mx
myorigin = humae.com.mx
mydestination = $myhostname, localhost.$mydomain, localhost
inet_interfaces = loopback-only
mynetworks = 127.0.0.0/8 [::1]/128

smtp_tls_security_level = may
smtp_tls_CApath = /etc/ssl/certs

maximal_queue_lifetime = 1d
bounce_queue_lifetime = 1d

# Integración con OpenDKIM
milter_default_action = accept
smtpd_milters = local:/run/opendkim/opendkim.sock
non_smtpd_milters = local:/run/opendkim/opendkim.sock
bash
sudo systemctl enable --now postfix opendkim

DKIM con OpenDKIM

bash
sudo mkdir -p /etc/opendkim/keys/humae.com.mx
sudo opendkim-genkey -s default -d humae.com.mx -D /etc/opendkim/keys/humae.com.mx
sudo chown -R opendkim:opendkim /etc/opendkim/keys
cat /etc/opendkim/keys/humae.com.mx/default.txt
# → copiar el TXT a DNS: default._domainkey.humae.com.mx

Verificación de deliverability

bash
# Que Postfix esté escuchando
sudo ss -lntp | grep :25

# Mandar un correo de prueba con Laravel
cd /var/www/humae_backend
php artisan tinker --execute="\Mail::raw('smoke',fn(\$m)=>\$m->to('tu@correo.com')->subject('smoke'));"

# Seguir el log
sudo tail -f /var/log/mail.log

El receptor debe ver dkim=pass y spf=pass en los headers.

Puerto 25 saliente

Muchos providers bloquean 25 outbound por default. Abrir ticket al provider antes de deploy, o elegir uno que no bloquee.

Monitoreo

bash
mailq                    # cola pendiente de Postfix (debe estar casi vacía)
grep 'status=bounced' /var/log/mail.log | tail   # bounces recientes

Alertas recomendadas: mailq > 50, /var/log/mail.log con ráfagas de status=deferred, reputation check periódico en mxtoolbox.com.

Ver detalles y DNS completo en SMTP local.


Queue y scheduler

Queue worker con Supervisor

Instalar:

bash
sudo apt install supervisor

/etc/supervisor/conf.d/humae-queue.conf:

ini
[program:humae-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/humae_backend/artisan queue:work redis --queue=default --tries=3 --max-time=3600 --timeout=90
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/supervisor/humae-queue.log
stopwaitsecs=3600

Activar:

bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start humae-queue:*

Verificar:

bash
sudo supervisorctl status
# humae-queue:humae-queue_00   RUNNING   pid 12345, uptime 0:05:12
# humae-queue:humae-queue_01   RUNNING   pid 12346, uptime 0:05:12

Scheduler con cron

sudo crontab -e -u www-data:

* * * * * cd /var/www/humae_backend && php artisan schedule:run >> /dev/null 2>&1

Esto ejecuta los jobs programados. Para el catálogo completo de qué se ejecuta y cuándo, ver Cronjobs y tareas programadas.

Verificar:

bash
php artisan schedule:list

Failed jobs

Laravel guarda jobs fallidos en la tabla failed_jobs. Revisar con:

bash
php artisan queue:failed

Reintentar:

bash
php artisan queue:retry all

Admin puede verlos desde /admin/jobs-fallidos (Fase 2).


Frontend — Next.js

Opción A — Vercel (recomendada)

  1. Conectar repo GitHub a Vercel.
  2. Framework preset: Next.js (auto-detect).
  3. Root directory: humae_frontend.
  4. Build command: npm run build (default).
  5. Output directory: .next (default).
  6. Env vars: agregar NEXT_PUBLIC_API_URL etc.
  7. Domain: conectar humae.com.mx.

Deploy automático en cada push a main.

Opción B — Cloudflare Pages

Similar a Vercel. Build command: npm run build. Framework preset: Next.js.

Opción C — Self-hosted con PM2

En el mismo VPS (o uno dedicado al frontend):

bash
cd /var/www
git clone https://github.com/tu-org/humae-frontend humae_frontend
cd humae_frontend

npm install
npm run build

sudo npm install -g pm2
pm2 start npm --name humae-frontend -- start
pm2 save
pm2 startup   # activar al boot

Nginx reverse proxy:

nginx
server {
    listen 443 ssl http2;
    server_name humae.com.mx www.humae.com.mx;

    ssl_certificate /etc/letsencrypt/live/humae.com.mx/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/humae.com.mx/privkey.pem;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Docs — este manual

Opción A — Cloudflare Pages

Framework preset: VitePress
Build command: npm run build
Output directory: .vitepress/dist
Root directory: humae_docs

Opción B — Static file serving

bash
cd humae_docs && npm run build
# Contenido en .vitepress/dist/ se puede subir a cualquier static host

Deploy con Docker (alternativa)

HUMAE incluye docker-compose.yml listo para producción:

bash
cd "HUMAE NEW"
docker compose up -d

Servicios incluidos: mysql, redis, mailhog, backend, frontend.

Para prod real recomendamos agregar watchtower o similar para auto-update, y exponer solo los puertos necesarios detrás del reverse proxy.


Monitoreo operacional

Logs

bash
# Backend (Laravel)
tail -f /var/www/humae_backend/storage/logs/laravel-$(date +%F).log

# Nginx
tail -f /var/log/nginx/humae_api_error.log

# Queue worker
sudo supervisorctl tail -f humae-queue:humae-queue_00

# MySQL slow log
tail -f /var/log/mysql/slow.log

Recursos del servidor

bash
# CPU + RAM
htop

# Disco
df -h

# Conexiones
ss -s

Health externo

Curl desde otra máquina:

bash
curl -i https://api.humae.com.mx/api/v1/health

Debe devolver 200 OK con {"success":true,"data":{"app":"ok",...}}.

Siguiente

Verificación end-to-end post-deploy: Verificación post-deploy →

Manual de usuario HUMAE · Uso interno