SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-13

VPS에 Docker로 Discourse 설치하는 방법

공식 Docker 설치 프로그램을 사용하여 VPS에 Discourse를 구축하는 과정을 안내합니다. RAM과 스왑 설정, SMTP 구성, app.yml 파일 수정 및 재빌드 단계 등 설치 시 반드시 확인해야 할 필수 요구 사항을 상세히 정리했습니다.

VPS에 Discourse 설치: 단일 컨테이너, 단일 설정 파일

VPS에 Discourse를 설치하려면 프로젝트에서 제공하는 설치 프로그램을 실행하고, 간단한 마법사 질문에 답한 뒤 빌드가 완료될 때까지 기다리면 됩니다. Discourse는 Rails 애플리케이션, PostgreSQL, Redis, nginx를 포함하는 단일 Docker 컨테이너 형태로 제공됩니다. 이후 변경하게 될 모든 설정은 /var/discourse/containers/app.yml 파일 하나에 담기며, 모든 변경 사항은 재빌드 과정을 거쳐 사이트에 적용됩니다.

공식 설치 방식은 discourse_docker입니다. 이는 launcher 셸 스크립트와 일련의 YAML 템플릿으로 구성됩니다. Discourse는 사용자가 직접 작성한 Compose 파일을 지원하지 않으며, 컨테이너를 수동으로 분리하여 운영하도록 설계되지 않았습니다. 만약 Docker Compose를 사용하여 VPS에서 서비스를 운영하는 방식에 익숙하다면, 이와는 다른 구조를 예상해야 합니다. 여기에는 docker compose up -d가 없으며, ./launcher rebuild app가 곧 배포 과정입니다.

시작 전 Discourse가 요구하는 사항

사용자들이 자주 놓치는 네 가지 요구 사항이 있으며, 이들은 로그인 페이지에 도달하기 전에 문제를 일으킵니다.

  • 메모리. 하나의 컨테이너가 PostgreSQL, Redis, Sidekiq 및 Ruby 웹 서버를 모두 실행합니다. 빌드 단계에서 에셋을 컴파일할 때 실제 운영 중인 사이트보다 더 많은 메모리가 필요합니다.
  • 실제 도메인 이름. 제공되는 샘플 설정 파일에 명시된 대로 "Discourse는 IP 주소만으로는 작동하지 않습니다."
  • 아웃바운드 메일 경로. 계정 활성화, 비밀번호 재설정, 관리자 초대 및 요약 메일은 모두 SMTP(Simple Mail Transfer Protocol)를 통해 발송됩니다.
  • 호스트의 80번 및 443번 포트. 이미 운영 중인 프록시 뒤에 Discourse를 배치하기로 의도적으로 설정한 경우가 아니라면, 이 포트들은 비어 있어야 합니다.
ChartDiscourse published hardware requirements (official install docs, August 2026)
The data behind this chart
[
  {
    "label": "Documented minimum",
    "ram_gb": 1,
    "storage_gb": 10
  },
  {
    "label": "Documented recommended",
    "ram_gb": 2,
    "storage_gb": 20
  }
]

공식 설치 문서에서는 최소 사양으로 1 GB의 RAM(스왑 포함)과 10 GB의 디스크 공간을 제시하며, 권장 사양으로 2 GB의 RAM과 20 GB의 디스크 공간을 권장합니다. 첫 번째 수치는 설치를 완료하기 위한 최소한의 기준일 뿐, 커뮤니티를 원활하게 운영하기 위한 권장 사양은 아님을 유의하십시오. 메모리 사용량의 정점은 트래픽이 아닌 빌드 과정에서 발생하므로 이 차이는 중요합니다.

설치 전 도메인을 서버로 연결하기

사용할 호스트네임에 대한 A 레코드를 생성한 다음, 서버에서 직접 해당 레코드를 확인하십시오.

dig +short forum.example.com
curl -4 -s https://ifconfig.co

두 명령어는 동일한 주소를 출력해야 합니다. 설치 마법사가 호스트네임에 대해 연결 테스트를 수행하므로 두 주소는 일치해야 합니다. 이전 주소를 가리키는 레코드가 남아 있으면 테스트가 실패합니다. 2분 전에 생성한 레코드라도 캐시가 남아 있을 수 있으므로, 마법사와 씨름하기보다는 이전 TTL(time to live)이 만료될 때까지 기다리십시오.

해당 레코드를 CDN으로 프록시할지 여부를 지금 결정하십시오. 프록시된 레코드는 서버 주소를 숨기며, 이 경우 컨테이너의 인증서 요청이 실패하게 됩니다. ACME(automatic certificate management environment) 챌린지에 Discourse가 아닌 프록시가 응답하기 때문입니다. 최초 설치 시에는 레코드를 프록시하지 않은 상태로 유지하십시오.

