Vai al contenuto

Installare Quire con Docker Compose

Installa Quire sulla tua infrastruttura con Docker Compose.

Questo è il prodotto completo su un solo host: l’LMS, il suo lavoro in background, i servizi realtime e di modifica collaborativa, e ogni servizio facoltativo dietro un profilo. Il progetto è nella sezione 2 di docs/architecture/23-ops.md.

Altri target: Vercel e Cloudflare Workers eseguono solo il livello web. Gli aggiornamenti sono in upgrade.md, e backup e prova di ripristino in backup-restore.md.

Cosa serve

  • Docker Engine 27 o successivo con plugin Compose 2.30 o successivo.
  • 4 core CPU e 8 GB di memoria per lo stack predefinito; 8 core e 16 GB con --profile full (il solo ClamAV occupa circa 1,5 GB di firme).
  • Un nome DNS per il livello web e un secondo per i contenuti non fidati. Devono essere host diversi: pacchetti SCORM e HTML caricati girano sull’origine dei contenuti così non possono mai leggere i cookie dell’LMS.
  • Per una prova locale, lvh.me e *.localhost risolvono a 127.0.0.1, come usa docker/.env.example. Il servizio proxy proprio dello stack serve entrambi via https con un’autorità di certificazione locale, quindi non si installa nient’altro (vedi “TLS”).
  • Porte 80 e 443 libere sull’host (QUIRE_PROXY_HTTP_PORT e QUIRE_PROXY_HTTPS_PORT le spostano).

Prima esecuzione

QUIRE_APP_ORIGIN=https://learn.example.org \
QUIRE_CONTENT_ORIGIN=https://content.example-content.org \
QUIRE_SETUP_ADMIN_EMAIL=you@example.org \
  docker/scripts/init-env.sh
docker compose -f docker/compose.yaml up -d --build
docker compose -f docker/compose.yaml logs init

docker/scripts/init-env.sh scrive docker/.env da docker/.env.example con ogni segreto generato (password dei database, chiavi di firma e master, coppia di chiavi di avvio dei contenuti) e la chiave di firma del checkpoint di audit in docker/secrets/audit-signing-key.pem, che Compose monta nei worker come segreto. Servono solo sh, awk e openssl, e rifiuta di sovrascrivere un docker/.env esistente. Copia entrambi i file fuori dall’host: senza QUIRE_MASTER_KEY un database ripristinato non può decifrare le sue credenziali archiviate. Per compilare il file a mano, cp docker/.env.example docker/.env; il file spiega come generare ciascun segreto.

Entrambe le origini devono essere https: il servizio dei contenuti rifiuta http in chiaro in produzione, e non devono condividere un dominio registrabile. Il servizio proxy termina TLS per entrambe (vedi “TLS”); init-env.sh rifiuta un’origine http://.

Lo stack parte in ordine fisso, e ciascun passaggio attende quello precedente:

  1. postgres diventa sano. Al primissimo avvio il suo script di init (docker/postgres/init/90-passwords.sh) imposta le password dei quattro ruoli.
  2. migrate applica ogni migrazione e avvia la coda dei job nel database di controllo e in ogni database tenant dedicato, verifica che siano tutti d’accordo, poi esce (docs/ops/upgrade.md). Le migrazioni girano a ogni avvio e sono idempotenti, quindi un aggiornamento è una nuova immagine e un riavvio.
  3. init (apps/web/src/first-run.ts) registra il database applicativo sotto QUIRE_DATABASE_ID e, quando QUIRE_SETUP_ADMIN_EMAIL è impostato, crea la prima organizzazione e il suo amministratore. L’indirizzo di accesso e una password generata sono stampati una volta, in docker compose logs init.
  4. web, content, worker, scheduler, collab e centrifugo partono.
  5. proxy parte una volta che web e content sono sani.

Apri https://demo. seguito dal tuo dominio applicativo (il registro init stampa l’indirizzo di accesso esatto), e accedi. Su un’installazione locale, considera prima attendibile l’autorità di certificazione del proxy (vedi “TLS”). Cambia la password generata in /account/security.

Un processo avviato senza un segreto richiesto rifiuta di avviarsi e nomina l’impostazione mancante nel suo registro. Nulla parte mezzo configurato.

