How to configure nginx with multiple docker-compose.yml files? - docker

If I want to setup nginx with my docker containers, one option is to setup the nginx instance in my docker-compose.yml, and link the nginx container to all application containers.
The drawback of this approach, however, is that the docker-compose.yml becomes server-level, since only one nginx container can expose ports 80/443 to the internet.
I'm interested in being able to define several docker-compose.yml files on the same server, but still easily expose the public-facing containers in each compose file via a single server-specific nginx container.
I feel this should be pretty easy, but I haven't been able to find a good resource or example for this.

First, you need to create a network for nginx and the proxied containers:
docker network create nginx_network
Next, configure the nginx container in a compose file like this:
services:
nginx:
image: your_nginx_image
ports:
- "80:80"
- "443:443"
networks:
- nginx_network
networks:
nginx_network:
external: true
After that you can run proxied containers:
services:
webapp1:
image: ...
container_name: mywebapp1
networks:
- nginx_network # proxy and app must be in same network
- webapp1_db_network # you can use additional networks for some stuff
database:
image: ...
networks:
- webapp1_db_network
networks:
nginx_network:
external: true
webapp1_db_network: ~ # this network won't be accessible from outside
Also, to make this work you need to configure your nginx properly:
server {
listen 80;
server_name your_app.example.com;
# Docker DNS
resolver 127.0.0.11;
location / {
# hack to prevent nginx to resolve container's host on start up
set $docker_host "mywebapp1";
proxy_pass http://$docker_host:8080;
}
}
You need to tell nginx to use Docker's DNS, so it will be able to access containers by their names.
But note that if you run the nginx container before the others, then nginx will try to resolve another container's host and fail, because the other containers are not running yet. You can use a hack with placing the host into a variable. With this hack, nginx won't try to resolve host until receiving a request.
With this combination you can have nginx always up, while starting and stopping proxied applications independently.
Update:
If you want a more dynamic solution, you can modify the nginx config as follows:
server {
listen 80;
resolver 127.0.0.11;
# define server_name with regexp which will read subdomain into variable
server_name ~^(?<webapp>.+)\.example\.com;
location / {
# use variable from regexp to pass request to desired container
proxy_pass http://$webapp:8080;
}
}
With this configuration, a request to webapp1.example.com will be passed to container "webapp1", webapp2.example.com to "webapp2" etc. You only need to add DNS records and run app containers with right name.

