Files

7.2 KiB

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.

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).

El que funciona:

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:

.\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

. .\.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):

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:

# 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).