단일 서버에서 Discourse의 독립 설치(multisite 미사용)를 여러 개 호스팅할 수 있습니다 (별도 컨테이너 / 별도 포트 / 별도 app.yml).
multisite보다 수동적인 작업이 필요하지만, 인스턴스 간 격리를 유지하고 나중에 개별 사이트를 전용 서버로 이전하는 것이 더 쉬워집니다.
실용적인 패턴 중 하나는 다음과 같습니다:
• 외부 Postgres (단일 인스턴스)
• 외부 Redis (단일 인스턴스)
• 여러 Discourse 웹 컨테이너
• 단일 Sidekiq 노드
• 헬스 체크가 포함된 리버스 프록시
이 방식은 multisite를 완전히 피하면서 저트래픽 환경에서도 비용 절감을 허용합니다.
DISCOURSE 멀티 컨테이너 런북
외부 Postgres + Redis + HAProxy + app1 / app2
- 호스트 패키지
| 단계 | 명령어 |
|---|---|
| 시스템 업데이트 | apt-get update |
| 기본 도구 설치 | apt-get install -y ca-certificates curl gnupg lsb-release |
| HAProxy + certbot + socat 설치 | apt-get install -y haproxy certbot socat |
- DOCKER 네트워크 (필수)
컨테이너가 이름으로 서로를 해결(resolve)할 수 있도록 사용자 정의 Docker 네트워크가 필요합니다.
| 단계 | 명령어 |
|---|---|
| 네트워크 생성 | docker network create discourse-net |
| 확인 | docker network ls | grep discourse-net |
이로 인해 다음이 올바르게 작동합니다:
• DISCOURSE_DB_HOST=pg
• DISCOURSE_REDIS_HOST=redis
- 시크릿(비밀번호)
| 목적 | 명령어 |
|---|---|
| Postgres 슈퍼유저 | export PG_SUPERPASS='REPLACE_ME_super_strong' |
| Discourse DB 비밀번호 | export DISCOURSE_DBPASS='REPLACE_ME_discordb_strong' |
| Redis 비밀번호 | export REDIS_PASS='REPLACE_ME_redis_strong' |
| 시크릿 키 베이스 | export SECRET_KEY_BASE="$(openssl rand -hex 64)" |
- POSTGRES 컨테이너
| 단계 | 명령어 |
|---|---|
| 디렉터리 생성 | mkdir -p /var/discourse/external/postgres |
| 컨테이너 실행 | 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 |
| 확인 | docker ps | grep pg |
- 데이터베이스 생성
| 단계 | 명령어 |
|---|---|
| 역할 생성 | docker exec -it pg psql -U postgres -c "CREATE ROLE discourse LOGIN PASSWORD '$DISCOURSE_DBPASS';" |
| DB 생성 | docker exec -it pg psql -U postgres -c "CREATE DATABASE discourse OWNER discourse ENCODING 'UTF8' TEMPLATE template0;" |
| 텍스트 검색 설정 | docker exec -it pg psql -U postgres -d discourse -c "ALTER DATABASE discourse SET default_text_search_config = 'pg_catalog.english';" |
| 로그인 테스트 | docker exec -it pg psql -U discourse -d discourse -c "select 1;" |
- PGVECTOR 확장
최신 Discourse 버전에 필요합니다.
| 단계 | 명령어 |
|---|---|
| 설치 | docker exec -it pg bash -lc 'apt-get update && apt-get install -y postgresql-15-pgvector && rm -rf /var/lib/apt/lists/*' |
| 확장 생성 | docker exec -it pg psql -U postgres -d discourse -c "CREATE EXTENSION IF NOT EXISTS vector;" |
| 확인 | docker exec -it pg psql -U postgres -d discourse -c "SELECT extname FROM pg_extension WHERE extname='vector';" |
- REDIS 컨테이너
| 단계 | 명령어 |
|---|---|
| 디렉터리 생성 | mkdir -p /var/discourse/external/redis |
Redis 구성 템플릿:
requirepass REPLACE_ME_REDIS
appendonly yes
save 900 1
save 300 10
save 60 10000
| 단계 | 명령어 |
|---|---|
| 구성 작성 | tee /var/discourse/external/redis/redis.conf >/dev/null <<EOF |
| 비밀번호 삽입 | sed -i "s/REPLACE_ME_REDIS/$REDIS_PASS/" /var/discourse/external/redis/redis.conf |
| 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 |
| 인증 테스트 | docker exec -it redis redis-cli -a "$REDIS_PASS" ping |
- DISCOURSE 디렉터리 레이아웃
| 단계 | 명령어 |
|---|---|
| 기본 디렉터리 생성 | mkdir -p /var/discourse |
| 진입 | cd /var/discourse |
| 저장소 클론 | git clone https://github.com/discourse/discourse_docker.git |
| 컨테이너 디렉터리 | mkdir -p /var/discourse/containers |
| 공유 로그 | mkdir -p /var/discourse/shared/web-only/log/var-log |
| 컨테이너 링크 | ln -sfn /var/discourse/containers /var/discourse/discourse_docker/containers |
| 런처 링크 | ln -sfn /var/discourse/discourse_docker/launcher /var/discourse/launcher |
- 애플리케이션 컨테이너
app1.yml
• web + sidekiq
• 포트 8001
docker_args: "--network=discourse-net"
expose:
- "8001:80"
app2.yml
• web only
• 포트 8002
• sidekiq 비활성화
docker_args: "--network=discourse-net"
expose:
- "8002:80"
run:
- exec: bash -lc 'mkdir -p /etc/service/sidekiq && touch /etc/service/sidekiq/down'
- 부트스트랩
| 단계 | 명령어 |
|---|---|
| 진입 | cd /var/discourse/discourse_docker |
| app1 부트스트랩 | ./launcher bootstrap app1 |
| app1 시작 | ./launcher start app1 |
| app2 부트스트랩 | ./launcher bootstrap app2 |
| app2 시작 | ./launcher start app2 |
- 헬스 체크
| 단계 | 명령어 |
|---|---|
| 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 |
- TLS 인증서
| 단계 | 명령어 |
|---|---|
| 프록시 중지 | systemctl stop haproxy |
| 인증서 발급 | certbot certonly --standalone -d example.com --agree-tos -m you@example.com --non-interactive |
| 프록시 시작 | systemctl start haproxy |
- 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>"
- 제로 다운타임 리빌드
| 단계 | 명령어 |
|---|---|
| app1 비활성화 | echo "disable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock |
| app1 리빌드 | ./launcher rebuild app1 |
| app1 활성화 | echo "enable server be_discourse/app1" | socat stdio /run/haproxy/admin.sock |
| 단계 | 명령어 |
|---|---|
| app2 비활성화 | echo "disable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock |
| app2 리빌드 | ./launcher rebuild app2 |
| app2 활성화 | echo "enable server be_discourse/app2" | socat stdio /run/haproxy/admin.sock |
END
Docker 네트워크 필수
외부 Postgres 및 Redis
pgvector 설치됨
Sidekiq가 app1에 격리됨
HAProxy 헬스 체크 활성화됨
유지보수 폴백 활성화됨
롤링 리빌드 지원됨
나중에 한 사이트를 전용 서버로 이전하는 방법
완전히 독립적인 Discourse 설치(multisite가 아닌)를 실행하는 것의 이점 중 하나는 이전이 단순하고 위험이 낮다는 것입니다.
각 Discourse 인스턴스는 이미 다음을 가지고 있습니다:
• 자체 컨테이너
• 자체 업로드
• 자체 데이터베이스
• 자체 Redis 사용
• 자체 app.yml
multisite를 분리하는 작업은 필요하지 않습니다.
고수준 이전 단계
- 새 VPS 프로비저닝
새 서버에 Docker와 Discourse를 정상적으로 설치합니다.
multisite를 구성하지 마세요.
- 전체 백업 생성
소스 사이트에서:
관리자 → 백업 → 백업 생성
백업 파일을 다운로드합니다.
이것은 다음을 포함합니다:
• 데이터베이스
• 업로드
• 사용자
• 설정
• 테마
- 새 서버에서 복원
새 서버에서:
• 초기 설정 완료
• 관리자 로그인
• 백업 업로드
• 복원
Discourse는 스키마 호환성을 자동으로 처리합니다.
- DNS 전환
도메인의 A 레코드를 새 서버 IP를 가리키도록 업데이트합니다.
DNS가 전파되면 사용자는 투명하게 이동됩니다.
- 구 컨테이너 폐기
원래 서버에서:
• 구 컨테이너 중지
• 확신이 들면 제거
같은 호스트의 다른 Discourse 설치에는 영향을 주지 않습니다.
이것이 multisite보다 단순한 이유
multisite 환경에서는 이전 시 종종 다음이 필요합니다:
• 데이터베이스 분리
• 사이트별 데이터 추출
• multisite.yml 조정
• Sidekiq 재구성
• 업로드 및 이메일 재설정
독립 설치의 경우 이러한 작업은 전혀 필요하지 않습니다.
각 사이트는 이미 독립적입니다.
요약
이 접근 방식은 초기 운영 복잡성을 약간 감수하는 대가로
나중에 매우 단순한 분리를 제공합니다.
실험 또는 초기 단계 커뮤니티 구축 시 특히 잘 작동합니다.
이 접근 방식이 적합하지 않을 가능성이 있는 경우
다음과 같은 경우 이 설정은 일반적으로 좋은 아이디어가 아닙니다:
• 사이트가 초기부터 중등도 이상의 트래픽을 예상하는 경우
• 공식 Discourse 지원에 크게 의존하는 경우
• Docker, 네트워킹 또는 리버스 프록시 디버깅에 익숙하지 않은 경우
• 가동 시간 요구 사항이 엄격하거나 비즈니스에 중요한 경우
• 여러 사이트가 운영 면에서 긴밀하게 결합되어 있는 경우
• 모든 인스턴스에서 빈번한 플러그인 실험을 예상하는 경우
이러한 경우, 다음 중 하나가 운영상의 예상치 못한 문제를 줄이는 데 도움이 될 것입니다:
• 지원되는 multisite 설정
또는
• 서버당 하나의 Discourse 설치
중요한 참고 사항
이 접근 방식은 인프라 유연성을 증가시키지만,
관리자의 책임도 증가시킵니다.
전체 스택을 소유하고 간헐적인 장애를 학습 과정의 일부로 취급하는 데 익숙한 사람이 실행할 때 가장 잘 작동합니다.
안정성과 지원 가능성이 주요 목표라면,
지원되는 구성이 거의 항상 더 나은 선택입니다.