Servizi e profili

Servizio Profilo Cosa fa
postgres sempre Il database (PostgreSQL 18 con pgvector, compilato da docker/postgres.Dockerfile), con WAL archiviato dal primo avvio
migrate, init sempre One-shot: migrazioni, poi prima esecuzione
web sempre L’LMS, su QUIRE_HTTP_PORT (8080)
content sempre L’origine dei contenuti non fidati, su QUIRE_CONTENT_PORT (8081)
worker sempre Job in background: email, report, elaborazione file, webhook
scheduler sempre Job ricorrenti: registra le 64 programmazioni runtime e le affida al worker; un leader alla volta
collab sempre Websocket di modifica collaborativa, su QUIRE_COLLAB_HTTP_PORT (1234)
centrifugo sempre Fan-out realtime, su QUIRE_REALTIME_PORT (8000)
proxy sempre Caddy, la porta TLS sulle porte 80 e 443 (vedi “TLS”)
valkey cache Cache e limiti di frequenza
clamav scan Scansione malware dei caricamenti
gotenberg preview Anteprime da Office a PDF, rendering certificati
imgproxy images Immagini ridimensionate e convertite
transcoder video L’immagine worker con ffmpeg solo LGPL, per le versioni video
seaweedfs storage Object storage compatibile S3 su questo host
otelcol observability Un collector OpenTelemetry
mailpit devmail Cattura tutta la posta in uscita, per provare Quire
backup backup Backup di base one-shot; vedi backup-restore.md
backup-scheduler, backup-offsite backup Un backup di base ogni QUIRE_BACKUP_INTERVAL_HOURS, e copie cifrate fuori host con prova di verifica settimanale
h5p h5p L’immagine strumento H5P LTI 1.3 fornita da te in QUIRE_H5P_IMAGE, su QUIRE_H5P_PORT (8090); vedi “Collegare un provider H5P”

--profile full avvia ogni servizio facoltativo tranne backup e h5p. Avviane uno con docker compose -f docker/compose.yaml --profile scan up -d. Senza un servizio facoltativo Quire funziona comunque e dice cosa manca: senza scanner i caricamenti sono archiviati non scansionati e l’amministratore è avvisato; senza Gotenberg i file offrono il download invece dell’anteprima; senza transcoder i video vengono riprodotti come file originale.

Ogni immagine di terze parti e i suoi obblighi di licenza sono elencati in docker/third-party-containers.yaml.

Collegare un provider H5P

Quire non integra né fornisce un runtime o sidecar H5P (ADR 0019). Se usi H5P, procurati un abbonamento ospitato oppure gestisci separatamente da Quire una tua istanza H5P autonoma. Registra quel provider come strumento esterno LTI 1.3 e aggiungi i suoi contenuti ai corsi come attività strumento. Quire scambia voti e avanzamento di attività/valutazione tramite LTI Assignment and Grade Services (AGS). Se il provider invia anche istruzioni xAPI, configurale separatamente per l’archivio istruzioni xAPI di Quire; lo scambio voti/avanzamento AGS non invia istruzioni xAPI. Le importazioni Moodle segnalano le attività H5P come bisognose di una connessione a uno strumento LTI. Il provider resta responsabile del suo runtime H5P, della creazione, della banca dei contenuti e della cronologia dei tentativi.

Per eseguire una tua istanza autonoma su questo host, imposta QUIRE_H5P_IMAGE sulla sua immagine e avvia il profilo h5p. Compose la pubblica su QUIRE_H5P_PORT (8090) e ne conserva i dati nel volume h5p-data; l’immagine, e gli obblighi che comporta, restano tuoi.

Impostazioni

Ogni processo legge docker/.env. Il modello, docker/.env.example, elenca ciascuna impostazione col suo default. I gruppi:

Indirizzi

Impostazione Significato
QUIRE_APP_ORIGIN L’indirizzo pubblico dell’LMS, ad esempio https://learn.example.com
QUIRE_CONTENT_ORIGIN L’origine dei contenuti, un host diverso
QUIRE_PLATFORM_DOMAINS Domini sotto cui vivono le organizzazioni, separati da virgole
QUIRE_MARKETING_ORIGIN Optional. The marketing site, default https://quirelms.com. The only origin the waitlist form (POST /api/waitlist, POST /waitlist) accepts and redirects to. Comma separated; a www. variant is allowed only if listed
QUIRE_DEPLOY_TARGET compose qui. Vedi le altre guide per vercel e cloudflare
QUIRE_TRUSTED_PROXY_CIDRS Proxy il cui X-Forwarded-For è considerato attendibile

