FireFeed Docs

Deploy

Come è deployato FireFeed in produzione.

Production usa app.fire-feed.com (Feed-Manager), oms.fire-feed.com (OMS) e docs.fire-feed.com. Development usa domini separati. Entrambi gli stack vivono sullo stesso host Dokploy con immagini, container, volumi e cookie distinti.

Compose

services:
  migrator: # prisma migrate deploy → exit 0/1 (init-container)
  web: # Next.js 15 Feed-Manager,    porta 3000  → exp.fire-feed.com
  worker: # Fastify + BullMQ,           porta 4000  (interno)
  oms: # Next.js 15 OMS sorella,     porta 3010  → oms.fire-feed.com
  docs: # Fumadocs standalone,        porta 3001  → docs.fire-feed.com
  db-studio: # Opzionale (profilo db-studio): Prisma Studio, loopback 5555 (namespace del proxy)
  db-studio-proxy: # Opzionale (profilo db-studio): Nginx + Basic Auth, porta 8080 → dominio admin HTTPS
networks:
  - firefeed-net # interno
  - dokploy-network # condiviso con Postgres e Redis managed
volumes:
  - firefeed-storage # condiviso web ↔ worker per file export scaricabili

Ogni ambiente deve valorizzare FIREFEED_DEPLOYMENT e FIREFEED_COOKIE_NAMESPACE con development o production. Il compose non imposta container_name: Docker Compose usa il project name Dokploy per isolare i container e i volumi. I tag immagine includono invece esplicitamente FIREFEED_DEPLOYMENT, evitando collisioni fra build sullo stesso host. FIREFEED_COOKIE_NAMESPACE e NEXT_PUBLIC_APP_URL sono passati anche come build arg alle immagini Next.js, così middleware e bundle client ricevono i valori corretti per ciascun ambiente.

Definito in docker-compose.prod.yml in repo root.

Prisma Studio — amministrazione database

Con il profilo db-studio abilitato (disattivato di default), lo stack avvia db-studio dopo le migrazioni, riutilizzando l'immagine worker e il database dell'ambiente. Il servizio db-studio-proxy protegge tutte le richieste con HTTP Basic Authentication. Le credenziali sono separate dal login FireFeed: chi accede può leggere e modificare i dati direttamente, incluse le cancellazioni, senza i controlli applicativi per tenant.

Prima configurazione

  1. Generare credenziali diverse per development e production. Sul Mac/server con htpasswd installato, eseguire il comando seguente: la password viene richiesta interattivamente e non finisce nella cronologia della shell.

    htpasswd -nB -C 10 studio-admin | sed '/^$/d' | base64 | tr -d '\n'

    Abilitare esplicitamente i due servizi opzionali impostando COMPOSE_PROFILES=db-studio nelle variabili di Dokploy (oppure usare docker compose --profile db-studio -f docker-compose.prod.yml up -d per un deploy manuale). Senza il profilo, il deploy standard esclude Studio e il proxy anche quando le credenziali non sono configurate.

    Copiare l'output in DB_STUDIO_AUTH_B64 nelle variabili di Dokploy. È un file htpasswd codificato in base64 contenente l'hash bcrypt, non una cifratura: trattarlo come un segreto. Non raddoppiare i caratteri $ e non inserire la password in chiaro. Il proxy accetta nomi utente composti da lettere, numeri, _, . e -, hash bcrypt con costo fra 04 e 16.

  2. Creare il DNS del dominio amministrativo, distinto per ambiente, puntato al server Dokploy.

  3. Nella sezione Domains dello stack Dokploy aggiungere il dominio con servizio db-studio-proxy, porta container 8080, path /, HTTPS e certificato Let's Encrypt. Abilitare il redirect HTTP → HTTPS.

  4. Controllare Preview Compose, quindi eseguire il deploy: il dominio deve instradare soltanto al proxy. Non aggiungere domini a db-studio, né porte in Advanced → Ports. Non servono mapping ports: sul server.

  5. Aprire il dominio HTTPS e inserire username e password nel prompt del browser.

La porta 5555 appartiene a Studio ed è interna; la porta 8080 appartiene al proxy. Il browser usa HTTPS sulla porta 443. Prisma 7 ascolta esclusivamente su loopback: Studio condivide lo spazio di rete del proxy tramite network_mode: service:db-studio-proxy. La porta 5555 non è quindi raggiungibile neppure dagli altri container sulla rete Dokploy. Ogni stack ha il proprio spazio di rete. Il proxy raggiunge dokploy-network, permettendo a Studio di connettersi al database. Nginx verifica che l'Origin, quando presente, corrisponda al dominio HTTPS richiesto, quindi lo traduce nell'origine localhost richiesta da Prisma. Origini esterne ricevono 403.

