nginx proxy_pass, docker-compose, two different containers, same domain - nginx

I am having trouble setting up an nginx reverse proxy to serve content from different containers depending on the URI.
I have three docker images: (1) a flask app that serves a website (2) a bokeh app that serves a dashboard, (3) nginx to direct external requests to either (1) or (2). I build and run the stack using docker-compose. After running the stack (docker-compose -f docker-compose-local.yaml up):
I can, as expected, access (1) flask app via (3) nginx at localhost:1337.
I can, as expected, access (2) bokeh app directly at localhost:8002
I cannot access (2) the bokeh app via (3) nginx at localhost:1337/dboard
I would like the user to experience the dashboard as though it is part of the website, when on the backend the dashboard is in one container and all the other content is in a different container.
I have read a great many stackoverflow questions (e.g. here, here), serverfault questions (e.g. here, here), and blog posts, tried all manner of permutations of trailing slashes in the dashboard nginx location line and its proxy_pass, a number of proxy_set_header directives. I've gotten all sorts of "404 not found" and "502 bad gateway" responses, but so far no luck accessing the dashboard through nginx.
Any help much appreciated.
docker-compose-local.yaml:
version: '3.10'
services:
website:
image: website_image_name
build: .
command: gunicorn -b 0.0.0.0:8000 app_name:app
expose:
- 8000
env_file:
- ./app.env
dashboard:
image: timh-urban-vprm-dashboard:latest
command: mamba run --no-capture-output -n venv-urbanVPRM python urban_vprm_dashboard.py
expose:
- 8002
ports:
- 8002:8002
nginx:
image: nginx_image_name
build: ./nginx
ports:
- 1337:80
depends_on:
- website
- dashboard
links:
- website
- dashboard
nginx.conf:
upstream site {
server website:8000;
}
upstream dboard {
server dashboard:8002;
}
server {
listen 80;
server_name 0.0.0.0;
location / {
proxy_pass http://site;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /dboard/ {
proxy_pass http://dboard;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
}

Related

Traefik conf for server-side events

I'm trying to set a server-side event correctly with Traefik to no avail.
I have a Django server that has a URL that sends sse, I can proxy easily with nginx with this simple conf:
server {
listen 80;
server_name _ ;
charset utf-8;
client_max_body_size 20M;
location / {
proxy_pass http://django:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
In traefik using docker labels I set:
labels:
- "traefik.enable=true"
- "traefik.http.routers.dj.rule=Host(`$HOST_ADK`)"
- "traefik.http.routers.dj.entrypoints=web"
- "traefik.http.routers.dj.service=dj"
- "traefik.http.services.dj.loadbalancer.server.port=8000"
#- "traefik.http.routers.dj.middlewares=compress-no-stream"
#- "traefik.http.middlewares.compress-no-stream.compress=true"
#- "traefik.http.middlewares.compress-no-stream.compress.excludedcontenttypes=text/event-stream"
This resolve to internal server error. When proxying to django run with runserver (ie non Daphne that is used in the docker) I get a "Temporary failure in name resolution" and I cannot understand what it refers to.
Any hint on what's the correct configuration to make Server-Sent event work?
It turns out that Django's view that responded with the sse was setting 2 identical headers and that makes Traefik bristle.
Complete explanation on the issue.
Once fixed the headers everything worked.

Issue with setting up Nginx with multiple Docker containers

Currently I want to setup one server that has a Docker WordPress and Nginx that serves as a proxy in front. I would like in future to be able have multiple WordPress, NodeJS, ROR, etc, sitting behind this Nginx proxy.
When ever I try to connect to my server on port 80 I get a 403 forbidden.
I am able to build a Docker WordPress image and can connect to it on port 8080 on a remote PC.
Here is the compose.yml for my Docker WordPress:
version: "3.1"
services:
my_wordpress:
image: wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_PASSWORD: password
WORDPRESS_DB_HOST: my_mysql_wordpress
my_mysql_wordpress:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: password
This is the part when I try to build a Nginx container I am getting a 403 forbidden.
Nginx DockerFile:
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
Nginx compose.yml:
version: "2"
services:
web:
restart: always
image: nginx
ports:
- "80:80"
volumes:
- /path/in/vm/www:/usr/share/nginx/html
external_links:
- mywordpress_wordpress_1:mywordpress
networks:
default:
external:
name: mywordpress_default
Nginx nginx.conf:
http {
#...
upstream wordpress {
server mywordpress:8080;
}
#...
server {
listen 80;
server_name 192.168.1.124 test.me;
location / {
proxy_pass http://wordpress/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}
}
}
Now for me it would seem that this most likely has to do with my nginx.conf as I am still able to connect to my WordPress site on port 8080. As well as I stated I am also able to connect to my Nginx proxy and I don't see any errors when it launches.
Is what I'm trying to doing even possible or do I need to have the Nginx application sitting on the OS and not inside a docker container?
You are putting Nginx and Wordpress in 2 different compose files. If you are running then on same machines then
external_links:
- mywordpress_wordpress_1:mywordpress
Above would not work if you are on different machines. Also make sure the external link you are using the correct name by checking docker ps.
Also check the logs of your nginx container to see if it is showing in any error. Because the error log will give a pointer as to why a 403 is being thrown, and it could be that the proxy_pass is not able to connect to your wordpress server because of the way you have configured it.
If you are running these compose files on different machines then instead of external_links use extra_hosts
extra_hosts:
- "mywordpress:<IP of the wordpress machine>"
If I am right, You want to run multiple wordpress docker images and use nginx to reverse proxy to the wordpress instances. In that Use-case, The nginx should sit on your OS and not inside a docker image. That way, the nginx will have the ability to proxy to ports on your OS which are tied to the wordpress containers.

Docker nginx proxy to host

Short description:
Nginx running on docker, how to configure nginx so that it forwards calls to host.
Long description:
We have one web application which communicates to couple of backends (lets says rest1, rest2 and rest3). We are responsible for rest1.
Lets consider that I started rest1 manually on my pc and running on 2345 port. I want nginx (which is running in docker) to redirect all call torest1 to my own running instance(note, the instance is running on host, not any container and not in docker). And for rest2 and rest3 to some other docker node or may be some other server (who cares).
What I am looking for is:
docker-compose.yml configurations (if needed).
nginx configuration.
Thanks in advance.
Configure nginx like the following (make sure you replace IP of Docker Host) and save it as default.conf:
server {
listen 80;
server_name _;
location / {
proxy_pass http://<IP of Docker Host>;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Now bring up the container:
docker run -d --name nginx -p 80:80 -v /path/to/nginx/config/default.conf:/etc/nginx/conf.d/default.conf nginx
If you are using Docker Compose file version 3 you don't need any special config for docker-compose.yml file at all, just use the special DNS name host.docker.internal to reach a host service, as on the following nginx.conf example:
events {
worker_connections 1024;
}
http {
upstream host_service {
server host.docker.internal:2345;
}
server {
listen 80;
access_log /var/log/nginx/http_access.log combined;
error_log /var/log/nginx/http_error.log;
location / {
proxy_pass http://host_service;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $realip_remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}
Solution 1
Use network_mode: host, this will bind your nginx instance to host's network interface.
This could result in conflicts when running multiple nginx containers: every exposed port is binded to host's interface.
Solution 2
I'm running more nginx instances for every service I would like expose to outside world.
To keep the nginx configurations simple and avoid binding every nginx to host use the container structure:
dockerhost - a dummy container with network_mode: host
proxy - nginx container used as a proxy to host service,
link dockerhost to proxy, this will add an /etc/hosts entry in proxy contianer - we can use 'dockerhost' as a hostname in nginx configuration.
docker-compose.yaml
version: '3'
services:
dockerhost:
image: alpine
entrypoint: /bin/sh -c "tail -f /dev/null"
network_mode: host
proxy:
image: nginx:alpine
links:
- dockerhost:dockerhost
ports:
- "18080:80"
volumes:
- /share/Container/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
default.conf
location / {
proxy_pass http://dockerhost:8080;
This method allows us to have have automated let's encrtypt certificates generated for every service running on my server. If interested I can post a gist about the solution.
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://host.docker.internal:3000;
}
}
Docker expose host address is host.docker.internal in Mac os
There a couple of things you have to keep in mind:
Docker compose (from version 3) by default uses the service name as hostname for inter container networking
Nginx need to know the upstream first
I strongly recommend mounting the default.conf directly into your docker-compose.yml.
Lastly you have to dockerize your backend to make use of docker internal networking.
An example repo where I use nginx and docker-compose in a full-stack project: https://gitlab.com/datails/api.
The following example have some prerequisites:
you have a folder structure like:
- backend/
- frontend/
- default.conf
- docker-compose.yml
Secondly the backend and front-end dit have a Dockerfile that exposes an application on port 3000.
Example default.conf:
upstream backend {
server backend:3000;
}
upstream frontend {
server frontend:3000;
}
server {
listen 80;
location /api {
proxy_pass http://backend;
}
location / {
proxy_pass http://frontend/;
}
}
Example docker-compose.yml:
version: '3.8'
services:
nginx:
image: nginx:1.19.4
depends_on:
- server
- frontend
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- '8080:80'
Then make sure you have your backend dockerized and called (in this case) backend as a service and a front-end (if needed) called frontend as a service in your docker-compose:
version: '3.8'
services:
nginx:
image: nginx:1.19.4
depends_on:
- server
- frontend
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf
ports:
- '8080:80'
frontend:
build: ./frontend
backend:
build: ./backend
This is a bare minimum example to get started. Hope this will help future developers.

Docker Nginx Proxy: how to route traffic to different container using path and not hostname

lets say that now I have different app running on the same server on different path:
10.200.200.210/app1
10.200.200.210/app2
10.200.200.210/app3
I want to run each app on a different Docker container using nginx as a proxy.
I tried jwilder/nginx-proxy and works great if I use different domain names (app1.domain.com, app2.domain.com, etc), but I'm not able to use domains, I need to use the same IP.
also I can't use different ports like:
10.200.200.210:81/app1
10.200.200.210:82/app2
10.200.200.210:83/app3
all must work on port 80.
Is there a way to configure jwilder/nginx-proxy to do this?
Is there another Docker image like jwilder/nginx-proxy that make it.
or pls could you give me some hint to build an nginx docker container by myself?
In case if somebody is still looking for the answer.
jwilder/nginx-proxy allows you to use custom Nginx configuration either a proxy-wide or per-VIRTUAL_HOST basis.
Here's how can you do it with Per-VIRTUAL_HOST location configuration.
Inside your poject folder create another folder - "vhost.d".
Create file "whoami.local" with custom nginx configuration inside "vhost.d" folder. This file must have the same name as VIRTUAL_HOST!
./vhost.d/whoami.local
location /app1 {
proxy_pass http://app1:8000;
}
location /app2 {
proxy_pass http://app2:8000;
}
Create docker-compose.yml file.
./docker-compose.yml
version: '3'
services:
nginx-proxy:
image: jwilder/nginx-proxy
ports:
- "8080:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- /path/to/vhost.d:/etc/nginx/vhost.d:ro
gateway:
image: jwilder/whoami
environment:
- VIRTUAL_HOST=whoami.local
app1:
image: jwilder/whoami
app2:
image: jwilder/whoami
Run docker-compose up
Check configuration
In bash run:
$ curl -H "Host: whoami.local" localhost:8080
I'm 1ae273bce7a4
$ curl -H "Host: whoami.local" localhost:8080/app1
I'm 52b1a7b1992a
$ curl -H "Host: whoami.local" localhost:8080/app2
I'm 4adbd3f9e7a0
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6a659a4d4b0a jwilder/nginx-proxy "/app/docker-entrypo…" 54 seconds ago Up 53 seconds 0.0.0.0:8080->80/tcp nginxreverseproxy_nginx-proxy_1
4adbd3f9e7a0 jwilder/whoami "/app/http" 54 seconds ago Up 53 seconds 8000/tcp nginxreverseproxy_app2_1
52b1a7b1992a jwilder/whoami "/app/http" 54 seconds ago Up 53 seconds 8000/tcp nginxreverseproxy_app1_1
1ae273bce7a4 jwilder/whoami "/app/http" 54 seconds ago Up 53 seconds 8000/tcp nginxreverseproxy_gateway_1
You can also add "whoami.local" domain to /etc/hosts file and make calls to this domain directly.
/etc/hosts
...
127.0.0.1 whoami.local
...
Result:
$ curl whoami.local:8080
I'm 52ed6da1e86c
$ curl whoami.local:8080/app1
I'm 4116f51020da
$ curl whoami.local:8080/app2
I'm c4db24012582
Just use nginx image to create container,**do remember set net "host" **which will make your container share same address and port with host machine.mount nginx.conf file and config proxy table.for example:
docker command:
docker run --name http-proxy -v /host/nginx.conf:/etc/nginx/nginx.conf --net host -itd --restart always nginx
nginx.conf:
server {
listen 80;
location /app1 {
proxy_pass YOUR_APP1_URL;
}
location /app2 {
proxy_pass YOUR_APP2_URL;
}
}
Here is a full nginx.conf
It redirects all to root, and only /api to a different container.
Source and an example container using it
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
proxy_pass http://frontend:3000/;
}
location /api {
proxy_pass http://backend/api;
}
}
}
just put this inside /etc/nginx/nginx.conf
worker_processes 1;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location /api {
proxy_pass http://awesome-api;
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-Host $server_name;
}
}
}
Default bridge network has gateway on 172.17.0.1. You can use this IP address in your nginx.conf
server {
listen 80;
server_name example.com;
location /app1 {
proxy_pass http://172.17.0.1:81;
}
location /app2 {
proxy_pass http://172.17.0.1:82;
}
}
They will be accessible using port 80 from outside
You can check your bridge gateway IP address by running command docker network inspect bridge
My situation is a little bit different. I'm working on a project where Django and a couple of other apps sit behind nginx (acting as a reverse proxy)
The accepted solution did not work for me, and I think it is because the various apps do not serve files (i.e. /app1-uri/bla/blo/bli/ and /app1-uri/bla/blo/bli are exactly equivalent). All static files are gathered for nginx to serve.
The 'problematic' behavior is explained in the docs here. In essence, nginx picks up the uris without trailing slash and try to resolve them as resources, if it can't, it then redirects to /bla/blo/bli/ instead of /app1-uri/bla/blo/bli/
This is what finally worked for me. In this example app1-uri is repairapp.
server {
listen 80;
listen 443 ssl http2;
...
# This is the line that fixes the issue.
rewrite ^/repairapp/([^static].*[^/])$ /repairapp/$1/ permanent;
# This is nginx serving my static files
location /repairapp/static/ {
alias /var/www/repairapp/static/;
}
# This is the uri that maps to my app (no file serving here)
location /repairapp/ {
proxy_pass http://repairappcontainer:8000/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
...
Note the line rewrite ^/repairapp/([^static].*[^/])$ /repairapp/$1/ permanent;
It rewrites and adds a trailing forward slash to any (of my app) uris that misses it; except for those that start with /repairapp/static. Those uris map to resources that nginx will then serve.
To debug, open a shell in the nginx container and run curl -IL http://[server-name]/repairapp/[string-of-uris-without-trailing-slash] to see exactly what happens.

How to forward upstream from nginx to upstream server

I am trying to reverse-proxy an ejabberd connection manager with nginx in docker.
Following is my docker-compose file
version: '2'
services:
nginx:
image: nginx
ports:
- "80:80"
- "443:443"
depends_on:
- ejabberd
links:
- ejabberd
ejabberd:
image: ejabberd:16.04
depends_on:
- mysql
ports:
- "5280:5280"
links:
- mysql
mysql:
image: mysql:5.6
ports:
- "3306:3306"
The images nginx, ejabberd:16.04 and mysql:5.6 are available in my local docker.
Following is my server config file which is included in the nginx.conf file
upstream ejabberd-server {
server ejabberd:5280;
}
server {
# regular silverstripe things here
# location should match your JabberPage::BOSHUrl
# with a leading slash
listen 80;
#server_name oops.hereim.co
location /http-bind {
# Local ejabberd with http-bind
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://ejabberd-server;
}
}
But when I try to access the url http://192.168.99.102/http-bind
I get the error 404 not found.
Am I missing something in the configuration above?
192.168.99.102 is the IP of my docker machine.
docker-compose exec nginx ping ejabberd
The above command returns a reply.
The upstream has to be setup to host ejabberd as nginx and ejabberd are not on the same ejabberd container.
If you see the docker-compose.yml, the ejabberd and mysql connection works fine and I am able to connect to mysql from ejabberd. But there seems to be some missing piece while connection nginx to ejabberd.
The issue is probably due to an nginx issue. nginx does not pick up .conf files under the /etc/nginx/conf.d directory.
I had to change the file /etc/nginx/sites-enabled/default and enter the location inside that file. This solved my problem for this question.
The next action item is to check why nginx does not read conf file from /etc/nginx/conf.d directory
Looks like your server part in upstream is a bit wrong.
Can you try server 127.0.0.1:5280; instead of server ejabberd:5280; ?

Resources