79 lines
3.6 KiB
YAML
79 lines
3.6 KiB
YAML
# 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: {}
|