The accepted answer is great, but since I am in the trenches with this right now I'm going to expand upon it with my debugging steps in hopes it helps someone (myself in the future?)
Docker-compose projects often use nginx as reverse-proxy to route http traffic to the other docker services. nginx was a service in my projectfolder/docker-compose.yml which was connected to two docker networks.
One was the default network created when I used docker-compose up on projectfolder/docker-compose.yml (It is named projectfolder_default and services connect to it by default UNLESS you have a networks property for your service with another network, then make sure you add - default to the list). When I ran docker network ls I saw projectfolder_default in the list and when I ran docker network inspect projectfolder_default I saw the nginx container, so everything was good.
The other was a network called my_custom_network that I set up myself. I had a startup script that created it if it did not exist using https://stackoverflow.com/a/53052379/13815107 I needed it in order to talk to the web service in otherproject/docker-compose.yml. I had correctly added my_custom_network to:
nginx service's networks list of projectfolder/docker-compose.yml
bottom of projectfolder/docker-compose.yml
web service's networks in otherproject/docker-compose.yml
bottom of otherproject/docker-compose.yml
The network showed up and had the right containers using docker network ls and docker network inspect my_custom_network
However, I assumed that proxy_pass to http://web in my server.conf would map to the docker service web.projectfolder_default. I was mistaken. To test this I opened shell on the nginx container (docker exec -it nginx sh). When I used ping web (may need to apt-get update, apt-get install iputils-ping) it succeeded, but it printed a url with my_custom_network which is how I figured out the mistake.
Update: I tried using http://web.my_custom_network in server.conf.template and it routed great, but my webapp (Django-based) choked on underscores in the url. I renamed web to web2 in otherproject/docker-compose.yml and then used something like docker stop otherproject_web and docker rm otherproject_web to get rid of the bad one.
projectfolder/docker-compose.yml
services:
# http://web did NOT map to this service!! Use http://web.main_default or change the names
web:
...
nginx:
...
links:
- web
networks:
- default
- my_custom_network
...
networks:
- my_custom_network
external: true
otherproject/docker-compose.yml
services:
# http://web connected to this service instead. You could use http://web.my_custom_network to call it out instead
web:
...
networks:
- default
- my_custom_network
...
networks:
- my_custom_network
external: true
projectfolder/.../nginx/server.conf.template (next to Dockerfile)
...
server {
...
location /auth {
internal;
# This routed to wrong 'web'
proxy_pass http://web:9001;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
location / {
alias /data/dist/;
}
location /robots.txt {
alias /robots.txt;
}
# Project Folder backend
location ~ ^/(api|login|logout)/ {
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;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
# This routed to wrong 'web'
proxy_pass http://web:9001;
}
# Other project UI
location /other-project {
alias /data/other-project-client/dist/;
}
# Other project Django server
location ~ ^/other-project/(rest)/ {
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;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
# nginx will route, but won't work some frameworks like django (it doesn't like underscores)
# I would rename this web2 in yml and use http://web2
proxy_pass http://web.my_custom_network:8000;
}
}

Related

Reverse Proxy with Hostnames with Docker compose

I have 2 APIs and a front end that work in docker containers. They can all be created via docker-compose and I have an Nginx reverse proxy that should route to them using their hostnames i.e. api-1.my-project.localhost. In fact, I have had this process working, but every so often, like this morning, I get the "Non-existent domain" or "We can't find this site" when visiting it using these hostnames.
I don't believe the docker-compose.yml is all that important but I have included it here:
version: "3.2"
services:
api-1:
# Runs on port 5000
image: my-companies-docker-registry.api-1
api-2:
# Runs on port 5000
image: my-companies-docker-registry.api-2
main-front-end:
# Runs on port 3000
image: my-companies-docker-registry.main-front-end
my-project:
image: nginx
depends_on:
- api-1
- api-2
- main-front-end
ports:
- 80:80
volumes:
- type: bind
read_only: true
source: ./nginx
target: /etc/nginx
My nginx config looks like so:
events {}
http {
# Api 1
upstream api-1{
server api-1:5000;
}
server {
listen 80;
server_name api-1.my-project.localhost;
location / {
proxy_pass http://api-1;
}
}
# Api 2
upstream api-2 {
server api-2:5000;
}
server {
listen 80;
server_name api-2.my-project.localhost;
location / {
proxy_pass http://api-2;
}
}
# Main Front end
upstream main-front-end {
server main-front-end:3000;
}
server {
listen 80 default_server deferred;
server_name my-project.localhost www.my-project.localhost;
location / {
proxy_pass http://main-front-end;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
}
The aim of this is so that I can access the site using http://my-project.localhost and the APIs using api-1.my-project.localhost and api-2.my-project.localhost. As I have listen 80 default_server deferred by visiting http://localhost I can hit nginx, which lets me see my front end site. So docker-compose has bound the ports correctly.
At some point in the past, I have been able to access the sites using the my-project.localhost suffix but now this is no longer the case. This site suggests adding a host entry for every site, I don't remember doing this before, as there are a lot of sites, this would have taken a while but it is possible I did and now they have been removed. I am also aware that Nginx is not docker, so I have no idea how those hostnames would have been extracted and added to my machines host resolution process.
How could this have worked before and not now? And how can I get my setup to work without making manual host file changes?

How to dockerize nginx and another server in separate docker-compose files?

I have a reactjs app which I want to server through nginx. The app makes API requests a node.js Express server. I decided to dockerize nginx together with static website files (from reactjs app) in one docker-compose file. I created another docker-compose file for Express server. The containers are currently on my laptop and are using localhost. I haven't been able to get them to work for a long time and there's unfortunately not much information about this on the internet. (the website and Express server work when not inside Docker).
First of all I created a new docker network:
docker network create --driver bridge appstore-net
This is my docker-compose file for website and nginx:
version: '3.5'
services:
appstore-front:
container_name: appstore-front-production
build:
context: .
dockerfile: Dockerfile-prod
ports:
- '80:80'
networks:
- appstore-net
external_links:
- appstore-bl-server-production
networks:
appstore-net:
external: true
This is my docker-compose file for Express server:
version: '3'
services:
appstore-bl-server:
container_name: appstore-bl-server-production
build:
dockerfile: Dockerfile-prod
context: .
volumes:
- ".:/usr/src/app"
ports:
- "3000:3000"
networks:
- appstore-net
networks:
appstore-net:
external: true
This is my nginx configuration:
server {
listen 80;
listen [::]:80;
#Docker DNS
resolver 127.0.0.11;
server_name localhost;
access_log /var/log/nginx/appstore.access.log;
error_log /var/log/nginx/appstore.error.log;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api {
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;
#hack to prevent nginx to resolve container's host on start up
set $docker_host "appstore-bl-server";
proxy_pass http://$docker_host:3000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
As you can see I'm using docker DNS resolver as well as creating "passing proxy" to URL consisting of Express container name.
How can I make it work?
EDIT: I found 2 issues that could've been the reason:
1) external_links needs to refer to container name, not container service.
2) docker_host variable in nginx needs to refer to service name, not container name.
With these corrections the setup works (corrected the values in the OP as well).

Nginx + Docker. 504 Gateway Timeout

I've been struggling for a bit on this and I'm starting to get depressed...
I'm running a droplet on DigitalOcean with Ubuntu 18.10.
I've used Docker to serve my different service (nuxt, socket.io, nginx, nodejs)
And I've used Nginx to revese proxy those services (at first i'd just want to render the nuxt app)
When executing
docker-compose up --build -d
All processes are running and are accessible on the server but inaccessible from my local machine (I get a 504 Gateway Timeout).
There is one time where I got it working but when adding some location rule, it broke again.
So here's my config for nginx :
server {
listen 8080;
server_name 104.248.201.255;
location / {
proxy_pass http://doodlehelha-nuxt:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
And here's the nginx service in my docker-compose.yml
nginx:
image: nginx:1.15
container_name: doodlehelha-nginx
ports:
- '8080:8080'
- '443:443'
expose:
- '8080'
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- nuxt
- api
- socket
This is a very simple configuration and that's why I don't know what's wrong...
If someone more experienced can point it out, I'd be very grateful.

nginx does not automatically pick up dns changes in swarm

I'm running nginx via lets-nginx in the default nginx configuration (as per the lets-nginx project) in a docker swarm:
services:
ssl:
image: smashwilson/lets-nginx
networks:
- backend
environment:
- EMAIL=sas#finestructure.co
- DOMAIN=api.finestructure.co
- UPSTREAM=api:5000
ports:
- "80:80"
- "443:443"
volumes:
- letsencrypt:/etc/letsencrypt
- dhparam_cache:/cache
api:
image: registry.gitlab.com/project_name/image_name:0.1
networks:
- backend
environment:
- APP_SETTINGS=/api.cfg
configs:
- source: api_config
target: /api.cfg
command:
- run
- -w
- tornado
- -p
- "5000"
api is a flask app that runs on port 5000 on the swarm overlay network backend.
When services are initially started up everything works fine. However, whenever I update the api in a way that makes the api container move between nodes in the three node swarm, nginx fails to route traffic to the new container.
I can see in the nginx logs that it sticks to the old internal ip, for instance 10.0.0.2, when the new container is now on 10.0.0.4.
In order to make nginx 'see' the new IP I need to either restart the nginx container or docker exec into it and kill -HUP the nginx process.
Is there a better and automatic way to make the nginx container refresh its name resolution?
Thanks to #Moema's pointer I've come up with a solution to this. The default configuration of lets-nginx needs to be tweaked as follows to make nginx pick up IP changes:
resolver 127.0.0.11 ipv6=off valid=10s;
set $upstream http://${UPSTREAM};
proxy_pass $upstream;
This uses docker swarm's resolver with a TTL and sets a variable, forcing nginx to refresh name lookups in the swarm.
Remember that when you use set you need to generate the entire URL by yourself.
I was using nginx in a compose to proxy a zuul gateway :
location /api/v1/ {
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_pass http://rs-gateway:9030/api/v1/;
}
location /zuul/api/v1/ {
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_pass http://rs-gateway:9030/zuul/api/v1/;
}
Now with Swarm it looks like that :
location ~ ^(/zuul)?/api/v1/(.*)$ {
set $upstream http://rs-gateway:9030$1/api/v1/$2$is_args$args;
proxy_pass $upstream;
# Set headers
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
Regex are good but don't forget to insert GET params into the generated URL by yourself.

Subdomains, Nginx-proxy and Docker-compose

I'm looking for a way to configure Nginx to access hosted services through a subdomain of my server. Those services and Nginx are instantiated with Docker-compose.
In short, when typing jenkins.192.168.1.2, I should access to Jenkins hosted on 192.168.1.2 redirected with Nginx proxy.
A quick look of what I currently have. It doesn't work without a top domain name, so it works fine on play-with-docker.com, but not locally with for example 192.168.1.2.
server {
server_name jenkins.REVERSE_PROXY_DOMAIN_NAME;
location / {
proxy_pass http://jenkins:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
To have a look of what I want: https://github.com/Ivaprag/devtools-compose
My overall goal is to access remote docker containers without modifying clients' DNS service.
Unfortunately nginx doesn't support sub-domains on IP addresses like that.
You would either have to modify the clients hosts file (which you said you didn't want to do)...
Or you can just set your nginx to redirect like so:
location /jenkins {
proxy_pass http://jenkins:8080;
...
}
location /other-container {
proxy_pass http://other-container:8080;
}
which would allow you to access jenkins at 192.168.1.2/jenkins
Or you can try and serve your different containers through different ports. E.g:
server {
listen 8081;
location / {
proxy_pass http://jenkins:8080;
...
}
}
server {
listen 8082;
location / {
proxy_pass http://other-container:8080;
...
}
}
And then access jenkins from 192.168.1.2:8081/
If you are already using docker-compose I recommend using the jwilder nginx-proxy container.
https://github.com/jwilder/nginx-proxy
This allows you to add unlimited number of web service containers to the backend of the defined nginx proxy, for example:
nginx-proxy:
image: jwilder/nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- "/etc/nginx/vhost.d"
- "/usr/share/nginx/html"
- "/var/run/docker.sock:/tmp/docker.sock:ro"
- "nginx_certs:/etc/nginx/certs:rw"
nginx:
build:
context: ./docker/nginx/
dockerfile: Dockerfile
volumes_from:
- data
environment:
VIRTUAL_HOST: www.host1.com
nginx_2:
build:
context: ./docker/nginx_2/
dockerfile: Dockerfile
volumes_from:
- data
environment:
VIRTUAL_HOST: www.host2.com
apache_1:
build:
context: ./docker/apache_1/
dockerfile: Dockerfile
volumes_from:
- data
environment:
VIRTUAL_HOST: www.host3.com
The nginx-proxy mount the host docker sock file in order to get information about the other containers running, if any of them have the env variable VIRTUAL_HOST set then it will add it to its configuration.
I was trying to configure subdomains in nginx (host), for two virtualhosts in one LXC container.
The way it worked for me:
For apache (in the container), I created two virtual hosts: one in port 80 and the other one in port 90.
For enabling port 90 in apache2 (container), it was necessary to add the line "Listen 90" below "Listen 80" in /etc/apache2/ports.conf
For NGINX (host machine), configured two DOMAINS, both in port 80 creating independent .conf files in /etc/nginx/sites-available. Created symbolic link for each file to /etc/nginx/sites-enabled.
In the first NGINX myfirstdomain.conf file, redirect to http://my.contai.ner.ip:80.
In the second NGINX myseconddomain.conf file, redirect to http://my.contai.ner.ip:90
That was it for me !

Resources