공식 설치 프로그램 실행

단일 명령어로 git을 설치하고, Docker 공식 설치 스크립트로 Docker를 설치하며, discourse_docker/var/discourse로 복제한 뒤 설정 마법사를 시작합니다.

wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/main/install-discourse | sudo bash

이미 서버에 Docker가 설치되어 있고 각 단계를 직접 확인하고 싶다면, 동일한 작업을 수동으로 수행하십시오.

sudo -s
git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
./discourse-setup

root 권한으로 실행하십시오. 일반 사용자로 시작하면 discourse-setupThis script must be run as root. Please sudo or log in as root first. 오류와 함께 즉시 중단됩니다. Docker가 설치되지 않은 상태라면 수동 복제로는 아무것도 설치되지 않으므로 Docker is not installed. Please install Docker first. 오류와 함께 중단됩니다.

설정 마법사가 묻는 내용과 기록하는 파일

2026년 8월 기준으로 discourse-setup은 얇은 래퍼(wrapper) 형태입니다. 이 도구는 호스트 네트워크와 Docker 소켓을 마운트한 상태로 discourse/setup-wizard:release를 컨테이너로 실행하며, 이를 통해 마법사가 구성 중인 시스템을 직접 검사할 수 있습니다. 마법사는 호스트 이름과 관리자 이메일 주소를 물어본 뒤, SMTP 블록 설정을 요청합니다. 이후 containers/app.yml 파일을 작성하고 빌드를 다시 수행합니다.

시작하기 전에 알아두어야 할 두 가지 동작이 있습니다. 시스템 메모리가 부족하고 스왑이 설정되어 있지 않으면, 마법사는 작업을 멈추고 스왑 생성을 제안합니다. 이때 래퍼는 2 GB 크기의 /swapfile을 생성하여 /etc/fstab에 추가하고, /etc/sysctl.d/30-discourse-swap.confvm.swappiness = 10을 설정한 뒤 마법사를 다시 시작합니다. 마법사가 완료되면 Rebuilding app in 5 seconds (Ctrl+C to cancel)...을 출력하고 호스트에서 ./launcher rebuild app을 실행합니다. 소형 VPS에서는 이 빌드 과정에 수 분이 소요되며, 모든 에셋을 처음부터 컴파일해야 하므로 첫 번째 빌드가 가장 느립니다.

./discourse-setup --help는 문제가 발생했을 때 중요한 플래그들을 나열합니다. --skip-rebuild은 빌드 없이 설정 파일만 작성하며, --skip-connection-test는 DNS 및 포트 검사를 건너뜁니다. --skip-connection-test는 테스트 실패 원인을 이미 알고 있는 경우에만 사용하십시오. 예를 들어, 호스트가 사용자가 직접 제어하는 네트워크 방화벽 뒤에 있는 경우가 이에 해당합니다.

첫 빌드 전 app.yml 검토하기

마법사가 생성한 파일은 이제 사용자가 직접 관리해야 합니다. sudo nano /var/discourse/containers/app.yml을 사용하여 파일을 엽니다. 이 파일의 항목들이 거의 모든 설정을 결정합니다.

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  ## Uncomment these two lines if you wish to add Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

expose:
  - "80:80"   # http
  - "443:443" # https

env:
  DISCOURSE_HOSTNAME: "forum.example.com"
  DISCOURSE_DEVELOPER_EMAILS: "you@example.com"
  DISCOURSE_SMTP_ADDRESS: smtp.example.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: user@example.com
  DISCOURSE_SMTP_PASSWORD: "your-smtp-password"

DISCOURSE_HOSTNAME은 사이트가 응답할 주소이며, Discourse는 이 값을 기준으로 링크를 생성합니다. 따라서 잘못된 값을 입력하면 사이트가 한 번 로드된 후 다른 곳으로 리다이렉트되는 문제가 발생합니다. DISCOURSE_DEVELOPER_EMAILS은 쉼표로 구분된 목록이며, 여기에 기재된 주소는 첫 가입 시 자동으로 관리자 권한을 부여받습니다. 본인의 이메일 주소를 입력하고 해당 계정으로 가입하십시오. 이것이 첫 번째 관리자 계정을 생성하는 방법입니다.

