Actualiza toolkit operativo y documentación

This commit is contained in:
urieljareth
2026-09-10 20:53:50 -06:00
parent 3b7209dcc1
commit 714057bfc8
69 changed files with 6023 additions and 384 deletions
+196
View File
@@ -0,0 +1,196 @@
# Caso: firecrawl llevaba meses `exited` y sin URL — stack mínimo con imágenes precompiladas
> Resuelto el **2026-08-24** contra el host real.
> Target: **LXC 102** (`coolify`), proyecto `AI AGENCY` / `production`.
> Resultado: `https://firecrawl.urieljareth.org` → **HTTP 200**, scrape real
> verificado (`"success": true`).
---
## 0. Resumen ejecutivo
La app `firecrawl` de Coolify (`build_pack=dockercompose`, uuid
`du3iknyvy22vap767t9tnf9s`) estaba `exited:unhealthy` y sin dominio.
**Causa:** seguía `git_branch: main`, y firecrawl upstream se rediseñó. El compose
de `main` hoy trae **7 servicios**, incluidos **FoundationDB, RabbitMQ y
nuq-postgres**, con **3 compilados desde fuente** (`apps/api`,
`apps/playwright-service-ts`, `apps/nuq-postgres`) y `mem_limit: 8G` en `api` más
`4G` en playwright. Este LXC tiene **4 cores** y el disco escribe a ~26 ms.
Compilar Chromium ahí es el peor caso posible.
**Solución:** se dejó de seguir upstream. Nuevo **service** de Coolify con un
compose propio de **5 servicios y cero compilaciones**, todo con imágenes ya
publicadas. Vive en
[`stacks/firecrawl/docker-compose.coolify.yml`](../../stacks/firecrawl/docker-compose.coolify.yml).
**La URL no se había perdido ese día:** `docker_compose_domains` estaba vacío, el
compose generado no tenía ninguna regla `Host(...)` y Traefik **nunca** había
emitido certificado para un dominio de firecrawl. Tampoco había ningún despliegue
desde antes del 2026-07-31.
---
## 1. El stack que sí aguanta este host
| Servicio | Imagen | Notas |
|---|---|---|
| `api` | `ghcr.io/firecrawl/firecrawl:2.10.19` | pinado; sirve en **3002** |
| `playwright-service` | `ghcr.io/firecrawl/playwright-service:latest` | no publica tags de versión |
| `nuq-postgres` | `ghcr.io/firecrawl/nuq-postgres:latest` | sustituye al build de `apps/nuq-postgres` |
| `redis` | `redis:alpine` | sin persistencia (`--save "" --appendonly no`) |
| `rabbitmq` | `rabbitmq:3-management` | |
Fuera quedaron **`foundationdb` y `foundationdb-init`**: solo se usan si
`NUQ_BACKEND` está definido, y aquí se deja vacío a propósito.
Solo hay **versiones 2.10.x** publicadas (2.10.1 … 2.10.19). No existe una línea
antigua más liviana a la que bajarse.
### RabbitMQ da errores y no pasa nada
En el log de `api` aparece, de forma normal:
```
NuQ sender connection error ... "Cannot get a message from queue
'nuq.queue_scrape.prefetch' in vhost '/': noproc"
NuQ sender get failed, falling back to postgres
```
Es **degradación controlada**: la cola cae a postgres y firecrawl funciona. No es
el problema que hay que perseguir si algo va mal.
---
## 2. Las tres trampas que costaron tiempo
### 2.1 La imagen no trae `wget` — y el healthcheck decide si hay ruta
Verificado dentro del contenedor:
| Binario | ¿Está? |
|---|---|
| `curl` | sí (`/usr/bin/curl`) |
| `wget` | **NO** |
| `nc` | **NO** |
Y los endpoints:
| Ruta | Código |
|---|---|
| `/` | **200** |
| `/is-production` | 200 |
| `/test` | 404 |
| `/health` | 404 |
| `/v1/health` | 404 |
Un healthcheck con `wget` o contra `/health` **falla siempre**. Y como Traefik
solo enruta contenedores `healthy`, el dominio devuelve `503 no available server`
aunque la app esté perfectamente viva y escuchando en 3002 (ver
[el caso del 503](coolify-servicio-nuevo-503-no-available-server.md)).
El que funciona:
```yaml
healthcheck:
test: ['CMD', 'curl', '-fsS', '-o', '/dev/null', 'http://127.0.0.1:3002/']
```
**Comprueba siempre qué binarios y qué rutas existen antes de escribir un
healthcheck:**
```powershell
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker exec <cont> sh -c 'command -v curl wget nc'"
```
### 2.2 El worker rechaza todo por carga
```
Can't accept connection due to RAM/CPU load
```
Los umbrales por defecto (`MAX_RAM`/`MAX_CPU` = 0.8) se superan constantemente en
un host compartido. Con `MAX_RAM: 0.95` y `MAX_CPU: 0.95` acepta trabajo.
### 2.3 `api` se queda en `Created` en el primer deploy
En el primer `compose up`, `api` quedó **`Created`** y nunca arrancó: sus
`depends_on: service_healthy` (rabbitmq y nuq-postgres) tardaron más que el
proceso de deploy. Un `restart` del service con las dependencias ya sanas lo
resolvió. Si ves `Created` sin logs ni error, no está roto: reinicia el service.
---
## 3. Cómo se reproduce
```powershell
. .\.env.local.ps1
.\deploy_skill\scripts\New-CoolifyService.ps1 `
-AppPath .\stacks\firecrawl `
-AppName firecrawl-min `
-Fqdn https://firecrawl.urieljareth.org `
-PrimaryService api `
-ProjectName "AI AGENCY" -EnvironmentName production -NoDeploy
# Secretos: Coolify siembra las variables desde los ${...} del compose con su
# valor por defecto. Hay que sobrescribir las que deben ser secretas por PATCH
# (POST devuelve 409 si ya existe): POSTGRES_PASSWORD y BULL_AUTH_KEY.
.\coolify_skill\scripts\Test-CoolifyServiceReady.ps1 -Uuid <uuid> -WaitSeconds 600
```
Verificación funcional (responder 200 en `/` no prueba que funcione):
```powershell
Invoke-RestMethod -Uri "https://firecrawl.urieljareth.org/v1/scrape" -Method POST `
-ContentType 'application/json' -Body '{"url":"https://example.com","formats":["markdown"]}'
# success = True
```
---
## 4. Tres bugs del toolkit que este caso destapó
Los tres estaban impidiendo que `New-CoolifyService.ps1` funcionara. **Corregidos
y verificados el 2026-08-24.**
1. **`type` junto a `docker_compose_raw`.** El script enviaba
`type = "one-click-service"` con un comentario que afirmaba que Coolify acepta
cualquier string. Es falso: la API responde
`422 "You cannot provide both service type and docker_compose_raw."`.
`type` es solo para servicios de la librería. **Se eliminó.**
2. **`docker_compose_raw` sin base64.** Se enviaba en crudo y la API responde
`422 "The docker_compose_raw should be base64 encoded."`.
3. **`Invoke-CoolifyApi.ps1` mandaba el body como *string*.** PowerShell 5.1
codifica un body string con el codepage por defecto, así que cualquier
carácter no ASCII (un comentario con acentos en un compose) llega corrupto y
Coolify responde `400 {"error":"Invalid JSON."}`. Ahora manda bytes UTF-8 con
`charset=utf-8`. **Afectaba a todo POST/PATCH**, no solo a los servicios.
### Y una inconsistencia que sigue abierta
`Test-PreDeployChecklist.ps1` solo escanea `docker-compose.yml|yaml` y
`compose.yml|yaml`, pero el default de `New-CoolifyService.ps1` es
`docker-compose.coolify.yml`. **Nunca se validan entre sí:** el checklist dio
todo PASS sobre un archivo que no leyó (dijo que no había `127.0.0.1` cuando sí
lo había). Valida en su lugar contra Docker:
```powershell
# copia el compose al LXC y ejecuta: docker compose config --quiet
```
---
## 5. Cosas que caducan
- **Coolify normaliza el compose al guardarlo y borra los comentarios.** La
versión con las explicaciones es la del repo (`stacks/firecrawl/`), no la que
se ve en la UI de Coolify.
- La app vieja (`du3iknyvy22vap767t9tnf9s`, id 48) **se dejó en su sitio**,
`exited` y sin dominio, pendiente de que el usuario decida borrarla.
- `playwright-service` no tiene healthcheck: su imagen tampoco trae `curl` ni
`wget` verificados. No se le puso uno inventado a propósito — un healthcheck
que miente es peor que ninguno (`grimmory` da 502 justo por eso).