Upgrading failing with: The "before-server" Nginx outlet is missing... discourse_docker is not compatible with the chosen Discourse version

I’ve a Discourse server for which ./launcher rebuild app results in:

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

The code is up to date:

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

nothing to commit, working tree clean

I tried running ./discourse-doctor but that resulted in the same error.

The server is question is running Ubuntu:

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

With the latest 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.

Does anyone have any suggestions? Do I need to manually edit the Nginx config in the running container to get around this or something?

I have tried downgrading docker_manager code base to the version before the last commit on Jul 10, 2025:

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

But that didn’t make a difference.

I just did a rebuild without any trouble. Are there other errors above what you included?

What does free -h report?

It’s not a disk space issue:

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

The server doesn’t have any swap but it has 6 GB 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

Do you think a swap disk is needed?

There were no other errors but I could post the whole of the ./launcher rebuild app output if you think that might help?

Following is what the Nginx config (/etc/nginx/conf.d/discourse.conf) in the container contains, there was no reference to 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;
  }

}

So I installed vim and dos2unix and edited the files to add the line:

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

And I stopped and started Nginx and then exited the container and ran ./launcher rebuild app again but the same error was returned:


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

So I restarted the container (./launcher start app) to check it:

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

So I don’t understand why it is failing when the Nginx include is present?

There are a lot of differences between /var/www/discourse/config/nginx.sample.conf and /etc/nginx/conf.d/discourse.conf — should I try replacing the file with the sample and editing it to update the domain name?

Update

Earlier the Discourse web interface wouldn’t allow updates, it said updates must be done using the CLI, however now it does allow them (why is that?) so I have updated discourse_docker using the Web UI:

And also discourse:

So this issue is resolved however what, if anything, I did to solve it is a mystery… :woman_shrugging:

I’ve tracked down the issue — it was caused by the discourse-images-guardian plugin from @mbcahyono which needed the nginx.sample.conf file updating, I have done this and created a pull request to fix it.

Merged! Thanks for the PR.

I didn’t get a chance to test it, so I’m trusting you on this one :slight_smile:

1 Like

Thanks @mbcahyono I did some basic testing and it seems to work…! However this is this issue — overriding the site image URLs doesn’t work any more, any ideas how to fix this?