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), proyectoAI 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.
-
typejunto adocker_compose_raw. El script enviabatype = "one-click-service"con un comentario que afirmaba que Coolify acepta cualquier string. Es falso: la API responde422 "You cannot provide both service type and docker_compose_raw.".typees solo para servicios de la librería. Se eliminó. -
docker_compose_rawsin base64. Se enviaba en crudo y la API responde422 "The docker_compose_raw should be base64 encoded.". -
Invoke-CoolifyApi.ps1mandaba 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 responde400 {"error":"Invalid JSON."}. Ahora manda bytes UTF-8 concharset=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,exitedy sin dominio, pendiente de que el usuario decida borrarla. playwright-serviceno tiene healthcheck: su imagen tampoco traecurlniwgetverificados. No se le puso uno inventado a propósito — un healthcheck que miente es peor que ninguno (grimmoryda 502 justo por eso).