업그레이드 실패: "before-server" Nginx 아웃렛이 없습니다... discourse_docker가 선택한 Discourse 버전과 호환되지 않습니다

Discourse 서버에서 ./launcher rebuild app을 실행하면 다음과 같은 오류가 발생합니다:

FAILED
--------------------
Pups::ExecError: grep -q 'outlets/before-server' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The "before-server" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 ) failed with return #<Process::Status: pid 300 exit 1>
Location of failure: /usr/local/lib/ruby/gems/3.3.0/gems/pups-1.3.0/lib/pups/exec_command.rb:131:in `spawn'
exec failed with the params {"cmd"=>["cp $home/config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf", "rm /etc/nginx/sites-enabled/default", "mkdir -p /var/nginx/cache", "grep -q 'outlets/before-server' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"before-server\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )", "grep -q 'outlets/server' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"server\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )", "grep -q 'outlets/discourse' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"discourse\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )", "mkdir -p /etc/nginx/conf.d/outlets/before-server", "touch /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf", "touch /etc/nginx/conf.d/outlets/before-server/30-ratelimited.conf", "mkdir -p /etc/nginx/conf.d/outlets/server", "touch /etc/nginx/conf.d/outlets/server/10-http.conf", "touch /etc/nginx/conf.d/outlets/server/20-https.conf", "touch /etc/nginx/conf.d/outlets/server/30-offline-page.conf", "mkdir -p /etc/nginx/conf.d/outlets/discourse", "touch /etc/nginx/conf.d/outlets/discourse/20-https.conf", "touch /etc/nginx/conf.d/outlets/discourse/30-ratelimited.conf"]}
bootstrap failed with exit code 1
** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one.
./discourse-doctor may help diagnose the problem.
727ce99bf07d8a65ba26b70f4515a2b6dab493ca00f436098de95e70604b6d6b

코드는 최신 상태입니다:

git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

./discourse-doctor을 실행해 보았지만 동일한 오류가 발생했습니다.

해당 서버는 Ubuntu를 실행 중입니다:

cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.2 LTS"

최신 버전의 Docker를 사용 중입니다:

apt info docker-ce
Package: docker-ce
Version: 5:28.3.2-1~ubuntu.24.04~noble
Priority: optional
Section: admin
Maintainer: Docker <support@docker.com>
Installed-Size: 90.1 MB
Pre-Depends: init-system-helpers (>= 1.54~)
Depends: containerd.io (>= 1.6.24), docker-ce-cli, iptables, libc6 (>= 2.34), libsystemd0
Recommends: apparmor, ca-certificates, docker-ce-rootless-extras, git, pigz, procps, xz-utils
Suggests: cgroupfs-mount | cgroup-lite, kmod
Conflicts: docker (<< 1.5~), docker-engine, docker.io
Replaces: docker-ce-cli (<< 5:28.0.0), docker-engine
Homepage: https://www.docker.com
Download-Size: 19.6 MB
APT-Manual-Installed: yes
APT-Sources: https://download.docker.com/linux/ubuntu noble/stable amd64 Packages
Description: Docker: the open-source application container engine
 Docker is a product for you to build, ship and run any application as a
 lightweight container
 .
 Docker containers are both hardware-agnostic and platform-agnostic. This means
 they can run anywhere, from your laptop to the largest cloud compute instance and
 everything in between - and they don't require you to use a particular
 language, framework or packaging system. That makes them great building blocks
 for deploying and scaling web apps, databases, and backend services without
 depending on a particular stack or provider.

N: There are 34 additional records. Please use the '-a' switch to see them.

해결책이 있는 분 계신가요? 이 문제를 우회하기 위해 실행 중인 컨테이너의 Nginx 설정을 수동으로 편집해야 하는 건가요?

마지막 커밋인 2025년 7월 10일 이전 버전으로 docker_manager 코드베이스를 다운그레이드해 보았습니다:

su - discourse
cd /var/discourse/
./launcher enter app
su - discourse
cd /var/www/discourse/plugins/docker_manager
git checkout d91016c
exit
exit
./launcher rebuild app

하지만 달라진 점이 없었습니다.

문제가 전혀 없이 재빌드를 마쳤습니다. 포함하신 내용 위에 다른 오류가 있나요?

free -h의 출력 결과는 어떻게 되나요?

디스크 공간 문제는 아닙니다:

df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           588M  1.1M  587M   1% /run
/dev/sda2       118G   79G   34G  71% /
tmpfs           2.9G     0  2.9G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           588M   12K  588M   1% /run/user/1001

서버에는 스왑이 없지만 6GB의 RAM이 있습니다:

free -h
               total        used        free      shared  buff/cache   available
Mem:           5.7Gi       793Mi       1.2Gi       1.0Mi       4.1Gi       5.0Gi
Swap:             0B          0B          0B

스왑 디스크가 필요할까요?

다른 오류는 없었지만, 도움이 될 것 같다면 ./launcher rebuild app의 전체 출력을 게시할 수 있습니다.

컨테이너 내 Nginx 설정(/etc/nginx/conf.d/discourse.conf)에는 다음과 같은 내용이 포함되어 있으며, outlets/before-server에 대한 참조는 없었습니다:

# Additional MIME types that you'd like nginx to handle go in here
types {
    text/csv csv;
}

upstream discourse { server 127.0.0.1:3000; }

proxy_cache_path /var/nginx/cache keys_zone=one:10m max_size=200m;

# see: https://meta.discourse.org/t/x/74060
proxy_buffer_size 8k;

# If you are going to use Puma, use these:
#
# upstream discourse {
#   server unix:/var/www/discourse/tmp/sockets/puma.sock;
# }


# attempt to preserve the proto, must be in http context
map $http_x_forwarded_proto $thescheme {
  default $scheme;
  https https;
}

log_format log_discourse '[$time_local] "$http_host" $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $status $bytes_sent "$http_referer" $upstream_response_time $request_time "$sent_http_x_discourse_username"';

limit_req_zone $binary_remote_addr zone=flood:10m rate=12r/s;
limit_req_zone $binary_remote_addr zone=bot:10m rate=200r/m;
limit_req_status 429;
limit_conn_zone $binary_remote_addr zone=connperip:10m;
limit_conn_status 429;
server {
  listen 80;
  return 301 https://discourse.example.org$request_uri;
}
server {


  access_log /var/log/nginx/access.log log_discourse;

  listen 443 ssl;
http2 on;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

ssl_certificate /shared/ssl/discourse.example.org.cer;
ssl_certificate /shared/ssl/discourse.example.org_ecc.cer;

ssl_certificate_key /shared/ssl/discourse.example.org.key;
      proxy_ignore_headers "Set-Cookie";
      proxy_hide_header "Set-Cookie";

      proxy_cache one;
      proxy_cache_key $uri;
      proxy_cache_valid 200 7d;
      proxy_cache_valid 404 1m;
      proxy_set_header Connection "";

      proxy_pass https://avatars.discourse.org/;
      break;
    }

    # we need buffering off for message bus
    location /message-bus/ {
      proxy_set_header X-Request-Start "t=${msec}";
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $thescheme;
      proxy_http_version 1.1;
      proxy_buffering off;
      proxy_pass http://discourse;
      break;
    }

    # this means every file in public is tried first
    try_files $uri @discourse;
  }

  location /downloads/ {
    internal;
    alias $public/;
  }

  location @discourse {
add_header Strict-Transport-Security 'max-age=31536000'; # remember the certificate for a year and automatically connect to HTTPS for this domain

  limit_conn connperip 20;
  limit_req zone=flood burst=12 nodelay;
  limit_req zone=bot burst=100 nodelay;
    add_header Referrer-Policy 'no-referrer-when-downgrade';
    proxy_set_header Host $http_host;
    proxy_set_header X-Request-Start "t=${msec}";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $thescheme;
    proxy_pass http://discourse;
  }

}

그래서 vim과 dos2unix를 설치하고 파일을 편집하여 해당 라인을 추가했습니다:

include conf.d/outlets/before-server/*.conf;

그 후 Nginx를 중지하고 다시 시작했으며, 컨테이너를 종료한 뒤 ./launcher rebuild app을 다시 실행했지만 동일한 오류가 반환되었습니다:


FAILED
--------------------
Pups::ExecError: grep -q 'outlets/before-server' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The "before-server" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 ) failed with return #<Process::Status: pid 300 exit 1>
Location of failure: /usr/local/lib/ruby/gems/3.3.0/gems/pups-1.3.0/lib/pups/exec_command.rb:131:in `spawn'
exec failed with the params {"cmd"=>["cp $home/config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf", "rm /etc/nginx/sites-enabled/default", "mkdir -p /var/nginx/cache", "grep -q 'outlets/before-server' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"before-server\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )", "grep -q 'outlets/server' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"server\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )", "grep -q 'outlets/discourse' /etc/nginx/conf.d/discourse.conf || ( >&2 echo 'The \"discourse\" Nginx outlet is missing. This version of discourse_docker is not compatible with the chosen Discourse version.' ; exit 1 )", "mkdir -p /etc/nginx/conf.d/outlets/before-server", "touch /etc/nginx/conf.d/outlets/before-server/20-redirect-http-to-https.conf", "touch /etc/nginx/conf.d/outlets/before-server/30-ratelimited.conf", "mkdir -p /etc/nginx/conf.d/outlets/server", "touch /etc/nginx/conf.d/outlets/server/10-http.conf", "touch /etc/nginx/conf.d/outlets/server/20-https.conf", "touch /etc/nginx/conf.d/outlets/server/30-offline-page.conf", "mkdir -p /etc/nginx/conf.d/outlets/discourse", "touch /etc/nginx/conf.d/outlets/discourse/20-https.conf", "touch /etc/nginx/conf.d/outlets/discourse/30-ratelimited.conf"]}
bootstrap failed with exit code 1
** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one.
./discourse-doctor may help diagnose the problem.
b18ed0cbde3a34dfe76ea066657ece16c5a063ff18627d4a7e4b9787268917c0

