Running other websites on the same machine as Discourse


(Kane York) #1

If you want to run other websites on the same machine as Discourse, you need to set up an extra NGINX or HAProxy proxy in front of the Docker container.

If you have not already, please read the Advanced Troubleshooting with Docker guide, as it covers the basics on the separation between host and container.

This guide assumes you already have Discourse working - if you don’t, it may be hard to tell whether or not the configuration is working.

Install nginx outside the container

First, make sure the container is not running:

cd /var/discourse
./launcher stop app

Then install nginx from its PPA (Ubuntu ships by default a very old version, 1.4.0):

sudo add-apt-repository ppa:nginx/stable -y
sudo apt-get update && sudo apt-get install nginx

Change the container definition

This is where we change how Discourse actually gets set up. We don’t want the container listening on ports - instead, we’ll tell it to listen on a special file.

Change your /var/discourse/containers/app.yml to look like this:

# base templates used; can cut down to include less functionality per container templates:
  - "templates/cron.template.yml"
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/sshd.template.yml"
  - "templates/web.template.yml"
  # - "templates/web.ssl.template.yml" # remove - https will be handled by outer nginx
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"  # <-- Added
# which ports to expose?
# expose: comment out entire section

Be sure to remove the next line containing

- "80:80" # fwd host port 80 to container port 80 (http)

Create an NGINX ‘site’ for the outer nginx

For an HTTP site, put this in /etc/nginx/sites-enabled/discourse.conf, making sure to change the server_name:

server {
	listen 80; listen [::]:80;
	server_name forum.example.com;  # <-- change this

	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 $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
	}
}

For an HTTPS site, make /etc/nginx/sites-enabled/discourse.conf look like this:

server {
    listen 80; listen [::]:80;
    server_name forum.example.com;  # <-- change this

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;  listen [::]:443 ssl http2;
    server_name forum.example.com;  # <-- change this

    ssl on;
    ssl_certificate      /var/discourse/shared/standalone/ssl/ssl.crt;
    ssl_certificate_key  /var/discourse/shared/standalone/ssl/ssl.key;
    ssl_dhparam          /var/discourse/shared/standalone/ssl/dhparams.pem;
    ssl_session_tickets off;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;

    http2_idle_timeout 5m; # up from 3m default

    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 $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Make sure that the default site is either disabled or has the correct server_name set.

Then, in a shell:

# Make sure that Discourse isn't running
/var/discourse/launcher stop app || true

# test configuration
sudo nginx -t
# Important: If nginx -t comes back with an error, correct the config before reloading!
sudo service nginx reload

# Rebuild the container to apply changes
/var/discourse/launcher rebuild app

Create your other sites

You’re done with the Discourse section!

Make other NGINX “sites”, then link and enable them, as in the last step above.


Port already in use, what to do?
Run Discourse with or alongside existing Apache sites?
Best way to install Discourse on my server?
Multisite configuration with Docker
How can I create a seperate home page along with discourse as my main website?
Configuring nginx to work alongside Discourse
Can't make Discourse accessible through Apache
Faster rebuilds?
Container rebuild is failing with permission error
Take down site during maintenance
Installing Discourse Under Nginx
Which performance is better if discourse or phpbb hosts on a same vps?
Custom avatars not showing up in preferences
All of my internal users show as coming from 127.0.0.1!
Discourse and Apache using same ports
[Solved] Help setting other websites on the same DigitalOcean droplet
Error: listen tcp 0.0.0.0:80: bind: address already in use
Wordpress and Discourse in a single droplet
Cannot start container -- Port 80 already in use
Discourse not showing up at specified hostname
Success - New Multisite Install on Dedicated server using ServerPilot, Nginx and Apache
Error in installing
Fix for "Logjam" vulnerability is deployed; requires rebuild
Launcher rebuild app failing: repository name must be lowercase [SOLVED]
[Solved] Dev instance with nginx: topic preview not working
Discourse doesn't deliver webpages, fresh install on Linode Ubuntu 14.04
Installation on Ubuntu 14.04 LTS + Apache 2 + Plesk 12
Discourse delivering blank web pages on Ubuntu 14 on Linode
Disable direct access with port (nginx)
Webhooks/Sidekiq issue on dev instance
Problems accessing uploaded files
Discouse passes localhost uri as oauth redirect_uri
Create another website along with Discourse on same server
Is Digital Ocean $10/month Package Good for Discourse?
Wordpress and discourse on the same server
How to install wordpress & discourse on the same server?
Hosting wordpress, discourse, and a php app on the same server
What is the recommended practice for WordPress and Discourse on the same server?
NoMethodError (undefined method `title' for nil:NilClass)
Using nginx alongside the Docker install
Redirecting old forum URLs to new Discourse URLs
Can I download Discourse and install it manually on my server?
Javascript Errors - looks like files are truncated
Broken images inside posts
SSL on Apache Server
Nginx + discourse
Nginx + discourse
Discourse with zpanel
NGINX proxy in front of the Docker container.errors
Backup uploads terribly slow
How to install wordpress in the Secondary directory?
How should I enable letsencrypt while discourse is beside other websites
Discourse on subdomain
Deploying discourse on server already running another rails app
I got a problem when starting ./launcher start app
I got a problem when starting ./launcher start app
I need help with my nginx settings for multiple sites
Apache webserver already running
How to upload files in root directory?
Running Discourse out of a folder, on a server running Wordpress with Apache, with SSL support
WordPress, Discourse and Local SMTP Server
Default app.yml file anywhere?
502 Bad gateway error after switching to SSL
How to set domain 301 redirects?
Wrong sending domain used
Wrong sending domain used
Wordpress integration with mini forums
Discourse + VestaCP
How can one install a panel with discourse?
A better "site not available" page
Bad gateway when trying to use SSL
Adding an offline page when rebuilding
Adding an offline page when rebuilding
Accessing to the database from outside the container
Verification link not working
How to change the port from docker - help
How to get into nginx for a digitalocean 1-click setup?
Installation problem with Discourse
Docker upgrade prevents access to Discourse on Digital Ocean
Discourse with Nginx proxy | error
Discourse stopped working after installing Plesk
Problem with discourse in a subfolder
Unable to get nginx to work together
Discourse blocking with password reset
Rate Limiting when behind Nginx Proxy
Twitter Login Redirect Is Incorrect And Uses Port
Twitter Login Redirect Is Incorrect And Uses Port
Twitter Login Redirect Is Incorrect And Uses Port
SSL with Apache reverse proxy and docker container
Installing wp in sub folder (https-docker-setup)
Installing wp in sub folder (https-docker-setup)
Unable to start docker service (latest step)
Difference between socket- and port-based connection to outer NGINX?
Nginx configuration for discourse
Nginx configuration for discourse
Advanced Troubleshooting with Docker
Nginx configuration for discourse
Custom Layouts Plugin
Setting up Let's Encrypt
Setting up Let's Encrypt
Setting up Discourse on a VPS with other sites
Setting up Discourse on a VPS with other sites
How to move my Discourse instance LOCALLY? Or at least the big files?
Put forum under sub-domain and have website
Discourse + Docker on existing VM with PHP site
Port already in use, what to do?
Can Discourse ship frequent Docker images that do not need to be bootstrapped?
Can Discourse ship frequent Docker images that do not need to be bootstrapped?
All avatar uploads are broken
Discourse with ec2 doubt
Embedding comments via JS not working
Starting a second Discourse forum on the same VPS
ERR_TOO_MANY_REDIRECTS After Rebuild
IP Spoofing Attack
Discourse with apache port problem
Discours + Direct Admin (VPS)
Preview pane uses local docker container IP address
E-mail didn't delivered in the easyengine(nginx) proxy mandrill environment
Error while doing discourse setup
Discourse and Wordpress help
Run other php script
Any difference with installation via ssh or bitnami?
Can't relay Discourse traffic for website with nginx and vestacp
Installation on v-server as a subfolder with other services in subfolders using apache
Installation on v-server as a subfolder with other services in subfolders using apache
Installation on v-server as a subfolder with other services in subfolders using apache
Docker0 Missing (OpenVZ)
Sandbox and test discourse on host?
NGINX proxy in front of the Docker container.errors
Discourse not working through separate nginx docker
Error install. Help please!
How do I do what I did in htaccess in nginx?
How do I do what I did in htaccess in nginx?
Advice on integrating into a Docker-Compose setup?
How to install discourse on hosting?
URLs for avatars & emoticons adding :80 to the end of the domain
CDN causes white screen
Nginx configuration for discourse
One server and 2x discourse forum?
429 too many connections issue with NGINX in front of NGINX
Discourse must be the only software on the server?
IP adress for two web servers
Generals Subfolder recommendations and tips
Problems rebooting due to Nginx taking port 80
Custom Intro page for discourse
All domain name redirect to my Discourse installation
Multisite configuration with Docker
Setting up nginx for use with Discourse
Multiple Standalone containers instead of multisite?
Issues with SSL termination and Docker
How should I enable letsencrypt while discourse is beside other websites
How to Docker+Discourse and Apache too?
Help understanding the structure of using discourse + static website
Installing docker on an already running web server?
Using Discourse With Other Sites on Same Droplet
Discourse with PHP main page
403 error along with CSRF token warning
403 forbidden after installation
Using the docker container ./launcher script how would I rebuild the web_only container before taking the old one offline?
(Jeff Atwood) #4

@Lee_Ars knows about this!


(Lee_Ars) #5

I am summoned!

I suggest a classic tech support technique: say less to appear smarter. Instead of actually giving an iptables rule, simply suggest firewalling the port. iptables is a tool for wizards and incorrect incantations can lead to disaster.

That being said, your syntax looks fine and when I executed it it showed up just fine in my ruleset, and it appears to work A-OK.

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
DROP       tcp  -- !localhost            anywhere             tcp dpt:4001

Are you seeing anomalous behavior, or is your version of iptables rejecting the command?


(Kane York) #6

Yeah - I was still able to connect with my browser to forum.riking.org:4001. You can too - https://forum.riking.org:4001/

Here’s iptables -L:

root@forum:/var/docker# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
DROP       tcp  -- !localhost            anywhere             tcp dpt:4001

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     tcp  --  anywhere             172.17.0.32          tcp dpt:https
ACCEPT     tcp  --  anywhere             172.17.0.32          tcp dpt:ssh
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

(Lee_Ars) #7

That’s seriously funky. It appears to work OK here, but I’m not testing it directly with Discourse. Rather, I’m setting up a new nginx vhost listening on port 4001. If I hit http://testsite:4001 without the rule, I get my index page. If I add that exact rule:

iptables -A INPUT ! -s localhost -p tcp --dport 4001 -j DROP

…and try http://testsite:4001 again, my web browser times out.

I modified things slightly so I could log exactly what was happening, first deleting the INPUT chain entry and then doing the following:

iptables -N LOGGING #  creates new LOGGING chain
iptables -A INPUT ! -s localhost -p tcp --dport 4001 -j LOGGING # Send matching to LOGGING chain
iptables -A LOGGING -j LOG --log-prefix "iptables drop: " --log-level 7 # Log matches to syslog at debug
iptables -A LOGGING -j DROP

After this, iptables -L shows:

Chain INPUT (policy ACCEPT)
target     prot opt source       destination         
LOGGING    tcp  -- !localhost    anywhere     tcp dpt:4001
...
Chain LOGGING (1 references)
target     prot opt source       destination         
LOG        all  --  anywhere     anywhere     LOG level debug prefix "iptables drop: "
DROP       all  --  anywhere     anywhere         

When I hit http://testsite:4001, I get this in the web server’s syslog:

Jul  8 21:22:28 frylock kernel: [560241.538879] iptables drop: IN=eth0 OUT= ... ID=8899 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:28 frylock kernel: [560241.776309] iptables drop: IN=eth0 OUT= ... ID=39633 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:28 frylock kernel: [560241.788826] iptables drop: IN=eth0 OUT= ... ID=18984 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560241.977224] iptables drop: IN=eth0 OUT= ... ID=63376 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.078010] iptables drop: IN=eth0 OUT= ... ID=3375 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.078032] iptables drop: IN=eth0 OUT= ... ID=27902 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.279139] iptables drop: IN=eth0 OUT= ... ID=22562 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.279160] iptables drop: IN=eth0 OUT= ... ID=10038 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.380119] iptables drop: IN=eth0 OUT= ... ID=40944 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.380140] iptables drop: IN=eth0 OUT= ... ID=61915 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.581197] iptables drop: IN=eth0 OUT= ... ID=31518 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.682155] iptables drop: IN=eth0 OUT= ... ID=18376 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:29 frylock kernel: [560242.783082] iptables drop: IN=eth0 OUT= ... ID=23128 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:30 frylock kernel: [560243.084435] iptables drop: IN=eth0 OUT= ... ID=53867 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:30 frylock kernel: [560243.185469] iptables drop: IN=eth0 OUT= ... ID=60692 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:30 frylock kernel: [560243.587670] iptables drop: IN=eth0 OUT= ... ID=32847 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:31 frylock kernel: [560244.089174] iptables drop: IN=eth0 OUT= ... ID=25728 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:31 frylock kernel: [560244.492741] iptables drop: IN=eth0 OUT= ... ID=35734 DF PROTO=TCP SPT=62673 DPT=4001 ... 
Jul  8 21:22:32 frylock kernel: [560245.800052] iptables drop: IN=eth0 OUT= ... ID=6454 DF PROTO=TCP SPT=62672 DPT=4001 ... 
Jul  8 21:22:33 frylock kernel: [560246.202932] iptables drop: IN=eth0 OUT= ... ID=11195 DF PROTO=TCP SPT=62673 DPT=4001 ... 

(Trimmed the lines some, but it still wraps, eh.)

It definitely appears to be dropping packets for me! Just to clarify, this is with me configuring and running iptables on the metal OS, not inside of docker or anything like that.


(Kane York) #8

Hmm, maybe the Docker-inserted rules in the FORWARD chain are interfering?


(Lee_Ars) #9

I don’t think they would. The INPUT chain will apply to packets with your server as the destination; FORWARD should only get invoked for packets whose destination isn’t the server.

You could always strip them out real quick, but it shouldn’t make a bit of difference. I had the same ones in place on the same box (I just snipped them out in the code window above).

Maybe enable logging and see what you’re getting?


(Kane York) #10

I’m leaving the Firewall section blank for now… I can’t figure it out.

Going to release this guide now - moving to public.


(Jens Maier) #11

You guys are forgetting that the Unicorn in the docker container is not binding to any device visible to the host. It is binding to a veth virtual ethernet device which that lives only in the container. The other end of this virtual device lives in the host, where Docker adds it to a ethernet bridge device. The host and container communicate with one another via this virtual network using randomly chosen IP addresses in the 172.16/12 range. When you expose a port, Docker sets up masquerading and destination NAT in the host’s firewall to redirect network traffic to the container by routing it through the virtual network devices.

Soooo… yes, traffic to the container needs to be filtered in the FORWARD chain.
Also, the most interesting firewall rules docker creates are in different tables; use iptables -t nat -L and iptables -t mangle -L to get an idea of what Docker is doing behind the scenes.

However, the better solution would be to find a way to get Docker to assign a static configured private IP address to the container Discourse is running in, and then point Nginx directly at 172.1x.y.z:4001 instead. This would allow you to remove the exposed port altogether, and since the host does not by default route anything to the containers, firewalling becomes a non-issue.


(Sam Saffron) #12

I really don’t see the big drama in having a double proxy for these cases … just treat Discourse in the container as an endpoint and have NGINX / varnish / haproxy or whatever sit in front and route traffic.


(Jens Maier) #13

That’s not what I meant. What I meant was that you could (should?) avoid the roundtrip through masquerading and DNAT…


Fresh install does not work
(Sam Saffron) #14

I follow, you can also avoid this entire issue by simply exposing a socket from NGINX in the container in a public volume and then proxying that on local. Totally bypasses networking.


(Ova Light) #15

The problem is that I have applied this, in order to link my discourse outside of docker’s nginx but the twitter problem still persists.

Here is my code:

server {
listen 80;
server_name example.com;



location / {
	proxy_pass http://0.0.0.0:4001;
}

}

and it does the job correctly, but not for twitter auth. Any advice?


Why Discourse redirect to dokuwiki?
(Kane York) #16

You’re missing this line:

 proxy_set_header Host $http_host;

(David García-Navas) #17

So, could i run on a same 2GB RAM DigitalOcean droplet a little Discourse forum (20 people) + Wordpress (personal blog) + TinyTinyRSS + SemanticScuttle + WallaBag following this steps?

I’m thinking of leaving my current hosting and take it all to Digital Ocean.


(Jeff Atwood) #18

It should work provided you set up a swap file (as the guide recommends at the 1 GB RAM level) to cover you “just in case”. Our memory requirements are less than they used to be.


(ben_a_adams) #19

Continuing the discussion from Improve Discourse’s pagespeed score?:

Did you have any issues with compression being passed through with this config?

If I go direct all is well, if I go via ngnix compression is off for some things. Having a dig around ngnix proxy options to see if its loosing headers.


(Sam Saffron) #20

Have a look at our nginx config, it is very thorough discourse/nginx.sample.conf at master · discourse/discourse · GitHub


(ben_a_adams) #21

Thank you, that helped eliminate the impossible, as everything is correct and enabled me to look tangentially. The answer being, that http 1.0 doesn’t support gzip and proxy_http_version defaults to 1.0 [1]

Adding a proxy_http_version line to the ngnix reverse proxy resolves this (see below)

 location / {
            proxy_pass http://0.0.0.0:4001;
            proxy_set_header Host $http_host;
            proxy_http_version 1.1;
}

Will edit the wiki at top* (*new feature encoutered)


As an aside I have also added proxy_buffering off; not sure if its a good or bad thing so can’t advise either way


How to install wordpress & discourse on the same server?
(Zane Beckman) #22

Couldn’t the docker instance just bind to localhost or 127.0.0.1, instead of 0.0.0.0 (all interfaces, including public interfaces), eliminating the need for a firewall?


Edit: After a bit of research, I’ve found that this can be done quite easily when launching docker: docker run -p 127.0.0.1:80:80 server.

The most elegant way to accomplish this is probably to patch discourse_docker to allow specifying a binding address in app.yml