Credenziali, riavvio e diagnostica

Con il profilo db-studio abilitato, se DB_STUDIO_AUTH_B64 manca o è malformata, il proxy termina senza servire richieste e il deploy può fallire. Lasciare il profilo disabilitato negli ambienti che non usano Studio. Per ruotare la password, ripetere la generazione, aggiornare la variabile in Dokploy e ricreare proxy e Studio insieme tramite redeploy (condividono lo spazio di rete). La vecchia password deve quindi ricevere 401. Il browser può conservare le credenziali Basic in memoria: usare una finestra privata per verificare il nuovo login.

docker compose -f docker-compose.prod.yml logs --tail=100 db-studio db-studio-proxy
docker compose -f docker-compose.prod.yml restart db-studio

Il healthcheck di Studio verifica la risposta HTTP; quello del proxy verifica che una richiesta anonima riceva 401. Non costituiscono una verifica di lettura/scrittura del database. Il proxy raggiunge il backend su loopback anche dopo il riavvio di Studio. Se si ricrea il proxy, ricreare anche Studio nello stesso deploy.

Verifica del deploy

  • Senza credenziali e con password errata, pagina iniziale ed endpoint Studio devono rispondere 401; il redirect HTTP deve portare a HTTPS.
  • Con credenziali corrette, verificare caricamento e consultazione delle tabelle. Provare inserimento, modifica e cancellazione soltanto su un database di prova.
  • Verificare che development mostri il proprio database e production il proprio; le credenziali devono essere diverse. Verificare l'assenza di porte host pubblicate per entrambi i servizi e l'assenza di route dirette verso Studio.
  • Riavviare Studio e verificare che FireFeed rimanga operativo e che il proxy torni a raggiungerlo. Con credenziali mancanti/invalide il proxy deve rimanere inaccessibile.

OMS — modulo sorella

oms.fire-feed.com è l'app sorella di Feed-Manager dedicata all'order management cross-marketplace (port da oms-module di ADIC). Condivide stessa session Keycloak via SSO trasparente — vedi auth-sso per il flow dettagliato.

Setup richiesto (una tantum, prima del primo deploy):

  1. Keycloak client firefeed-oms nel realm firefeed:
    • Redirect URI: https://oms.fire-feed.com/api/auth/callback/keycloak
    • Web origins: https://oms.fire-feed.com
    • Access type: confidential, Standard flow enabled
  2. DNS A record oms.fire-feed.com → stesso IP di app.fire-feed.com
  3. Env vars nel .env Dokploy (è unico per tutto il compose — vedi nota sotto):
    • AUTH_OMS_KEYCLOAK_ID=firefeed-oms
    • AUTH_OMS_KEYCLOAK_SECRET=<secret>
    • OMS_PUBLIC_URL=https://oms.fire-feed.com (link pubblico Feed-Manager → OMS, risolto a runtime)
    • OMS_URL=https://oms.fire-feed.com (per chiamate server-side cross-modulo)

Perché AUTH_OMS_* e non AUTH_KEYCLOAK_* — In Dokploy il file .env è singolo per l'intero compose: web/worker/oms/docs lo condividono. Non possiamo quindi avere AUTH_KEYCLOAK_ID=firefeed-web e AUTH_KEYCLOAK_ID=firefeed-oms al tempo stesso. Il service oms ha un blocco environment: esplicito nel docker-compose.prod.yml che mappa ${AUTH_OMS_KEYCLOAK_ID} → AUTH_KEYCLOAK_ID solo dentro al suo container, ignorando il valore "web" presente nel .env. Web e worker continuano a leggere AUTH_KEYCLOAK_ID=firefeed-web come prima.

Le tabelle config (oms_channels, oms_sync_logs) vivono in public come per Feed-Manager. Le tabelle volume per company (orders, order_items, shipments, returns) vivono in oms_<companyId>, create on-demand da SchemaService.createOmsSchema() al primo accesso dell'utente nella sua company.

Variabili ambiente

Tutte in .env adiacente al compose (popolato da Dokploy UI o copiato da .env.prod.example):

DATABASE_URL=postgresql://...
REDIS_URL=redis://...
AUTH_SECRET=...
AUTH_KEYCLOAK_ID=...
AUTH_KEYCLOAK_SECRET=...
AUTH_KEYCLOAK_ISSUER=...
AUTH_TRUST_HOST=true
RESEND_API_KEY=...
APP_URL=https://app.fire-feed.com
DOCS_URL=https://docs.fire-feed.com

Build

docker compose -f docker-compose.prod.yml build