Segreti

Impostazione Significato
QUIRE_SECRET_KEY Firma sessioni e token. 64 caratteri esadecimali
QUIRE_MASTER_KEY Avvolge le credenziali archiviate come segreti SSO e webhook. 32 byte, base64. Il livello web e il worker necessitano dello stesso valore. Rotazione: key-rotation.md
QUIRE_MASTER_KEY_VERSION L’etichetta di versione della chiave master, v1 se non impostata. Alzala quando ruoti
QUIRE_MASTER_KEY_RETIRED Chiavi master precedenti ancora necessarie per leggere ciò che hanno sigillato, come v1=<base64>. Rimuovile dopo che una rotazione termina senza irrisolti
QUIRE_COLLAB_SIGNING_KEY Condivisa da web e collab per firmare i token di modifica
QUIRE_BACKUP_SIGNING_KEY Firma i backup dei corsi (facoltativo)

Conserva una copia di QUIRE_MASTER_KEY altrove rispetto a questo host. Un database ripristinato senza di essa non può decifrare le credenziali che contiene.

Database

Impostazione Significato
POSTGRES_PASSWORD Il superuser, usato dal contenitore e dai backup
QUIRE_DB_APP_PASSWORD, QUIRE_DB_MIGRATOR_PASSWORD, QUIRE_DB_REPORT_PASSWORD, QUIRE_DB_AUDIT_PASSWORD Password dei ruoli, impostate al primo avvio
DATABASE_URL Il ruolo applicativo. La sicurezza a livello di riga si applica a ogni query che esegue
DATABASE_MIGRATOR_URL, QUIRE_MIGRATION_URL Il ruolo migratore, per migrate e init
QUIRE_SUPERUSER_URL Usato solo dalla prima esecuzione
QUIRE_REPORT_DATABASE_URL Il ruolo report di sola lettura, per report e generatore di report
QUIRE_AUDIT_DATABASE_URL Il ruolo audit, per la console di audit ed esportazione SIEM
QUIRE_DATABASE_ID Un UUID qualsiasi, fisso per la vita dell’installazione

Le password dei ruoli sono applicate solo quando il volume del database viene creato per la prima volta. Per cambiarne una in seguito, usa ALTER ROLE e poi aggiorna l’URL corrispondente.

QUIRE_REPORT_DATABASE_URL è usato per il database fisico configurato da DATABASE_URL. Per ogni altro database fisico registrato, imposta il suo URL di connessione quire_report negli ambienti web e worker, poi inserisci il nome della variabile nel campo Variabile d’ambiente di reporting di quel database come env:NAME. Il riferimento deve puntare allo stesso database della sua connessione app, idealmente la sua replica di lettura. Ogni superficie di report segue il tenant alla connessione report del proprio database: il generatore di report e i report salvati, le consegne programmate, le esportazioni di report, le analisi, il registro di audit, le risorse audit REST e la ricerca audit dell’assistente. Nessuna di esse prende mai in prestito l’URL report di un altro database. Quando un database non ha una connessione report, i report ordinari girano sulla connessione applicativa del database stesso, mentre le analisi e ogni lettura audit rifiutano e lo dicono, perché il ruolo applicativo non può leggere la traccia di audit.

Driver

