Discourse with jwilder/nginx-proxy howtos?

Hi All

I would like to deploy Discourse behind nginx-proxy (https://github.com/jwilder/nginx-proxy). This seems to be a pretty popular proxy and I already have it working with other sites and its Letsencrypt companion.

There’s small bits of information scattered all over the forum, but nothing concrete, whether it has been done and detailed ‘how’ it has been done.

Anything that would help would be really appreciated.

Thanks!

It looks like it should “just work”. You’ll need to change the Discourse port number by editing app.yml by hand (and, obviously, don’t enable Let’s Encrypt in app.yml).

Give it a try and let us know.

Thanks, @pfaffman. I tried that. No luck. I get something like this, when checking my nginx-logs:

2018/08/14 14:11:25 [error] 8#8: *60 no live upstreams while connecting to upstream, client: {my_client_ip}, server: {myapp.mydomain.com}, request: “GET / HTTP/1.1”, upstream: “http://{myapp.mydomain.com}”, host: “{myapp.mydomain.com}”

It seems to be a common thing, as far as I can tell, based on pieces of info I could scrounge up.

It’s almost as if all the VIRTUAL_HOST, _PORT, etc. are not getting recognised or Discourse is not making it’s port available. However, the companion does generate valid SSL certs, so I am guessing environment variables do get recognised. Thus leaning towards something to do with how ports are exposed via Discourse.

Hi, I get the same troubles as @confused
It seems that it doesn’t take care of VIRTUAL_HOST environment.
Moreover, why can’t we set the “ports” parameters as exposed by docker-compose ?
If anyone else has a workaround, that would save a lot of time…
Thank you.

I have shared various files of mine below with personal settings obfuscated. My setup is as follows.

  • Two Ubuntu 18.04 servers running Nginx: Server1, Server 2
  • Both servers are running Nginx v. 1.14.0 with LetsEncrypt SSL certificates.
  • Both servers are using CSF as a software-level firewall.
  • The main domain is listed as domain.com and Discourse runs on www.domain.com.
  • All traffic is forced through Server1 which then proxies to Server2.
  • Server1 uses a static IP address. Server2 uses a dynamic IP address.
  • Discourse utilizes a web socket
  • I do have other content running on the server.
  • I am not using a CDN. I do have CloudFlare, but it is only being utilized for DNS.

Various of my config files can be condensed, separating them just cleans up the files and allows for code reuse. I do have other config files, most of which are given in the list; I have not included them as they do not affect Discourse’s function (e.g. restrictions.conf).

While this does not use the automated proxy given in the OP, you can dissect the following configuration files for whatever you might need to add (or run it without the automated proxy). I have had to slowly tackle various problems I’ve run into over my time running Discourse; at the given moment, I am not aware of any outstanding errors being recorded to my logs.

I’m happy to provide whatever information I can. I had worked as a Linux Systems Administrator for a couple of years and am happy to provide what knowledge I can.

I hope this helps!

Server #1 nginx.conf
user www-data;
worker_processes auto;
pid /var/run/nginx.pid;

include /etc/nginx/modules-enabled/50-mod-http-geoip.conf;

error_log /var/log/nginx/error.log info;

events {
	worker_connections 1024;
	multi_accept on;
	use epoll;
}

worker_rlimit_nofile 30000;

http {

map $remote_addr $ip_anonym1 {
default 0.0.0;
"~(?P<ip>(\d+)\.(\d+)\.(\d+))\.\d+" $ip;
"~(?P<ip>[^:]+:[^:]+):" $ip;
}

map $remote_addr $ip_anonym2 {
default .0;
"~(?P<ip>(\d+)\.(\d+)\.(\d+))\.\d+" .0;
"~(?P<ip>[^:]+:[^:]+):" ::;
}

map $ip_anonym1$ip_anonym2 $ip_anonymized {
default 0.0.0.0;
"~(?P<ip>.*)" $ip;
}

log_format anonymized '$ip_anonymized - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';

	server_names_hash_bucket_size 64;
	upstream php-handler {
		server unix:/run/php/php7.2-fpm.sock;
	}

	# Cloudflare IPs
        set_real_ip_from 204.93.240.0/24;
        set_real_ip_from 204.93.177.0/24;
        set_real_ip_from 199.27.128.0/21;
        set_real_ip_from 173.245.48.0/20;
        set_real_ip_from 103.21.244.0/22;
        set_real_ip_from 103.22.200.0/22;
        set_real_ip_from 103.31.4.0/22;
        set_real_ip_from 141.101.64.0/18;
        set_real_ip_from 108.162.192.0/18;
        set_real_ip_from 190.93.240.0/20;
        set_real_ip_from 188.114.96.0/20;
        set_real_ip_from 197.234.240.0/22;
        set_real_ip_from 198.41.128.0/17;
        set_real_ip_from 162.158.0.0/15;
        real_ip_header     CF-Connecting-IP;

	include /etc/nginx/mime.types;
	include /etc/nginx/conf.d/optimization.conf;
	default_type application/octet-stream;

	log_format main '$remote_addr - $remote_user [$time_local] "$request" '
	'$status $body_bytes_sent "$http_referer" '
	'"$http_user_agent" "$http_x_forwarded_for" '
	'"$host" sn="$server_name" '
	'rt=$request_time '
	'ua="$upstream_addr" us="$upstream_status" '
	'ut="$upstream_response_time" ul="$upstream_response_length" '
	'cs=$upstream_cache_status' ;

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

	client_max_body_size 30M;
	sendfile on;
	send_timeout 3600;
	tcp_nopush on;
	tcp_nodelay on;
	open_file_cache max=500 inactive=10m;
	open_file_cache_errors on;
	keepalive_timeout 120;
	reset_timedout_connection on;
	server_tokens off;
	resolver_timeout 10s;
	include /etc/nginx/conf.d/php_optimization.conf;
	include /etc/nginx/conf.d/proxy.conf;
	include /etc/nginx/conf.d/gateway.conf;
	include /etc/nginx/conf.d/letsencrypt.conf;
	include /etc/nginx/sites-enabled/*.vhost;
	include /etc/nginx/sites-enabled/*.conf;
}
Server #1 proxy.conf
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header Referrer "no-referrer";
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
proxy_redirect off;
proxy_buffering off;
proxy_cache_valid 5m;
Server #1 gateway.conf
server {

	listen 80 default_server;
	server_name domain.com www.domain.com forums.domain.com domain.org. domain.net;

	location ^~ /.well-known/acme-challenge {
		proxy_pass http://127.0.0.1:81;
		proxy_set_header Host $host;
	}

	location / {
		return 301 https://$host$request_uri;
	}
}
Server #1 letsencrypt.conf
server {
	listen 127.0.0.1:81 default_server;
	server_name 127.0.0.1; 
	include /etc/nginx/conf.d/proxy.conf;
	location ^~ /.well-known/acme-challenge {
		default_type text/plain;
		root /var/www/letsencrypt;
	}
}
Server #1 domain.com.conf
upstream discourse {	
	server server2.domain.com:443 fail_timeout=5; # This could also be an IP address; in this case, my proxy forwards to a dynamic IP address, so I set the upstream server to a domain name instead of an IP.
}

server {
	listen 80; listen [::]:80;
	server_name domain.com www.domain.com forums.domain.com;
	return 301 https://www.domain.com$request_uri;
}

server {
        listen 443 ssl http2;  listen [::]:443 ssl http2;
        server_name www.domain.com domain.com;

        ssl on;
        ssl_certificate      /etc/letsencrypt/live/domain.com/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/domain.com/privkey.pem;
	include /etc/nginx/conf.d/ssl.conf;
	include /etc/nginx/conf.d/header.conf;

        http2_idle_timeout 5m; # up from 3m default

        location / {
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $http_host;
		proxy_redirect off;
		# pass to the upstream discourse server mentioned above
		proxy_pass https://discourse;
	}

}
Server #1 ssl.conf
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
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:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK:!AES128';
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1;
ssl_stapling on;
ssl_stapling_verify on;
Server #1 header.conf
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin" always;

Server #2 nginx.conf
user www-data www-data;
worker_processes 4;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
# load_module modules/ngx_http_modsecurity_module.so;
# load_module modules/ngx_http_fancyindex_module.so;
#include modules/*.so;

load_module modules/ngx_http_auth_pam_module.so;
load_module modules/ngx_http_cache_purge_module.so;
load_module modules/ngx_http_dav_ext_module.so;
load_module modules/ngx_http_echo_module.so;
load_module modules/ngx_http_fancyindex_module.so;
load_module modules/ngx_http_geoip_module.so;
load_module modules/ngx_http_headers_more_filter_module.so;
load_module modules/ngx_http_image_filter_module.so;
#load_module modules/ngx_http_lua_module.so;
load_module modules/ngx_http_perl_module.so;
load_module modules/ngx_http_subs_filter_module.so;
load_module modules/ngx_http_uploadprogress_module.so;
load_module modules/ngx_http_upstream_fair_module.so;
load_module modules/ngx_http_xslt_filter_module.so;
load_module modules/ngx_mail_module.so;
load_module modules/ngx_nchan_module.so;
load_module modules/ngx_stream_module.so;


events {
	worker_connections 1024;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 180;
        types_hash_max_size 2048;
	include             /etc/nginx/mime.types;
	default_type        application/octet-stream;
	client_max_body_size 32M;
	autoindex off;

	##
	# Logging Settings
	##
	log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

	access_log  /var/log/nginx/access_log  main;
	error_log /var/log/nginx/error_log;

	##
	# SSL Settings
	##

	ssl_protocols TLSv1.2 TLSv1.3;

        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  20m;
        ssl_prefer_server_ciphers on;

        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';

        map $scheme $fastcgi_https {
                default off;
                https on;
        }

	proxy_ssl_session_reuse on;

	gzip on;
	gzip_disable "msie6";
        gzip_min_length 256;
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 5;
        gzip_types
                application/atom+xml
                application/javascript
                application/json
                application/ld+json
                application/manifest+json
                application/rss+xml
                application/vnd.geo+json
                application/vnd.ms-fontobject
                application/x-font-ttf
                application/x-web-app-manifest+json
                application/xhtml+xml
                application/xml
                font/opentype
                image/bmp
                image/svg+xml
                image/x-icon
                text/cache-manifest
                text/css
                text/plain
                text/vcard
                text/vnd.rim.location.xloc
                text/vtt
                text/x-component
                text/x-cross-domain-policy;

        proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=map:8m max_size=1g inactive=24h;

	upstream php-handler {
		server unix:/run/php/php7.2-fpm.sock;
	}

	upstream domain {
		server unix:/run/php/domain.sock;
	}

	upstream domain2 {
		server unix:/run/php/domain2.sock;
	}

	include /etc/nginx/conf.d/php_optimization.conf;
	#include /etc/nginx/conf.d/php-fpm.conf;
	#include /etc/nginx/conf.d/browser-caching.conf;
	include /etc/nginx/conf.d/proxy.conf;
	#include /etc/nginx/conf.d/gdpr.conf;
	#include /etc/nginx/conf.d/gateway.conf;
	#include /etc/nginx/conf.d/letsencrypt.conf;
	#include /etc/nginx/conf.d/site-*.conf;
	#include /etc/nginx/conf.d/restrictions*.conf;
	include /etc/nginx/sites-enabled/*;

	server_names_hash_bucket_size 64;

	# Proxy Server IPs # this should be the IP of your reverse proxy server, or server 1
	set_real_ip_from ip.ad.dr.ess;

	# Cloudflare IPs
	set_real_ip_from 204.93.240.0/24;
	set_real_ip_from 204.93.177.0/24;
	set_real_ip_from 199.27.128.0/21;
	set_real_ip_from 173.245.48.0/20;
	set_real_ip_from 103.21.244.0/22;
	set_real_ip_from 103.22.200.0/22;
	set_real_ip_from 103.31.4.0/22;
	set_real_ip_from 141.101.64.0/18;
	set_real_ip_from 108.162.192.0/18;
	set_real_ip_from 190.93.240.0/20;
	set_real_ip_from 188.114.96.0/20;
	set_real_ip_from 197.234.240.0/22;
	set_real_ip_from 198.41.128.0/17;
	set_real_ip_from 162.158.0.0/15;
	real_ip_header     CF-Connecting-IP;

}
Server #2 proxy.conf
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header Referer "no-referrer";
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
proxy_redirect off;
Server #2 gateway.conf
server {

        listen 80 default_server;
        server_name server1.domain.com domain.com www.domain.com forums.domain.com domain.org domain.net;

        location ^~ /.well-known/acme-challenge {
                proxy_pass http://127.0.0.1:81;
                proxy_set_header Host $host;
        }

        location / {
                return 301 https://$host$request_uri;
        }
}
Server #2 letsencrypt.conf
server {
	listen 127.0.0.1:81 default_server;
	server_name 127.0.0.1; 
	include /etc/nginx/conf.d/proxy.conf;
	location ^~ /.well-known/acme-challenge {
		default_type text/plain;
		root /var/www/letsencrypt;
	}
}
Server #2 domain.com.conf
server {
	listen 80; listen [::]:80;
	server_name domain.com www.domain.com forums.domain.com;

	return 301 https://www.domain.com$request_uri;
}

server {
	listen 443 ssl http2; listen [::]:443 ssl http2;
	server_name domain.com;
        ssl_certificate      /etc/letsencrypt/live/domain.com/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/domain.com/privkey.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;
        ssl_prefer_server_ciphers on;

	return 301 https://www.domain.com$request_uri;
}

server {
	listen 443 ssl http2;  listen [::]:443 ssl http2;
	server_name www.domain.com;

	ssl on;
	ssl_certificate      /etc/letsencrypt/live/domain.com/fullchain.pem;
	ssl_certificate_key  /etc/letsencrypt/live/domain.com/privkey.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;
	ssl_prefer_server_ciphers on;

	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;
		#proxy_read_timeout 180;
    }
}
Server #2 app.yml
## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed

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"
#  - "templates/web.letsencrypt.ssl.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
## Uncomment these two lines if you wish to add Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"
  #- "templates/cloudflare.template.yml"
 
## which TCP/IP ports should this container expose?
## If you want Discourse to share a port with another webserver like Apache or nginx,
## see https://meta.discourse.org/t/17247 for details
expose:
  - "2222:22" # ssh

params:
  db_default_text_search_config: "pg_catalog.english"
  upload_size: 30m

  ## Set db_shared_buffers to a max of 25% of the total memory.
  ## will be set automatically by bootstrap based on detected RAM, or you can override
  #db_shared_buffers: "256MB"
  
  ## can improve sorting performance, but adds memory usage per-connection
  #db_work_mem: "40MB"
  
  ## Which Git revision should this container use? (default: tests-passed)
  Version: tests-passed
  #version: stable

env:
  LANG: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en

  ## How many concurrent web requests are supported? Depends on memory and CPU cores.
  ## will be set automatically by bootstrap based on detected CPUs, or you can override
  #UNICORN_WORKERS: 3

  ## TODO: The domain name this Discourse instance will respond to
  DISCOURSE_HOSTNAME: 'www.domain.com'
  
  ## Uncomment if you want the container to be started with the same
  ## hostname (-h option) as specified above (default "$hostname-$config")
  DOCKER_USE_HOSTNAME: false

  ## TODO: List of comma delimited emails that will be made admin and developer
  ## on initial signup example 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: 'yourpersonalemail@domain.com'

  ## TODO: The SMTP mail server used to validate new accounts and send notifications
  DISCOURSE_SMTP_ADDRESS: mailserver.domain.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: myserveremail@domain.com
  DISCOURSE_SMTP_PASSWORD: yourpasswordhere
  DISCOURSE_SMTP_ENABLE_START_TLS: true
  
  ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate
  LETSENCRYPT_ACCOUNT_EMAIL: yourletsencryptemail@domain.com

  ## The CDN address for this Discourse instance (configured to pull)
  ## see https://meta.discourse.org/t/14857 for details
  #DISCOURSE_CDN_URL: //discourse-cdn.example.com
## The Docker container is stateless; all data is stored in /shared
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

## Plugins go here
## see https://meta.discourse.org/t/19157 for details
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - mkdir -p plugins
          - git clone https://github.com/discourse/docker_manager.git
          # Your plugins here

## Any custom commands to run after building
run:
1 Like

I use jwilder/nginx-proxy with discourse just fine.

What I did was to add the following stuff to my app.yml file:

VIRTUAL_HOST: domain.com
VIRTUAL_PORT: 80
LETSENCRYPT_HOST: domain.com
LETSENCRYPT_EMAIL: some@email.com

My ports are like this, but I don’t remember anymore why… I think it avoid discourse complaining about host 80 port is being used :

expose:
  - "127.0.0.1:3040:80"`

Make sure your container is connected to the nginx network with:

docker_args:
  - "--network=name-of-your-network"

To fix discourse not getting the real IP from the host nginx, add this to your app.yml:

run:
  - replace:
      filename: /etc/nginx/conf.d/discourse.conf
      from: "types {"
      to: |
        set_real_ip_from 172.18.0.0/24;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        types {

Make sure the network there matches the subnet of your nginx docker network. You can check with docker inspect nginx-proxy and the ip in the command above will always be your gateway but replace last digit for 0.

So for me my nginx-proxy gateway is 172.18.0.1 so that’s why I have it as 172.18.0.0

3 Likes

Thanks, @vitorl!

This is what I was missing. Thanks.

Real IP snippet is really helpful too.

3 Likes

@vitorl @confused

Could one of you show me your docker-compose file? I am having trouble getting this working. Here is mine:

## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
## Uncomment these two lines if you wish to add Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

## which TCP/IP ports should this container expose?
## If you want Discourse to share a port with another webserver like Apache or nginx,
## see https://meta.discourse.org/t/17247 for details
expose:
  - "172.17.0.1:8080:80"   # http
  - "172.17.0.1:4434:443" # https

params:
  db_default_text_search_config: "pg_catalog.english"

  ## Set db_shared_buffers to a max of 25% of the total memory.
  ## will be set automatically by bootstrap based on detected RAM, or you can override
  db_shared_buffers: "768MB"

  ## can improve sorting performance, but adds memory usage per-connection
  #db_work_mem: "40MB"

  ## Which Git revision should this container use? (default: tests-passed)
  #version: tests-passed

env:
  LANG: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en

  ## How many concurrent web requests are supported? Depends on memory and CPU cores.
  ## will be set automatically by bootstrap based on detected CPUs, or you can override
  UNICORN_WORKERS: 4

  ## TODO: The domain name this Discourse instance will respond to
  ## Required. Discourse will not work with a bare IP number.
  DISCOURSE_HOSTNAME: discourse.bakeitcookbook.com

  ## Uncomment if you want the container to be started with the same
  ## hostname (-h option) as specified above (default "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: List of comma delimited emails that will be made admin and developer
  ## on initial signup example 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: 'bkalashian@gmail.com'

  ## TODO: The SMTP mail server used to validate new accounts and send notifications
  # SMTP ADDRESS, username, and password are required
  # WARNING the char '#' in SMTP password can cause problems!
  DISCOURSE_SMTP_ADDRESS: smtp.mailgun.org
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: ...
  DISCOURSE_SMTP_PASSWORD: ...
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (optional, default true)

  ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate
  #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com

  ## The CDN address for this Discourse instance (configured to pull)
  ## see https://meta.discourse.org/t/14857 for details
  #DISCOURSE_CDN_URL: //discourse-cdn.example.com

## The Docker container is stateless; all data is stored in /shared
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

environment:
  - "VIRTUAL_PORT:80"
  - "VIRTUAL_HOST:discourse.bakeitcookbook.com"
  - "LETSENCRYPT_HOST:discourse.bakeitcookbook.com"
  - "LETSENCRYPT_EMAIL:bkalashian@gmail.com"

## Plugins go here
## see https://meta.discourse.org/t/19157 for details
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

docker_args:
  - "--network=nginx-proxy"

## Any custom commands to run after building
run:
  - exec: echo "Beginning of custom commands"
  ## If you want to set the 'From' email address for your first registration, uncomment and change:
  ## After getting the first signup email, re-comment the line. It only needs to run once.
  #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'"
  - exec: echo "End of custom commands"i
  - replace:
      filename: /etc/nginx/conf.d/discourse.conf
      from: "types {"
      to: |
        set_real_ip_from 172.17.0.0/24;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        types {

What am I doing wrong?

Here is mine, beware that I have a lot of custom settings, so it may not work out of the box for you.

This is my app.yml

## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"

expose:
  - "127.0.0.1:3030:80"  

docker_args:
  - "--network=name-of-your-docker-network"
  - "--sysctl net.core.somaxconn=4096"

params:
  db_default_text_search_config: "pg_catalog.english"
  db_work_mem: "40MB"
 
env:
  LANG: en_US.UTF-8

  VIRTUAL_HOST: redacted.app,www.redacted.app,redacted.com.br,www.redacted.com.br
  VIRTUAL_PORT: 80
  LETSENCRYPT_HOST: redacted.app,www.redacted.app,redacted.com.br,www.redacted.com.br
  LETSENCRYPT_EMAIL: email@email.com

  DISCOURSE_HOSTNAME: redacted.app
  DISCOURSE_DEVELOPER_EMAILS: 'email@email.com'

  DISCOURSE_SMTP_ADDRESS: smtp.sendgrid.net
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: apikey
  DISCOURSE_SMTP_PASSWORD: "password"
  DISCOURSE_SMTP_ENABLE_START_TLS: true
  SSL_POLICY: Mozilla-Modern         

volumes:
  - volume:
      host: /opt/discourse/data
      guest: /shared
  - volume:
      host: /opt/discourse/data/log
      guest: /var/log

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/discourse/discourse-sitemap.git
          - git clone https://github.com/discourse/discourse-solved.git
          - git clone https://github.com/angusmcleod/discourse-locations.git
          - git clone https://github.com/angusmcleod/discourse-events.git

  after_web_config:
    - replace:
        filename: /etc/nginx/nginx.conf
        from: /sendfile.+on;/
        to: |
          server_names_hash_bucket_size 64;
          sendfile on;
    - file:
        path: /etc/nginx/conf.d/discourse_redirect_1.conf
        contents: |
          server {
            server_name www.redacted.app www.redacted.com.br redacted.com.br;
            return 301 https://redacted.app$request_uri;
          }

run:
  - replace:
      filename: /etc/nginx/conf.d/discourse.conf
      from: "types {"
      to: |
        set_real_ip_from 172.18.0.0/24;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        types {

Do not copy and paste. You have to change some stuff, this also assumes you are using sendgrid. You need to replace the domains, email, sendgrid apikey password.

Good luck.

1 Like

For some reason my real ip snippet is not working anymore. Not sure if it’s because of cloudflare CDN. do you have cloudflare and is it working with you?

If the orange cloud is on then try turning it off does that solve the problem?

You can’t use Cloudflare caching or optimisations on discourse code anyhow, so it’s of limited value.

1 Like

I use Cloudflare to hide my server IP and won’t disable it, even if it means not getting the real IP’s.

What I have noticed is that if I use the Cloudflare template I see my docker IP in the access.log, regardless of having this snippet or not:

  - replace:
      filename: /etc/nginx/conf.d/discourse.conf
      from: "types {"
      to: |
        set_real_ip_from 172.18.0.0/24;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        types {

If I remove the Cloudflare template and use just the snippet above I see Cloudflare IP’s on my logs.