Quattro immagini multi-stage (Node 20-alpine + pnpm 10.17): firefeed-<ambiente>-web, firefeed-<ambiente>-worker, firefeed-<ambiente>-oms, firefeed-<ambiente>-docs. Il migrator riusa l'immagine worker dello stesso ambiente come init-container (vedi commento dedicato in docker-compose.prod.yml).

Migrazioni Prisma

Automatiche: il compose include un service migrator (init-container) che gira prisma migrate deploy ad ogni docker compose up. Web e worker dichiarano depends_on: migrator: condition: service_completed_successfully, quindi:

  • migration OK → web/worker partono normalmente
  • migration KO → l'intero deploy si blocca (fail-fast, nessuna app che gira contro DB con schema sbagliato)

Log del solo migrator (one-shot, esce a fine job):

docker compose -f docker-compose.prod.yml logs migrator

Lancio manuale (per debug o se vuoi applicare prima del up):

docker compose -f docker-compose.prod.yml run --rm migrator

Gli schemi company_<id> (Feed-Manager project tables) e oms_<id> (OMS volume tables) vengono creati on-demand dal SchemaService rispettivamente al primo project create e al primo accesso OMS della company (fuori dal flusso Prisma migrate).

Logging

docker compose -f docker-compose.prod.yml logs -f web worker docs

Log strutturati JSON dove possibile. Per la pipeline, il database table pipeline_logs è la source of truth (consultabile anche via UI Run Detail).

Reverse proxy

Una sola istanza Traefik/Caddy davanti che route:

  • exp.fire-feed.com → web:3000
  • docs.fire-feed.com → docs:3001

I labels Docker (dokploy.domain=...) abilitano l'autoconfigurazione.

Limiti attuali

Stato Maggio 2026, post chiusura del piano di consolidamento:

  • Storage artifact export: WS2B chiusa — gli artifact vivono su RustFS (S3-compatible) a exp.data.fire-feed.com, manifest persistito in export_artifacts. Volume Docker firefeed-storage resta come buffer local + fallback per export legacy. Retention via cron giornaliero (WS8): tiene gli ultimi EXPORT_ARTIFACT_RETENTION (default 10) per export, droppa il resto da S3 + manifest + filesystem. Vedi ADR 0001.
  • Replica DB: single-instance Postgres managed da Dokploy. Backup affidati alla policy Dokploy. Niente read replica.
  • Replica Redis: single-instance. Persistenza configurata via Dokploy.
  • Worker single-replica: una sola istanza firefeed-worker. Concurrency interna tunable via env (PIPELINE_CONCURRENCY etc, vedi Concurrency). Scaling a N istanze richiede deduplicazione delle responsibility (e.g. cron temp-cleanup attivo solo su instance 0).
  • Multi-region: no — single region, single host. Latency utenti EU/IT, OK; utenti US/APAC vedranno latenza.
  • OpenTelemetry distributed tracing: non attivo. Solo Prometheus metrics su worker (vedi GET /metrics worker, porta 4000 interna).

Per il dettaglio degli env disponibili: Variabili d'ambiente.

Observability

  • Healthcheck: web /api/health + worker /health pingano DB e Redis. Ritornano 503 se uno fallisce → Dokploy/Traefik instradano solo verso instance sane.
  • Metrics Prometheus: worker /metrics espone counter pipeline runs, histogram durata, gauge BullMQ jobs, histogram size artifact. Da scrapeare con Prometheus/vmagent/grafana-agent.
  • Logs strutturati: worker usa pino (Fastify built-in + createJobLogger(pipelineRunId) per job context). docker compose logs -f worker | jq per inspect.
  • Pipeline log persistenti: tabella pipeline_logs con pipelineRunId e step, sempre disponibile via UI Run Detail.

DB recovery

In caso di crash Postgres (could not find redo location), procedura:

  1. Stop dei container che scrivono (web, worker).
  2. Snapshot del volume (tar -cf snapshot.tar /var/lib/postgresql/data).
  3. docker run --rm -v <volume>:/var/lib/postgresql/data postgres:15 gosu postgres pg_resetwal -f /var/lib/postgresql/data
  4. Riavvio container DB, verifica SELECT pg_is_in_recovery().
  5. REINDEX SYSTEM, opzionale VACUUM ANALYZE.
  6. Riavvio app.

Importante: pg_resetwal -f lascia spesso il system catalog incoerente (orfani in pg_attrdef, pg_attribute, indici user-level con heap tid stale). Dopo qualsiasi pg_resetwal pianifica subito: REINDEX (CONCURRENTLY) DATABASE firefeedexp + TRUNCATE pg_statistic + ANALYZE. Se l'errore could not find tuple for attrdef <oid> torna, intervento manuale su pg_depend/pg_class con allow_system_table_mods=on. Documentato nei runbook interni.

In questa pagina