Impostazione Questa release Note
QUIRE_STORAGE_DRIVER local (predefinito), s3 oppure azure local conserva i file nel volume files. s3 copre AWS S3, R2, interoperabilità GCS e altri store compatibili S3, con caricamenti multipart ripristinabili
QUIRE_REALTIME_DRIVER inprocess (predefinito), sse, centrifugo oppure durable_objects inprocess va bene per un solo contenitore web; usa centrifugo oppure sse quando ce ne sono diversi
QUIRE_CACHE_DRIVER memory (predefinito), postgres oppure valkey memory è per processo; usa valkey oppure postgres così i limiti di frequenza valgono tra contenitori
QUIRE_VIDEO_DRIVER ffmpeg (predefinito) oppure progressive_mp4 Oppure un provider ospitato: Cloudflare Stream, Mux o Bunny, con le loro chiavi
QUIRE_IMAGE_DRIVER noop (predefinito), imgproxy oppure cloudflare noop serve ogni immagine alla sua dimensione originale. imgproxy richiede il profilo images e le impostazioni sotto; cloudflare usa Cloudflare Images
QUIRE_MEETING_PROVIDER bbb, zoom, teams, meet, jitsi oppure in_process Il default di piattaforma per le sessioni live. Non impostato, le sessioni live dicono di non essere configurate, finché un’organizzazione non collega il proprio account in Integrazioni, Provider sessioni live. L’account proprio di un’organizzazione vince sempre su questo valore. Le impostazioni proprie di ciascun provider (BBB_URL e BBB_SECRET, le variabili ZOOM_*, TEAMS_*, GOOGLE_MEET_* e JITSI_*) sono lette solo per il provider qui nominato
QUIRE_MEETING_REGIONS Un elenco di eu, uk, us separato da virgole Dove il provider default di piattaforma elabora le riunioni. Non impostato, non viene confrontato con un’organizzazione vincolata a un’area geografica, come prima. L’account proprio di un’organizzazione dichiara le sue aree nella sua pagina

Un valore driver non incluso in questa release viene rifiutato all’avvio del livello web, col nome dell’impostazione, anziché sostituito in silenzio dal default.

Immagini

Le pagine chiedono immagini in quattro dimensioni fisse tramite /api/files/{id}/image/{size}, che verifica lo stesso accesso del file stesso e poi reindirizza al servizio immagini. Ciascuna organizzazione può chiedere QUIRE_IMAGE_SPECS_PER_HOUR (predefinito 2000) nuove coppie immagine e dimensione all’ora; le dimensioni già prodotte in quell’ora non contano. Usa valkey oppure postgres per QUIRE_CACHE_DRIVER con più di un contenitore web, così il limite vale tra essi.

Impostazione Driver Note
IMGPROXY_URL imgproxy L’indirizzo a cui i browser raggiungono imgproxy, ad esempio https://images.example.org. Il profilo images lo pubblica su QUIRE_IMAGES_PORT (8082)
IMGPROXY_KEY, IMGPROXY_SALT imgproxy Stringhe esadecimali, gli stessi valori con cui imgproxy è avviato. Genera ciascuna con openssl rand -hex 32. Quire firma ogni indirizzo immagine con esse, così imgproxy non renderizza nulla che Quire non abbia chiesto
QUIRE_IMAGE_SOURCE_ORIGIN imgproxy con storage locale Da dove imgproxy preleva gli originali. Compose imposta http://web:3000. Con storage s3 oppure azure imgproxy preleva dal bucket e questo non è usato
CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_IMAGES_TOKEN, CLOUDFLARE_IMAGES_ACCOUNT_HASH cloudflare Un token API con permesso Images edit, e l’hash account da Images, Risorse sviluppatore. Attiva le varianti flessibili per l’account
CLOUDFLARE_IMAGES_SIGNING_KEY cloudflare Facoltativo. Se impostato, le immagini sono private e ogni indirizzo è firmato e scade. Senza di esso, le immagini sono pubbliche ad indirizzi derivati da QUIRE_SECRET_KEY che nessuno può indovinare

Cloudflare Images conserva una propria copia di ciascun originale servito. Quando un file viene eliminato, il worker elimina quella copia prima dell’originale.

Coda

I job in background usano pg-boss nello stesso database Postgres, quindi non c’è un servizio di coda da eseguire e nulla da configurare. I job sono accodati nella stessa transazione della modifica che li ha causati, così un crash non può perderne uno né inviarne uno due volte. QUIRE_QUEUE_DRIVER qui è pgboss, il suo default; vercel e cloudflare spostano solo le consegne leggere di notifiche e webhook alla coda propria della piattaforma, e le guide Vercel e Cloudflare le descrivono e spiegano come i loro livelli web accodano.

Email

