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 scaricabiliOgni 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
-
Generare credenziali diverse per development e production. Sul Mac/server con
htpasswdinstallato, 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-studionelle variabili di Dokploy (oppure usaredocker compose --profile db-studio -f docker-compose.prod.yml up -dper 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_B64nelle variabili di Dokploy. È un filehtpasswdcodificato 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. -
Creare il DNS del dominio amministrativo, distinto per ambiente, puntato al server Dokploy.
-
Nella sezione Domains dello stack Dokploy aggiungere il dominio con servizio
db-studio-proxy, porta container8080, path/, HTTPS e certificato Let's Encrypt. Abilitare il redirect HTTP → HTTPS. -
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 mappingports:sul server. -
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-studioIl 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):
- Keycloak client
firefeed-omsnel realmfirefeed:- Redirect URI:
https://oms.fire-feed.com/api/auth/callback/keycloak - Web origins:
https://oms.fire-feed.com - Access type: confidential, Standard flow enabled
- Redirect URI:
- DNS A record
oms.fire-feed.com→ stesso IP diapp.fire-feed.com - Env vars nel
.envDokploy (è unico per tutto il compose — vedi nota sotto):AUTH_OMS_KEYCLOAK_ID=firefeed-omsAUTH_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 nonAUTH_KEYCLOAK_*— In Dokploy il file.envè singolo per l'intero compose: web/worker/oms/docs lo condividono. Non possiamo quindi avereAUTH_KEYCLOAK_ID=firefeed-webeAUTH_KEYCLOAK_ID=firefeed-omsal tempo stesso. Il serviceomsha un bloccoenvironment:esplicito neldocker-compose.prod.ymlche mappa${AUTH_OMS_KEYCLOAK_ID}→AUTH_KEYCLOAK_IDsolo dentro al suo container, ignorando il valore "web" presente nel.env. Web e worker continuano a leggereAUTH_KEYCLOAK_ID=firefeed-webcome 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.comBuild
docker compose -f docker-compose.prod.yml buildQuattro 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 migratorLancio manuale (per debug o se vuoi applicare prima del up):
docker compose -f docker-compose.prod.yml run --rm migratorGli 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:3000docs.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 inexport_artifacts. Volume Dockerfirefeed-storageresta come buffer local + fallback per export legacy. Retention via cron giornaliero (WS8): tiene gli ultimiEXPORT_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_CONCURRENCYetc, 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 /metricsworker, porta 4000 interna).
Per il dettaglio degli env disponibili: Variabili d'ambiente.
Observability
- Healthcheck:
web /api/health+worker /healthpingano DB e Redis. Ritornano 503 se uno fallisce → Dokploy/Traefik instradano solo verso instance sane. - Metrics Prometheus:
worker /metricsespone 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 | jqper inspect. - Pipeline log persistenti: tabella
pipeline_logsconpipelineRunIdestep, sempre disponibile via UI Run Detail.
DB recovery
In caso di crash Postgres (could not find redo location), procedura:
- Stop dei container che scrivono (
web,worker). - Snapshot del volume (
tar -cf snapshot.tar /var/lib/postgresql/data). docker run --rm -v <volume>:/var/lib/postgresql/data postgres:15 gosu postgres pg_resetwal -f /var/lib/postgresql/data- Riavvio container DB, verifica
SELECT pg_is_in_recovery(). REINDEX SYSTEM, opzionaleVACUUM ANALYZE.- Riavvio app.
Importante:
pg_resetwal -flascia spesso il system catalog incoerente (orfani inpg_attrdef,pg_attribute, indici user-level conheap tidstale). Dopo qualsiasipg_resetwalpianifica subito:REINDEX (CONCURRENTLY) DATABASE firefeedexp+TRUNCATE pg_statistic+ANALYZE. Se l'errorecould not find tuple for attrdef <oid>torna, intervento manuale supg_depend/pg_classconallow_system_table_mods=on. Documentato nei runbook interni.