# Despliegue en producción (Docker) Stack: `app` (PHP-FPM 8.3) + `nginx` (estáticos y proxy) + `db` (PostgreSQL 16) + `redis` (cache/sesión/colas) + `queue` (worker) + `scheduler`. ## 1. Preparar el `.env` ```bash cp .env.production.example .env ``` Editar `.env` y completar como mínimo: - `APP_URL` — dominio real, con `https://`. - `DB_PASSWORD` — contraseña de PostgreSQL (usada también por el servicio `db`). - `SESSION_DOMAIN` — dominio (o `.tu-dominio.com` si hay subdominios). - `APP_KEY` — generar uno nuevo, no reutilizar el de desarrollo: ```bash docker run --rm -v "$PWD":/app -w /app composer:2 sh -c \ "php -r \"echo 'base64:'.base64_encode(random_bytes(32)).PHP_EOL;\"" ``` Pegar el resultado en `APP_KEY=`. ## 2. Levantar el stack ```bash docker compose -f docker-compose.prod.yml up -d --build ``` En el primer arranque, el contenedor `app` (ver `docker/php/entrypoint.sh`): 1. Genera las claves RSA de Passport (`storage/oauth-private.key` / `.pub`) si no existen — quedan persistidas en el volumen `app_storage`, **no se regeneran en cada deploy** (regenerarlas invalidaría todos los tokens ya emitidos). 2. Corre `php artisan migrate --force`. 3. Cachea config/rutas/vistas y regenera la documentación de Swagger. ```bash docker compose -f docker-compose.prod.yml logs -f app ``` ## 3. Crear el primer usuario admin **Recomendado para producción:** no correr `db:seed` (ese seeder crea datos de demo con contraseñas conocidas). En su lugar, con la base de usuarios vacía, andá a `/register` y creá una cuenta — al ser la primera, el sistema la marca automáticamente como **admin**, aprobada y con la sesión iniciada (no hace falta que nadie la apruebe, porque todavía no existe ningún admin). Cualquier registro posterior vuelve a requerir aprobación manual desde el panel de usuarios. Alternativa solo para entornos de prueba/demo, con datos de ejemplo (usuarios, clientes OAuth, historial de logins falso): ```bash docker compose -f docker-compose.prod.yml exec app php artisan db:seed --force ``` Esto sí crea `admin@example.com` / `password` — **cambiar la contraseña de inmediato** si se usa fuera de un entorno descartable. ## 4. TLS Por defecto `nginx` sirve solo HTTP en el puerto 80 (`docker/nginx/default.conf`). Para HTTPS: 1. Conseguir certificado (por ejemplo con `certbot --webroot` apuntando a `docker/nginx/certs`, o cualquier CA propia). 2. Colocar `fullchain.pem` y `privkey.pem` en `docker/nginx/certs/`. 3. Descomentar el bloque `server { listen 443 ... }` en `docker/nginx/default.conf` y el `location /` que redirige a HTTPS. 4. Descomentar `"443:443"` en `docker-compose.prod.yml` (servicio `nginx`). 5. `docker compose -f docker-compose.prod.yml up -d --build nginx`. ## 5. Desplegar una actualización ```bash git pull # o el mecanismo que uses para traer código nuevo docker compose -f docker-compose.prod.yml up -d --build ``` Las claves de Passport, la base de datos y Redis viven en volúmenes con nombre (`app_storage`, `db_data`, `redis_data`) — sobreviven a los rebuilds. El código y los assets del frontend, en cambio, se hornean dentro de la imagen en cada build (no hay bind mounts de código en producción). ## 6. Backups Mínimo indispensable a respaldar: - Volumen `db_data` (o mejor, `pg_dump` periódico contra el servicio `db`). - Volumen `app_storage` — contiene, entre otras cosas, las claves de Passport. Perderlas invalida todos los tokens emitidos hasta ese momento. ```bash docker compose -f docker-compose.prod.yml exec db \ pg_dump -U "$DB_USERNAME" "$DB_DATABASE" > backup-$(date +%F).sql ``` ## Servicios del compose | Servicio | Rol | |-------------|-----| | `app` | PHP-FPM. Corre migraciones y cacheo al arrancar (`CONTAINER_ROLE=app`). | | `queue` | `php artisan queue:work redis` — misma imagen que `app`, sin el paso de migración. | | `scheduler` | `php artisan schedule:work` — reemplaza al cron del sistema. | | `nginx` | Sirve `public/build` y hace proxy de `*.php` a `app:9000`. | | `db` | PostgreSQL 16, datos en el volumen `db_data`. | | `redis` | Cache, sesiones y colas, datos en el volumen `redis_data` (AOF activado). |