Impostane una tra:

  • QUIRE_EMAIL_PROVIDER_CONFIG: un oggetto JSON che nomina un provider HTTP e le sue credenziali, ad esempio {"provider":"postmark","token":"..."}. Postmark, Amazon SES, Mailgun, SendGrid e Resend sono supportati.
  • QUIRE_SMTP_URL: smtp://user:password@host:587. Solo questo target; i target serverless bloccano SMTP.

QUIRE_MAIL_FROM è il mittente. Per provare Quire, avvia il profilo devmail, imposta QUIRE_SMTP_URL=smtp://mailpit:1025, e leggi la posta in http://localhost:8025.

Servizi facoltativi

Impostazione Con profilo
CLAMAV_URL=tcp://clamav:3310 scan
GOTENBERG_URL=http://gotenberg:3000 preview
IMGPROXY_KEY, IMGPROXY_SALT images
VALKEY_URL=redis://valkey:6379 cache
QUIRE_OPENSEARCH_URL oppure QUIRE_MEILISEARCH_URL Ricerca esterna; altrimenti full text Postgres
QUIRE_BREACH_CHECK_PROVIDER=off, QUIRE_BREACH_CHECK_URL Controllo violazioni password. Attivo per impostazione predefinita contro api.pwnedpasswords.com (viene inviato solo un prefisso hash di cinque caratteri); off lo disabilita, e l’URL punta a una range API ospitata da te

Osservabilità

OTEL_EXPORTER_OTLP_ENDPOINT nomina il collector a cui ogni processo invia tracce e metriche; con il profilo observability è http://otelcol:4318, e docker/otel-collector.yaml è dove aggiungi l’esporter per il tuo backend. I processi web, worker, scheduler, content e collab esportano span su OTLP/HTTP (richieste web, transazioni database tenant, job worker e chiamate in uscita) quando è impostato, e metriche allo stesso endpoint ogni minuto (OTEL_METRICS_EXPORTER=none le disattiva). OTEL_TRACES_SAMPLER_ARG imposta la quota di tracce conservate. I log vanno sullo standard output a LOG_LEVEL, e Compose li ruota. Le tracce non contengono mai dati personali.

Egress regionale (residenza dati UE)

QUIRE_REGION=eu indica che lo stack serve organizzazioni dell’Unione europea. Il worker trattiene allora ogni richiesta in uscita effettuata per un’organizzazione vincolata alla UE a un allowlist (sezione 8.1 di 21-compliance.md). L’allowlist è gli host dichiarati per l’area geografica dai servizi configurati (l’endpoint storage, il provider email, un provider video ospitato, i target storage propri dell’organizzazione, provider IA e account email), gli host di ogni servizio sotto deroga attiva, e gli host elencati da te in QUIRE_EGRESS_ALLOW_HOSTS. Una richiesta a qualsiasi altro host pubblico viene rifiutata prima dell’invio, il rifiuto è scritto nella traccia di audit dell’organizzazione come privacy/egress_refused, ed è elencato in Conformità, Residenza dati.

Impostazione Valori Effetto
QUIRE_EGRESS_ALLOW_HOSTS Un elenco di hostname separato da virgole, oppure *.example.org per ogni sottodominio Host extra che un’organizzazione UE può raggiungere. Endpoint webhook, xAPI e SIEM, feed blog e host Amazon SES appartengono qui, perché sono scelta propria di un’organizzazione e nessun servizio li dichiara. Loopback, indirizzi privati e nomi a singola etichetta come web o clamav sono la tua rete e non vengono mai verificati

Le organizzazioni UK e US non sono vincolate a un elenco host; conservano i controlli di area geografica dei servizi. Imposta l’elenco sul worker; la pagina admin lo legge sul livello web per mostrare l’allowlist, quindi mettilo in docker/.env, che ogni servizio legge.

Il controllo applicativo dà un errore chiaro e una voce di audit, e non è la garanzia: il codice può sbagliare. La garanzia è la rete. Compose non la applica per te. Per uno stack regionale, metti i servizi worker e web su una rete internal: true la cui unica via d’uscita sia un proxy di egress (ad esempio un contenitore Squid o tinyproxy) che consente gli stessi host di QUIRE_EGRESS_ALLOW_HOSTS più gli host dei tuoi servizi configurati, e imposta HTTPS_PROXY per quei servizi. La pagina di residenza elenca gli host esatti consentiti dall’applicazione, così le due liste si possono confrontare.

