Customising Discourse's web server behaviour using outlets

Summary

As of this commit the default nginx configuration for Discourse includes what are known as “outlets” - a supported way to inject additional statements into the nginx configuration in appropriate places.

This change enables a more robust approach to managing the configuration and behaviour of a web server where we were previously relying on relatively brittle search/replace commands.

Over time we will adapt our own configuration templates to use outlets.

Usage

There are three supported outlet sections:

  • before-server: http context configuration statements
  • server: server context configuration statements
  • discourse: location context configuration statements - these get applied to requests forwarded to Discourse

Examples

Here are a few examples of how to use these outlets in your app.yml configuration file to accomplish goals.

These can be added anywhere you can use a run or file command; my recommendation is to do it in the after_code hook so rebuild can fail fast if something is wrong with the syntax.

Adding a response header

This example adds a header to each response that gets forwarded to Discourse:

hooks:
  after_code:
    - file:
        path: /etc/nginx/conf.d/outlets/discourse/clacks-overhead.conf
        chmod: 444
        contents: |
          add_header x-clacks-overhead "GNU Terry Pratchett";

Result:

○ → curl -I https://example.contoso.com/
HTTP/2 200 
…
x-clacks-overhead: GNU Terry Pratchett

Adding a static file on a single path

This example adds a static file served by nginx on a single path, outside of the normal Discourse tree.

hooks:
  after_code:
    - file:
        path: /etc/nginx/conf.d/outlets/server/well-known-important-file.conf
        chmod: 444
        contents: |
          location = /.well-known/important-file {
            default_type application/json;
            root /var/www/static/;
          }
    - file:
        path: /var/www/static/.well-known/important-file
        chmod: 444
        contents: |
          {"content-free":true}

Result:

○ → curl -i https://example.contoso.com/.well-known/important-file
HTTP/2 200 
server: nginx
date: Fri, 07 Mar 2025 23:22:38 GMT
content-type: application/json
content-length: 22
last-modified: Fri, 07 Mar 2025 23:12:08 GMT
strict-transport-security: max-age=63072000
accept-ranges: bytes

{"content-free":true}
8 Likes

I think I’m really excited about this, except… I don’t understand it very well. Is the second example giving us a way to serve a static page of our choice?

That’s it exactly.

It’s not a very scalable solution in terms of managing the content, but it does have the benefit of being easy to implement.

1 Like

Thanks Michael, at least I understood enough to figure out what is going on.

nice, thanks for the clues