Você pode hospedar várias instalações independentes do Discourse em um único servidor (containers separados / portas separadas / app.yml separados), sem usar o recurso “multisite” do Discourse.
É mais manual do que o multisite, mas mantém as instâncias isoladas e facilita a migração de um site individual para seu próprio servidor mais tarde.
Um padrão prático é:
• PostgreSQL externo (instância única)
• Redis externo (instância única)
• múltiplos containers web do Discourse
• um nó Sidekiq
• proxy reverso com verificações de saúde (health checks)
Isso evita completamente o multisite, ao mesmo tempo que permite economia de custos em configurações de baixo tráfego.
ROTEIRO DE EXECUÇÃO MULTI-CONTAINER DO DISCOURSE
PostgreSQL Externo + Redis + HAProxy + app1 / app2
- PACOTES DO HOST
| Etapa | Comando |
|---|---|
| Atualizar sistema | apt-get update |
| Instalar ferramentas básicas | apt-get install -y ca-certificates curl gnupg lsb-release |
| Instalar HAProxy + certbot + socat | apt-get install -y haproxy certbot socat |
- REDE DOCKER (OBRIGATÓRIA)
Uma rede Docker definida pelo usuário é necessária para que os containers possam se resolver por nome.
| Etapa | Comando |
|---|---|
| Criar rede | docker network create discourse-net |
| Verificar | docker network ls | grep discourse-net |
Isso permite que:
• DISCOURSE_DB_HOST=pg
• DISCOURSE_REDIS_HOST=redis
funcionem corretamente.
- SECRETS
| Finalidade | Comando |
|---|---|
| Superusuário PostgreSQL | export PG_SUPERPASS='REPLACE_ME_super_strong' |
| Senha do banco Discourse | export DISCOURSE_DBPASS='REPLACE_ME_discordb_strong' |
| Senha do Redis | export REDIS_PASS='REPLACE_ME_redis_strong' |
| Chave secreta base | export SECRET_KEY_BASE="$(openssl rand -hex 64)" |
- CONTAINER POSTGRES
| Etapa | Comando |
|---|---|
| Criar diretório | mkdir -p /var/discourse/external/postgres |
| Executar 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 |
| Verificar | docker ps | grep pg |
- CRIAR BANCO DE DADOS
| Etapa | Comando |
|---|---|
| Criar role | docker exec -it pg psql -U postgres -c "CREATE ROLE discourse LOGIN PASSWORD '$DISCOURSE_DBPASS';" |
| Criar DB | docker exec -it pg psql -U postgres -c "CREATE DATABASE discourse OWNER discourse ENCODING 'UTF8' TEMPLATE template0;" |
| Pesquisa de texto | docker exec -it pg psql -U postgres -d discourse -c "ALTER DATABASE discourse SET default_text_search_config = 'pg_catalog.english';" |
| Testar login | docker exec -it pg psql -U discourse -d discourse -c "select 1;" |
- EXTENSÃO PGVECTOR
Necessária para versões modernas do Discourse.
| Etapa | Comando |
|---|---|
| Instalar | docker exec -it pg bash -lc 'apt-get update && apt-get install -y postgresql-15-pgvector && rm -rf /var/lib/apt/lists/*' |
| Criar extensão | docker exec -it pg psql -U postgres -d discourse -c "CREATE EXTENSION IF NOT EXISTS vector;" |
| Verificar | docker exec -it pg psql -U postgres -d discourse -c "SELECT extname FROM pg_extension WHERE extname='vector';" |
- CONTAINER REDIS
| Etapa | Comando |
|---|---|
| Criar diretório | mkdir -p /var/discourse/external/redis |
Template de configuração do Redis:
requirepass REPLACE_ME_REDIS
appendonly yes
save 900 1
save 300 10
save 60 10000
| Etapa | Comando |
|---|---|
| Escrever config | tee /var/discourse/external/redis/redis.conf >/dev/null <<EOF |
| Inserir senha | sed -i "s/REPLACE_ME_REDIS/$REDIS_PASS/" /var/discourse/external/redis/redis.conf |
| Executar 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 |
| Testar autenticação | docker exec -it redis redis-cli -a "$REDIS_PASS" ping |
- ESTRUTURA DE DIRETÓRIOS DO DISCOURSE
| Etapa | Comando |
|---|---|
| Criar diretório base | mkdir -p /var/discourse |
| Entrar | cd /var/discourse |
| Clonar repositório | git clone https://github.com/discourse/discourse_docker.git |
| Diretório containers | mkdir -p /var/discourse/containers |
| Logs compartilhados | mkdir -p /var/discourse/shared/web-only/log/var-log |
| Linkar containers | ln -sfn /var/discourse/containers /var/discourse/discourse_docker/containers |
| Linkar launcher | ln -sfn /var/discourse/discourse_docker/launcher /var/discourse/launcher |
- CONTAINERS DE APLICAÇÃO
app1.yml
• web + sidekiq
• porta 8001
docker_args: "--network=discourse-net"
expose:
- "8001:80"
app2.yml
• apenas web
• porta 8002
• sidekiq desabilitado
docker_args: "--network=discourse-net"
expose:
- "8002:80"
run:
- exec: bash -lc 'mkdir -p /etc/service/sidekiq && touch /etc/service/sidekiq/down'
- BOOTSTRAP
| Etapa | Comando |
|---|---|
| Entrar | cd /var/discourse/discourse_docker |
| Bootstrap app1 | ./launcher bootstrap app1 |
| Iniciar app1 | ./launcher start app1 |
| Bootstrap app2 | ./launcher bootstrap app2 |
| Iniciar app2 | ./launcher start app2 |
- VERIFICAÇÕES DE SAÚDE (HEALTH CHECKS)
| Etapa | 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 |
- CERTIFICADO TLS
| Etapa | Comando |
|---|---|
| Parar proxy | systemctl stop haproxy |
| Emitir certificado | certbot certonly --standalone -d example.com --agree-tos -m you@example.com --non-interactive |
| Iniciar proxy | systemctl start haproxy |
- LÓGICA DO 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>"
- RECONSTRUÇÕES SEM TEMPO DE INATIVIDADE (ZERO-DOWNTIME)
| Etapa | Comando |
|---|---|
| Desabilitar app1 | echo "disable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock |
| Reconstruir app1 | ./launcher rebuild app1 |
| Habilitar app1 | echo "enable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock |
| Etapa | Comando |
|---|---|
| Desabilitar app2 | echo "disable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock |
| Reconstruir app2 | ./launcher rebuild app2 |
| Habilitar app2 | echo "enable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock |
FIM
Rede Docker necessária
PostgreSQL e Redis externos
pgvector instalado
Sidekiq isolado no app1
Verificações de saúde do HAProxy habilitadas
Fallback de manutenção ativo
Reconstruções em rotação suportadas
Migrando um site para seu próprio servidor mais tarde
Uma vantagem de executar instalações totalmente independentes do Discourse (em vez de multisite) é que a migração é direta e de baixo risco.
Cada instância do Discourse já possui:
• seu próprio container
• seus próprios uploads
• seu próprio banco de dados
• seu próprio uso do Redis
• seu próprio app.yml
Não é necessário desvendar o multisite.
Passos gerais de migração
- Provisionar um novo VPS
Instale o Docker e o Discourse normalmente no novo servidor.
Não configure multisite.
- Criar um backup completo
No site de origem:
Admin → Backups → Criar Backup
Baixe o arquivo de backup.
Isso inclui:
• banco de dados
• uploads
• usuários
• configurações
• temas
- Restaurar no novo servidor
No novo servidor:
• conclua a configuração inicial
• faça login como admin
• faça upload do backup
• restaure
O Discourse lida automaticamente com a compatibilidade do esquema.
- Migração de DNS
Atualize o registro A do domínio para apontar para o IP do novo servidor.
Assim que o DNS propagar, os usuários serão movidos de forma transparente.
- Descomissionar o container antigo
No servidor original:
• pare o container antigo
• remova-o quando tiver certeza
Outras instalações do Discourse no mesmo host não são afetadas.
Por que isso é mais simples que o multisite
Em configurações multisite, a migração frequentemente requer:
• separação de bancos de dados
• extração de dados específicos do site
• ajuste do multisite.yml
• reestruturação do Sidekiq
• reconfiguração de uploads e e-mail
Com instalações independentes, nada disso é necessário.
Cada site já é independente.
Resumo
Esta abordagem troca um pouco de complexidade operacional no início
por uma separação muito simples mais tarde.
Funciona particularmente bem durante experimentação
ou construção de comunidades em estágio inicial.
Quando esta abordagem provavelmente não é adequada
Esta configuração geralmente não é uma boa ideia se:
• os sites esperam tráfego moderado ou alto desde o início
• você depende fortemente do suporte oficial do Discourse
• você não se sente confortável depurando Docker, redes ou proxies reversos
• os requisitos de uptime são rigorosos ou críticos para o negócio
• múltiplos sites estão operacionalmente fortemente acoplados
• você espera experimentação frequente de plugins em todas as instâncias
Nesses casos, ou:
• uma configuração multisite suportada
ou
• uma instalação do Discourse por servidor
geralmente resultará em menos surpresas operacionais.
Nota importante
Esta abordagem aumenta a flexibilidade da infraestrutura,
mas também aumenta a responsabilidade do administrador.
Funciona melhor quando a pessoa que a executa está confortável em assumir toda a pilha (full stack)
e tratar falhas ocasionais como parte do processo de aprendizado.
Se estabilidade e suporteabilidade são os objetivos principais,
uma configuração suportada é quase sempre a melhor escolha.