Stato

Endpoint Significato
/healthz Liveness: il processo risponde. I controlli di salute Compose usano questo
/readyz Readiness: dipendenze raggiungibili, e ciascun servizio facoltativo segnalato come configurato o no. Punta qui il tuo bilanciatore

docker compose -f docker/compose.yaml ps mostra lo stato di ciascun servizio.

TLS

Il servizio proxy (Caddy, Apache-2.0, docker/caddy/Caddyfile) fa parte dello stack predefinito. Risponde sulle porte 80 e 443 e instrada:

Host o percorso Va a
QUIRE_PROXY_CONTENT_HOST content
QUIRE_PROXY_APP_HOST, ogni sottodominio tenant e dominio personalizzato web
/_collab/ su quegli host collab (websocket, QUIRE_COLLAB_URL)
/_realtime/connection/ su quegli host il websocket client di centrifugo; la sua API server non è mai esposta
/_images/ su quegli host imgproxy, con il profilo images (IMGPROXY_URL)

init-env.sh deriva QUIRE_PROXY_APP_HOST, QUIRE_PROXY_CONTENT_HOST, QUIRE_PROXY_HTTPS_PORT, QUIRE_COLLAB_URL e IMGPROXY_URL dalle due origini, così non possono divergere. Modificali insieme se cambi un’origine a mano.

I certificati seguono QUIRE_PROXY_TLS:

  • internal (il default): l’autorità di certificazione propria di Caddy, per localhost, *.localhost e lvh.me. Considera attendibile la sua radice una volta, poi naviga:

    docker compose -f docker/compose.yaml cp \
      proxy:/data/caddy/pki/authorities/local/root.crt ./quire-local-ca.crt

    Aggiungi quire-local-ca.crt al trust store di sistema o del browser. curl lo accetta con --cacert.

  • Un indirizzo e-mail: certificati ACME automatici (Let’s Encrypt, poi ZeroSSL) per hostname reali. Il DNS di entrambe le origini e di ogni host tenant deve puntare qui, e le porte 80 e 443 devono essere raggiungibili da internet.

Gli host tenant sono emessi su richiesta, alla prima visita, e solo quando web conferma che il nome appartiene a questa installazione (/tls-allowed, chiesto sulla rete Compose). Non servono certificati wildcard né plugin DNS, e un estraneo che punta un nome all’host non può fargli richiedere certificati. Certificati e autorità locale vivono nel volume caddy-data; includilo nel backup col resto se usi internal.

Web considera X-Forwarded-For solo dal proxy: il proxy ha un indirizzo fisso (QUIRE_PROXY_ADDRESS, default 172.29.64.10) su una sottorete fissa (QUIRE_COMPOSE_SUBNET), e QUIRE_TRUSTED_PROXY_CIDRS nomina quell’indirizzo. Se la sottorete collide con una rete sull’host, cambia entrambi ed esegui docker compose down prima di up.

Dietro il proprio reverse proxy

Per usare un bilanciatore o proxy già in uso, lascia fuori proxy (docker compose up -d --scale proxy=0) e termina TLS davanti a web (8080), content (8081), collab (1234, websocket) e centrifugo (8000, websocket). Imposta gli indirizzi pubblici in QUIRE_APP_ORIGIN, QUIRE_CONTENT_ORIGIN e QUIRE_COLLAB_URL (wss://), e l’intervallo di indirizzi del tuo proxy in QUIRE_TRUSTED_PROXY_CIDRS.

Risoluzione dei problemi

  • init esce con “QUIRE_DATABASE_ID is not a UUID”: impostalo con uuidgen.
  • web si riavvia con “did not start on compose”: il registro elenca ciascuna impostazione che non può onorare e cosa usare invece.
  • Cambiare una password di ruolo in .env dopo il primo avvio non fa nulla: lo script di init gira una volta sola. Usa ALTER ROLE.
  • I caricamenti falliscono con errore di scansione mentre CLAMAV_URL è impostato: ClamAV scarica le sue firme al primo avvio, il che richiede qualche minuto.
Navigazione

Digita per cercare…

↑↓ per spostarti↵ per selezionareEsc per chiudere