그래서 컨테이너를 다시 시작(./launcher start app)하여 확인해 보았습니다:

grep -q 'outlets/before-server' /etc/nginx/conf.d/discourse.conf 
echo $?
0

따라서 Nginx include가 존재하는 상태인데 왜 실패하는지 이해할 수 없습니다.

/var/www/discourse/config/nginx.sample.conf와 /etc/nginx/conf.d/discourse.conf 사이에 많은 차이가 있습니다 — 도메인 이름을 업데이트하도록 편집하기 위해 샘플 파일로 파일을 교체해 볼까요?

업데이트

이전에는 Discourse 웹 인터페이스에서 업데이트를 허용하지 않아 CLI를 사용해야 한다고 표시되었지만, 지금은 허용됩니다(왜 그런 걸까요?). 그래서 웹 UI를 통해 discourse_docker를 업데이트했습니다:

그리고 discourse도 업데이트했습니다:

따라서 이 문제는 해결되었지만, 제가 이 문제를 해결하기 위해 무엇을 했는지는(혹은 아무것도 하지 않았는지는) 여전히 의문입니다… :woman_shrugging:

문제를 찾아냈습니다 — @mbcahyono 님의 discourse-images-guardian 플러그인이 원인이었습니다. 이 플러그인은 nginx.sample.conf 파일 업데이트가 필요했는데, 저는 이를 완료하고 수정을 위한 풀 리퀘스트를 생성했습니다.

병합했습니다! PR 올려주셔서 감사합니다.

직접 테스트할 기회가 없어서, 이번 건은 그대로 믿겠습니다 :slight_smile:

@mbcahyono 감사합니다. 기본적인 테스트를 해봤는데 작동하는 것 같습니다…! 다만 이 문제는 이 이슈입니다 — 사이트 이미지 URL을 오버라이드하는 기능이 더 이상 작동하지 않는데, 이 문제를 해결할 아이디어가 있을까요?