이 파일은 SMTP 비밀번호를 일반 텍스트로 저장하므로 sudo chmod 700 /var/discourse/containers를 사용하여 해당 디렉터리의 접근 권한을 제한하십시오. 또한 YAML 형식에서는 공백이 곧 설정의 일부입니다. 키의 들여쓰기가 어긋나면 구문 오류로 인해 빌드가 실패하고 사이트가 작동하지 않게 됩니다. 샘플 파일에도 명시된 주의 사항이 하나 있습니다. 따옴표로 묶이지 않은 비밀번호 내부에 #이 포함되면 그 이후부터는 주석으로 처리됩니다. 따라서 특수 문자가 포함된 비밀번호는 반드시 따옴표로 묶으십시오.

이메일 설정은 설치 과정에서 가장 많이 막히는 단계입니다

2026년 8월 기준으로 설치 마법사에서 SMTP 설정을 건너뛰고 Discourse ID 로그인을 사용할 수 있으며, app.yml에는 이메일 설정 유효성 검사를 건너뛰는 기능을 하는 DISCOURSE_SKIP_EMAIL_SETUP 스위치가 포함되어 있습니다. 소프트웨어를 처음 살펴보는 단계라면 설정을 건너뛰는 것도 합리적입니다. 하지만 커뮤니티 운영을 위해서는 권장하지 않습니다. 발신 메일이 없으면 사용자가 계정을 활성화하거나 비밀번호를 재설정할 수 없기 때문입니다.

실질적인 문제는 대부분의 VPS 제공업체가 25번 포트의 아웃바운드 트래픽을 차단한다는 점이며, 이로 인해 서버에 직접 구축한 메일 서버는 메일을 발송할 수 없습니다. 587번 포트나 암시적 TLS(transport layer security)를 사용하는 465번 포트에서 인증된 릴레이를 사용하십시오. 465번 포트의 경우 샘플 설정에서 권장하는 DISCOURSE_SMTP_FORCE_TLS: true을 설정하십시오. 리빌드하기 전에 호스트에서 연결 가능 여부를 먼저 테스트하십시오.

nc -vz smtp.example.com 587

정상적인 결과는 succeeded!로 끝나는 한 줄의 메시지입니다. 명령이 응답 없이 대기하다가 시간 초과(timeout)가 발생한다면 해당 포트가 VPS 외부로 나가는 경로에서 차단된 것이며, 어떤 Discourse 설정으로도 이를 해결할 수 없습니다. 제공업체가 허용하는 포트로 변경하거나, 해당 포트를 열어달라고 요청하십시오.

사이트가 가동되면 관리자 페이지의 이메일 메뉴에서 테스트 메시지를 발송한 뒤, 같은 페이지의 '건너뜀(Skipped)' 및 '반송됨(Bounced)' 탭을 확인하십시오. 이 탭들은 Discourse가 발송을 거부했거나 릴레이 서버에서 거절된 메일을 기록하는 곳입니다. 로그를 일일이 읽는 것보다 이곳에서 거부 사유를 확인하는 것이 훨씬 빠릅니다.

TLS: 컨테이너가 직접 인증서를 발급받게 설정하기

Discourse가 80번과 443번 포트를 직접 점유한다면, 내장된 발급 기능을 사용하십시오. 위에서 언급한 두 개의 SSL 템플릿 줄의 주석을 해제한 뒤, 빌드를 다시 수행합니다. 이 템플릿은 acme.sh를 구동하고, 인증서를 /shared/ssl 아래의 공유 볼륨에 저장하며, 컨테이너 내부에서 주기적으로 갱신하고, Discourse가 HTTPS를 강제하도록 설정합니다.

이 과정이 정상적으로 작동하려면 인터넷에서 80번 포트로 접근이 가능해야 합니다. HTTP 챌린지 응답이 해당 포트를 통해 이루어지기 때문입니다. 443번 포트만 허용하는 방화벽을 사용하면 빌드는 완료되더라도 인증서가 발급되지 않습니다. 빌드 직후 ./launcher logs app 명령을 사용하여 결과를 확인하십시오.

Nginx나 Caddy를 앞단에 배치해야 합니까?

VPS에서 Discourse만 단독으로 운영한다면 배치하지 마십시오. 컨테이너는 이미 최적화된 nginx를 실행 중이며, 두 번째 프록시를 추가하면 네트워크 홉이 늘어나고, 인증서 갱신이 복잡해지며, 헤더 관련 버그가 발생할 새로운 원인이 됩니다.

동일한 VPS에서 다른 사이트도 함께 운영할 때만 앞단에 프록시를 배치하십시오. templates 목록에 templates/web.socketed.template.yml을 추가하고, expose 줄 두 개를 주석 처리한 뒤, 두 개의 SSL 템플릿은 주석 처리된 상태로 둡니다. 그러면 컨테이너는 /var/discourse/shared/standalone/nginx.http.sock의 유닉스 소켓에서 대기하며 포트를 전혀 점유하지 않게 되어, 80번과 443번 포트를 사용자가 직접 설정한 프록시에서 사용할 수 있습니다.

