Running Discourse at / while serving a custom app at /tickets on the same domain

AI-generated summary

The user Ivernite seeks to serve a custom Go app at /tickets on the same domain as Discourse, which runs at the root /. They are using an external nginx reverse proxy on an OVH VPS to route traffic: / to Discourse (running in Docker on port 4000) and /tickets to their Go app (running on port 5000). itsbhanusharma confirms this setup is feasible with nginx. pfaffman advises using an external proxy and not modifying Discourse internals. After struggling with configuration, Ivernite successfully implements the setup, noting that a link to /tickets in the Discourse header must be set to

Hi everyone,

I’m trying to confirm the correct architecture for serving a custom application at a sub‑path while keeping Discourse at the root (/), without modifying Discourse internals.

What I want (final goal)

https://mydomain.com/         → Discourse
https://mydomain.com/tickets  → Custom ticket system (Go + Gin)

Requirements:

  1. Discourse must remain at / must live at /tickets
  2. Same domain
  3. No Discourse plugin

Current environment

  1. OVH VPS (Ubuntu)Discourse running in Docker (/var/discourse)
  2. Custom Go app running on the same server at 127.0.0.1:8080
  3. External nginx installed on the host (not inside the container)

I am NOT trying to run Discourse in a subfolder like /forum. Oh and before somone asks yes I have tried to use the Discourse tickets plugin - does not work like I want it to.

You can do it easily with nginx, while it is not recommended, I am not aware of discourse using /tickets route for anything.

Thanks for the quick reply! I am using nginx and struggling. This is my configuration and still no luck.

This is inside my app.yml file

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

/etc/nginx/sites-enabled/filename

server {
    listen 80;
    server_name mydomain.com;

    # ---- Tickets app ----
    location ^~ /tickets {
        proxy_pass http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_redirect off;

        proxy_set_header Host $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 $scheme;

        # Discourse auth headers
        proxy_set_header X-Discourse-Username $http_x_discourse_username;
        proxy_set_header X-Discourse-User-Id $http_x_discourse_user_id;
        proxy_set_header X-Discourse-Groups $http_x_discourse_groups;
    }

    # ---- Discourse ----
    location / {
        proxy_pass http://127.0.0.1:4000;
        proxy_http_version 1.1;
        proxy_redirect off;

        proxy_set_header Host $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 $scheme;
    }
}

Can you elaborate exactly where you are struggling?

I think those are probably your best instructions, though. You’ll follow those instructions, but sort-of backward. You’ll have your external reverse proxy serve / to Discourse and /tickets to your app.

@pfaffman So what I’m taking away is: reuse the concept, not the configuration — put an external reverse proxy in front that routes:

  • / → Discourse

  • /tickets → my custom app

Discourse remains unaware of /tickets entirely.
So look at this, right: Serve Discourse from a subfolder (path prefix) instead of a subdomain

I think the external nginx is the easiest way to do it. It would be possible to make a template that allowed the internal one to to it, but it’s more complicated and, I think, for little gain.

Yeah, but you don’t need to make any changes to the discourse container other than removing the ssl/let’s encrypt templates and maybe using a socket. (So, really, not much in that topic is of much help.)

I have got it working!

Thank you both for your replies!

“Tickets” link I added to the Discourse header works as well, when set to blank NOT self, as /tickets is then treated as a Discourse route instead and tries to just swap the react view. Blank forces a full page reload.

If you want to fix that, I’m pretty sure a theme component will do it by adding a route fror /tickets and telling it to do . . . something (I’m not great at Ember). Also, I think that you might add another subdomain to your Discourse server (like tickets.mydomain.com) using DISCOURSE_HOST_ALIASES and then link to that domain, which would then do a redirect, so Ember wouldn’t try to snag the route.