# OpenSEO self-host — Coolify stack (LXC 102) # # Por qué este compose en lugar del repo upstream (`every-app/open-seo`) vía # railpack: # - el deploy anterior (uuid kj0kccsb4d46tm0d6qe6docy) terminó con Caddy # respondiendo 404 a todo porque /app/dist no existía en la imagen cacheada # (Coolify saltó el build: "No build configuration changed & image found ... # Build step skipped"). # - el README upstream lo dice explícitamente: "We recommend self-hosting # with Cloudflare as opposed to Railway, Coolify or Dokploy. We plan to # make it simpler to host on those platforms in the next few months." # - esta imagen (`ghcr.io/every-app/open-seo:sha-c469a48`) es la build del # mismo commit, pero el build de Vite corre en `docker-entrypoint.sh` al # arrancar el contenedor, no en el build de la imagen. Y el entrypoint ya # tiene la lógica de fingerprint para no reconstruir cuando los env vars # relevantes no cambiaron. # # Contrato: docs/AGENTS-coolify-apps.md # - sin publicar 80/443: solo `expose`, Traefik enruta (§2.3) # - SECRETOS vía env vars inyectados por Coolify (§2.5) # - volumen con nombre para /app/.wrangler (SQLite que sobrevive al redeploy, §5) # - healthcheck independiente de servicios externos al boot # - AUTH_MODE=local_noauth porque aquí no hay TEAM_DOMAIN/POLICY_AUD de # Cloudflare Access. Si más adelante se quiere proteger con auth, cambiar # a AUTH_MODE=cloudflare_access + TEAM_DOMAIN + POLICY_AUD. # - ALLOWED_HOST es obligatorio detrás del túnel: sin él Vite bloquea toda # petición externa con "Blocked request" (ver preflight info level). services: app: image: 'ghcr.io/every-app/open-seo:sha-c469a48' environment: # Puerto en el que escucha `vite preview` (per Dockerfile.selfhost / entrypoint). - PORT=3001 # Single admin user, sin pantalla de login. NO exponer públicamente sin # poner tu propia auth delante — el preflight lo dice literal. - AUTH_MODE=local_noauth # Host header permitido. Es el FQDN público por el que llega el tráfico # desde el túnel de Cloudflare. - ALLOWED_HOST=openseo.urieljareth.org # Requerido por el runtime workerd para exponer process.env a los # bindings (lo exige el compose upstream). - CLOUDFLARE_INCLUDE_PROCESS_ENV=true # SEO data (opcional). Vacío = la app arranca, los workflows SEO # devuelven "no data". Se setea después vía Coolify env. - DATAFORSEO_API_KEY=${DATAFORSEO_API_KEY:-} # Telemetry opt-out (también vía DO_NOT_TRACK). Por defecto apagado. - OPENSEO_TELEMETRY_DISABLED=${OPENSEO_TELEMETRY_DISABLED:-} - DO_NOT_TRACK=${DO_NOT_TRACK:-} # AI features (SAM, el agente SEO integrado). Vacío = SAM deshabilitado. - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} - OPENROUTER_MODEL=${OPENROUTER_MODEL:-} expose: - '3001' # El endpoint /api/health lo sirve el propio preflight (ver # src/lib/selfhost-preflight.ts) sin auth — seguro para el healthcheck. # Usamos `node` directamente porque la imagen es node:22 y el HEALTHCHECK # upstream hace exactamente esto. healthcheck: test: - CMD-SHELL - "node -e \"fetch('http://127.0.0.1:3001/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\"" interval: 30s timeout: 10s retries: 5 # Primer arranque: preflight + migrations + vite build (1-2 min). start_period: 300s volumes: - 'openseo-data:/app/.wrangler' restart: unless-stopped logging: driver: json-file options: max-size: '10m' max-file: '3' volumes: openseo-data: {}