server {
  listen 443 ssl;
  server_name forum.example.com;

  location / {
    proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

.sock 뒤의 콜론은 nginx의 유닉스 소켓 문법에 포함되는 요소이며, 이를 생략하면 sudo nginx -t는 설정을 거부합니다. X-Forwarded-Proto 또한 필수 항목입니다. Discourse는 절대 경로 링크를 생성하므로, 해당 헤더가 없으면 HTTPS 페이지에서 http:// 링크를 출력하게 되어 브라우저가 혼합 콘텐츠(mixed content)로 간주하고 차단합니다. 컨테이너를 소켓 방식으로 전환하면 TLS 관리는 사용자의 몫이 되므로, Ubuntu 24.04 및 nginx에서의 Certbot을 참고하여 호스트에서 인증서를 발급받으십시오. 아직 사용할 프록시를 결정하지 못했다면, nginx, Caddy 및 Traefik 비교 문서를 통해 각 선택의 장단점을 확인하시기 바랍니다.

재빌드, 업그레이드 및 실무에서 사용하는 명령어

cd /var/discourse
./launcher rebuild app

rebuild는 실행 중인 컨테이너를 삭제하고, app.yml에서 새로운 컨테이너를 부트스트랩하여 시작합니다. 빌드하는 동안 사이트는 오프라인 상태가 되므로, 모든 설정 변경은 몇 분간의 계획된 다운타임으로 간주해야 합니다.

env: 하위의 값만 변경하는 경우에는 재빌드가 필요하지 않습니다. ./launcher destroy app && ./launcher start app은 이미 빌드된 이미지로부터 컨테이너를 다시 생성하며, 이는 수 초 내에 완료됩니다. templates: 또는 hooks: 하위의 변경 사항은 이미지 자체를 수정하므로 전체 재빌드가 필요합니다.

업그레이드는 두 가지 방식으로 진행됩니다. 포인트 릴리스는 app.yml이 빌드 중에 복제하는 docker_manager 플러그인이 제공하는 /admin/upgrade의 웹 인터페이스에서 적용합니다. 베이스 이미지나 템플릿에 대한 변경 사항은 git을 통해 이루어집니다.

cd /var/discourse
git pull
./launcher rebuild app

재빌드 과정에서 소규모 서버가 실패하는 경우가 많습니다. 에셋 컴파일은 전체 시스템에서 메모리 사용량이 가장 높은 작업이기 때문입니다. 빌드가 중간에 멈추고 dmesgruby 프로세스를 지칭하는 Out of memory: Killed process와 같은 줄이 표시된다면, 사이트 자체는 이전에 정상적으로 작동했더라도 빌드 도중 메모리가 부족해진 것입니다. 스왑(swap)을 추가하고 재빌드를 다시 실행하십시오.

./launcher logs app
./launcher enter app
./launcher cleanup

logs은 컨테이너의 출력을 출력하며, enter은 컨테이너 내부의 셸을 엽니다. cleanup는 24시간 이상 중지된 컨테이너를 제거합니다. 재빌드할 때마다 이전 컨테이너가 남게 되어 소규모 VPS의 디스크 용량이 조용히 고갈될 수 있으므로, 가끔 cleanup을 실행하십시오.

백업 및 백업 파일에 포함되지 않는 항목

Admin의 Backups 페이지에서 백업을 수행합니다. 아카이브 파일은 호스트의 /var/discourse/shared/standalone/backups/default/ 경로에 저장됩니다. 동일한 작업은 셸에서도 실행할 수 있습니다.

cd /var/discourse
./launcher enter app
discourse backup

discourse restore <filename> 명령은 복원을 수행하며, discourse enable_restore 명령을 실행하기 전까지는 복원이 거부됩니다. 이 안전장치는 실수로 입력한 명령이 운영 중인 포럼을 덮어쓰는 것을 방지하기 위해 존재합니다.

사용자가 직접 해결해야 할 두 가지 공백이 있습니다. 아카이브에는 데이터베이스가 포함되지만, 업로드된 파일은 백업 설정에서 업로드 포함 옵션이 켜져 있을 때만 포함되므로 신뢰하기 전에 해당 설정을 확인해야 합니다. 아카이브에는 app.yml 파일이 절대 포함되지 않으므로, 새로운 VPS에 복원하더라도 호스트 이름과 SMTP 설정 블록은 여전히 필요합니다. 따라서 해당 파일도 서버 외부로 별도 복사해 두어야 합니다.

또한 아카이브는 보호 대상 사이트와 동일한 디스크에 저장되므로, 이것만으로는 완전한 백업이라 할 수 없습니다. 일정을 정해 다른 곳으로 파일을 전송하십시오.

rsync -avz root@forum.example.com:/var/discourse/shared/standalone/backups/default/ ~/discourse-backups/

활발한 포럼이 사용하는 RAM 비용

부트스트랩은 감지된 메모리와 CPU를 바탕으로 UNICORN_WORKERSdb_shared_buffers을 설정하며, 샘플 설정은 공유 버퍼를 전체 메모리의 4분의 1로 제한합니다. 각 unicorn 워커는 완전한 Ruby 프로세스이며, Sidekiq은 그 옆에서 백그라운드 작업을 실행하므로 메모리 사용량은 등록된 회원 수가 아닌 동시 요청 수에 따라 결정됩니다. 회원이 수백 명 정도인 조용한 포럼은 부하가 큰 작업이 아닙니다.

이 문서를 포함하여 어떤 기사에 나온 수치만으로 서버 크기를 결정하지 마십시오. 직접 측정해야 합니다.

free -m
docker stats --no-stream

Swap이 지속적으로 사용되면서 페이지 응답이 느려진다면 RAM이 부족하다는 의미입니다. 메모리 사용량은 일정한데 페이지 응답이 느리다면 보통 다른 원인이 있는 것이므로, 더 큰 플랜을 구매하기 전에 ./launcher logs app을 읽어 보십시오. 외부에서 상태를 확인하는 점검 항목도 추가하십시오. 새벽 3시에 메모리가 부족해진 포럼은 조용히 실패하기 때문입니다. 별도의 호스트에서 실행되는 자체 호스팅 Uptime Kuma 상태 모니터를 사용하면 회원들이 알기 전에 관리자가 먼저 문제를 파악할 수 있습니다.

Discourse가 적합하지 않은 경우

Discourse는 설치 규모가 크고 app.yml에 있는 설정을 변경할 때마다 재빌드 과정을 거쳐야 하는 대규모 애플리케이션입니다. 이러한 비용을 지불하는 이유는 실질적인 관리 도구와 방대한 아카이브에서도 정상적으로 작동하는 검색 기능을 얻기 위해서입니다. 대화할 공간이 필요한 30명 규모의 커뮤니티에는 Discourse가 제공하는 기능이 과도할 수 있습니다. 먼저 자체 호스팅 포럼 소프트웨어 비교 문서를 읽어보십시오. Discourse를 선택하는 이유는 이미 알고 있는 이름이기 때문이 아니라, Discourse가 제공하는 기능이 반드시 필요하기 때문이어야 합니다.

FAQ

Can I install Discourse on a VPS without a domain name?

No. The shipped configuration states that Discourse will not work with a bare IP number, and DISCOURSE_HOSTNAME is required. Discourse builds absolute links from that hostname, so an IP address there breaks links and blocks certificate issuance. Create an A record before you start, and confirm with dig +short forum.example.com that it resolves to your server's address.

Do I have to configure SMTP to finish the install?

As of August 2026 you can skip it. The setup wizard offers Discourse ID logins instead, and app.yml carries a switch that skips email setup validation. For anything past a first look, configure it, because account activation and password resets both leave by mail. Use an authenticated relay on port 587 or 465, since most VPS providers block outbound port 25.

Why did my Discourse rebuild fail part way through?

Memory is the usual cause. Asset compilation during the build needs more memory than the running site, so a box that serves the forum fine can still fail to rebuild it. If dmesg shows Out of memory: Killed process naming a ruby process, add swap (the wizard's own swapfile is 2 GB) and run ./launcher rebuild app again. A build that stops on a YAML error instead points at an indentation mistake in app.yml.

Should Discourse sit behind my own nginx or Caddy?

Only when the VPS serves other sites too. Alone on a server, let the container keep ports 80 and 443 and issue its own certificate, which leaves fewer moving parts. To share the machine, add templates/web.socketed.template.yml, comment out the expose lines, and proxy to the unix socket at /var/discourse/shared/standalone/nginx.http.sock. Pass X-Forwarded-Proto through, or Discourse emits http:// links on an HTTPS page.

How do I back up a self-hosted Discourse?

Use the Backups page in Admin, or run discourse backup after ./launcher enter app. Archives land on the host at /var/discourse/shared/standalone/backups/default/. Confirm the setting that includes uploads is on, copy /var/discourse/containers/app.yml alongside the archive, and move both to another machine, because a backup on the same disk as the site does not survive the failure it exists for.