Puoi ospitare più installazioni autonome di Discourse su un singolo server (container separati / porte separate / app.yml separati) senza utilizzare la funzione “multisito” di Discourse.
È più manuale rispetto al multisito, ma mantiene le istanze isolate e rende più semplice migrare un singolo sito su un server dedicato in seguito.
Un modello pratico è:
• Postgres esterno (istanza singola)
• Redis esterno (istanza singola)
• più container web di Discourse
• un nodo Sidekiq
• reverse proxy con controlli di integrità (health checks)
Questo evita completamente il multisito, consentendo comunque risparmi sui costi per configurazioni a basso traffico.
RUNBOOK PER L’ESECUZIONE MULTI-CONTAINER DI DISCOURSE
Postgres esterno + Redis + HAProxy + app1 / app2
- PACCHETTI HOST
| Passaggio | Comando |
|---|---|
| Aggiorna il sistema | apt-get update |
| Installa strumenti di base | apt-get install -y ca-certificates curl gnupg lsb-release |
| Installa HAProxy + certbot + socat | apt-get install -y haproxy certbot socat |
- RETE DOCKER (OBBLIGATORIA)
È necessaria una rete Docker definita dall’utente in modo che i container possano risolversi per nome.
| Passaggio | Comando |
|---|---|
| Crea la rete | docker network create discourse-net |
| Verifica | docker network ls | grep discourse-net |
Questo permette:
• DISCOURSE_DB_HOST=pg
• DISCOURSE_REDIS_HOST=redis
di funzionare correttamente.
- SEGRETI
| Scopo | Comando |
|---|---|
| Superutente Postgres | export PG_SUPERPASS='REPLACE_ME_super_strong' |
| Password DB Discourse | export DISCOURSE_DBPASS='REPLACE_ME_discordb_strong' |
| Password Redis | export REDIS_PASS='REPLACE_ME_redis_strong' |
| Chiave segreta base | export SECRET_KEY_BASE="$(openssl rand -hex 64)" |
- CONTAINER POSTGRES
| Passaggio | Comando |
|---|---|
| Crea directory | mkdir -p /var/discourse/external/postgres |
| Esegui container | docker run -d --name pg --restart=always --network=discourse-net -e POSTGRES_PASSWORD="$PG_SUPERPASS" -v /var/discourse/external/postgres:/var/lib/postgresql/data postgres:15 |
| Verifica | docker ps | grep pg |
- CREA DATABASE
| Passaggio | Comando |
|---|---|
| Crea ruolo | docker exec -it pg psql -U postgres -c "CREATE ROLE discourse LOGIN PASSWORD '$DISCOURSE_DBPASS';" |
| Crea DB | docker exec -it pg psql -U postgres -c "CREATE DATABASE discourse OWNER discourse ENCODING 'UTF8' TEMPLATE template0;" |
| Ricerca testuale | docker exec -it pg psql -U postgres -d discourse -c "ALTER DATABASE discourse SET default_text_search_config = 'pg_catalog.english';" |
| Test accesso | docker exec -it pg psql -U discourse -d discourse -c "select 1;" |
- ESTENSIONE PGVECTOR
Necessaria per le versioni moderne di Discourse.
| Passaggio | Comando |
|---|---|
| Installa | docker exec -it pg bash -lc 'apt-get update && apt-get install -y postgresql-15-pgvector && rm -rf /var/lib/apt/lists/*' |
| Crea estensione | docker exec -it pg psql -U postgres -d discourse -c "CREATE EXTENSION IF NOT EXISTS vector;" |
| Verifica | docker exec -it pg psql -U postgres -d discourse -c "SELECT extname FROM pg_extension WHERE extname='vector';" |
- CONTAINER REDIS
| Passaggio | Comando |
|---|---|
| Crea directory | mkdir -p /var/discourse/external/redis |
Modello di configurazione Redis:
requirepass REPLACE_ME_REDIS
appendonly yes
save 900 1
save 300 10
save 60 10000
| Passaggio | Comando |
|---|---|
| Scrivi configurazione | tee /var/discourse/external/redis/redis.conf >/dev/null <<EOF |
| Inserisci password | sed -i "s/REPLACE_ME_REDIS/$REDIS_PASS/" /var/discourse/external/redis/redis.conf |
| Esegui Redis | docker run -d --name redis --restart=always --network=discourse-net -v /var/discourse/external/redis:/data -v /var/discourse/external/redis/redis.conf:/usr/local/etc/redis/redis.conf redis:7-alpine redis-server /usr/local/etc/redis/redis.conf |
| Test autenticazione | docker exec -it redis redis-cli -a "$REDIS_PASS" ping |
- STRUTTURA DELLE DIRECTORY DI DISCOURSE
| Passaggio | Comando |
|---|---|
| Crea directory base | mkdir -p /var/discourse |
| Entra | cd /var/discourse |
| Clona repository | git clone https://github.com/discourse/discourse_docker.git |
| Directory container | mkdir -p /var/discourse/containers |
| Log condivisi | mkdir -p /var/discourse/shared/web-only/log/var-log |
| Collega container | ln -sfn /var/discourse/containers /var/discourse/discourse_docker/containers |
| Collega launcher | ln -sfn /var/discourse/discourse_docker/launcher /var/discourse/launcher |
- CONTAINER APPLICAZIONE
app1.yml
• web + sidekiq
• porta 8001
docker_args: "--network=discourse-net"
expose:
- "8001:80"
app2.yml
• solo web
• porta 8002
• sidekiq disabilitato
docker_args: "--network=discourse-net"
expose:
- "8002:80"
run:
- exec: bash -lc 'mkdir -p /etc/service/sidekiq && touch /etc/service/sidekiq/down'
- BOOTSTRAP
| Passaggio | Comando |
|---|---|
| Entra | cd /var/discourse/discourse_docker |
| Bootstrap app1 | ./launcher bootstrap app1 |
| Avvia app1 | ./launcher start app1 |
| Bootstrap app2 | ./launcher bootstrap app2 |
| Avvia app2 | ./launcher start app2 |
- CONTROLLI DI INTEGRITÀ (HEALTH CHECKS)
| Passaggio | Comando |
|---|---|
| app1 | curl -sSf http://127.0.0.1:8001/srv/status |
| app2 | curl -sSf http://127.0.0.1:8002/srv/status |
| sidekiq app1 | docker exec -it app1 pgrep -fa sidekiq |
| sidekiq app2 | `docker exec -it app2 pgrep -fa sidekiq |
- CERTIFICATO TLS
| Passaggio | Comando |
|---|---|
| Ferma proxy | systemctl stop haproxy |
| Emetti certificato | certbot certonly --standalone -d example.com --agree-tos -m you@example.com --non-interactive |
| Avvia proxy | systemctl start haproxy |
- LOGICA HAPROXY
frontend fe_discourse
bind :80
bind :443 ssl crt /etc/letsencrypt/live/example.com/haproxy.pem
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
redirect scheme https code 301 if !{ ssl_fc }
use_backend be_discourse if { nbsrv(be_discourse) gt 0 }
default_backend be_maint
backend be_discourse
balance roundrobin
option httpchk GET /srv/status
server app1 127.0.0.1:8001 check
server app2 127.0.0.1:8002 check
backend be_maint
http-request return status 503 content-type text/html string "<h1>Maintenance</h1>"
- RICOSTRUZIONI SENZA TEMPO DI INATTIVITÀ (ZERO-DOWNTIME)
| Passaggio | Comando |
|---|---|
| Disabilita app1 | echo "disable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock |
| Ricrea app1 | ./launcher rebuild app1 |
| Abilita app1 | echo "enable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock |
| Passaggio | Comando |
|---|---|
| Disabilita app2 | echo "disable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock |
| Ricrea app2 | ./launcher rebuild app2 |
| Abilita app2 | echo "enable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock |
FINE
Rete Docker richiesta
Postgres e Redis esterni
pgvector installato
Sidekiq isolato su app1
Controlli di integrità HAProxy abilitati
Fallback per manutenzione attivo
Ricostruzioni rotative supportate
Migrare un sito su un server dedicato in seguito
Un vantaggio dell’eseguire installazioni completamente autonome di Discourse (invece del multisito) è che la migrazione è semplice e a basso rischio.
Ogni istanza di Discourse ha già:
• il proprio container
• i propri upload
• il proprio database
• il proprio utilizzo di Redis
• il proprio app.yml
Non è necessario alcun disaccoppiamento del multisito.
Passaggi generali per la migrazione
- Provisiona un nuovo VPS
Installa Docker e Discourse normalmente sul nuovo server.
Non configurare il multisito.
- Crea un backup completo
Dal sito di origine:
Admin → Backups → Crea Backup
Scarica il file di backup.
Questo include:
• database
• upload
• utenti
• impostazioni
• temi
- Ripristina sul nuovo server
Sul nuovo server:
• completa la configurazione iniziale
• accedi come amministratore
• carica il backup
• ripristina
Discourse gestisce automaticamente la compatibilità dello schema.
- Cambio DNS
Aggiorna il record A del dominio per puntare al nuovo IP del server.
Una volta propagato il DNS, gli utenti verranno spostati in modo trasparente.
- Smantella il vecchio container
Sul server originale:
• ferma il vecchio container
• rimuovilo quando sei sicuro
Le altre installazioni di Discourse sullo stesso host non sono influenzate.
Perché è più semplice del multisito
Nei setup multisito, la migrazione richiede spesso:
• separazione dei database
• estrazione dei dati specifici del sito
• aggiustamento di multisite.yml
• riprogettazione di Sidekiq
• riconfigurazione di upload ed email
Con installazioni autonome, nulla di tutto ciò è necessario.
Ogni sito è già indipendente.
Riepilogo
Questo approccio sacrifica un po’ di complessità operativa all’inizio
per una separazione molto semplice in seguito.
Funziona particolarmente bene durante le sperimentazioni
o la costruzione di comunità nelle fasi iniziali.
Quando questo approccio probabilmente non è adatto
Questa configurazione di solito non è una buona idea se:
• i siti si aspettano traffico moderato o elevato fin dall’inizio
• fai molto affidamento sul supporto ufficiale di Discourse
• non ti senti a tuo agio nel debug di Docker, reti o reverse proxy
• i requisiti di uptime sono rigorosi o critici per il business
• più siti sono strettamente accoppiati a livello operativo
• prevedi frequenti sperimentazioni con plugin su tutte le istanze
In questi casi, o:
• un setup multisito supportato
o
• un’installazione di Discourse per server
porterà generalmente a meno sorprese operative.
Nota importante
Questo approccio aumenta la flessibilità dell’infrastruttura,
ma aumenta anche la responsabilità dell’amministratore.
Funziona meglio quando la persona che lo gestisce è a proprio agio nel gestire l’intera stack
e considera eventuali guasti occasionali come parte del processo di apprendimento.
Se stabilità e supporto sono gli obiettivi principali,
un configurazione supportata è quasi sempre la scelta migliore.