NGINX Docker Multihost
I do have 3 frontend containers - running node/express frontend applications in different languages. Instead of running all of them on different domains I want to have them running on sub-domains:
Current State:
https://wiki.instar.com/
https://wiki.instar.de/
https://wiki.instar.fr/
Wanted State:
https://wiki.instar.com/en/
https://wiki.instar.com/de/
https://wiki.instar.com/fr/
Frontend Container
Pull your frontend container:
docker login -u me@email-address.com gitlab.example.com:34578
docker pull gitlab.example.com:34578/wiki/wiki-instar-en-docker:latest
docker pull gitlab.example.com:34578/wiki/wiki-instar-de-docker:latest
docker pull gitlab.example.com:34578/wiki/wiki-instar-fr-docker:latest
And start them:
docker run -d --name wiki-instar-en gitlab.example.com:34578/wiki/wiki-instar-en-docker:latest
docker run -d --name wiki-instar-de gitlab.example.com:34578/wiki/wiki-instar-de-docker:latest
docker run -d --name wiki-instar-fr gitlab.example.com:34578/wiki/wiki-instar-fr-docker:latest
All apps provide their own express webserver that is hosting static page content on a unique port like 8081, 8082 and 8083. Since we did not forward this port the web applications are now trapped inside the docker network.
Container Network
Now we have 3 containers running with no exposed ports to the client, but we need to get them to communicate with each other. In order to do that we need to be able to put them all under the same network. Let’s create our network:
docker network create wikinet
Now add the container by their name:
docker network connect wikinet wiki-instar-en
docker network connect wikinet wiki-instar-de
docker network connect wikinet wiki-instar-fr
Let’s see if they have been added:
docker network inspect wikinet
The output show us that all 3 apps have been added successfully:
[
{
"Name": "wikinet",
"Id": "1c5dac2e9a7496340a316c1d65f9768ed475547af6c3b7a46662d5aaedad288a",
"Created": "2020-09-03T12:35:31.598865218Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.19.0.0/16",
"Gateway": "172.19.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"0114e569753277d3f9a6087515e5bec428cdc6088c312e112a8b34e04e08de43": {
"Name": "wiki-instar-fr",
"EndpointID": "befe819138c011efd0761e6cfe0daf10b70d05911034c61367b76aa3c8b6e561",
"MacAddress": "02:42:ac:13:00:04",
"IPv4Address": "172.19.0.4/16",
"IPv6Address": ""
},
"1e14d78240c8ff11e0cd18424fb05d7f35e95d8194dfc7b2e4eba83ecee6558e": {
"Name": "wiki-instar-en",
"EndpointID": "8d16f5fe96448d08f995c4f67c9947e25fe56b4edcae48ad9b8b2ac1995ef8a8",
"MacAddress": "02:42:ac:13:00:02",
"IPv4Address": "172.19.0.2/16",
"IPv6Address": ""
},
"cb68e1b721b4bd90b4176c6ffe67f87f0aa38f8bc8affeab15502abd7c708bd7": {
"Name": "wiki-instar-de",
"EndpointID": "75ba745de043af8224954593a24f1ce431f9b5cc0569aa8a8d6e0ee70e30c7c0",
"MacAddress": "02:42:ac:13:00:03",
"IPv4Address": "172.19.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
Docker provides a DNS service to resolve IP address inside it's virtual networks. This means we should now be able to load the web content from one container by sending a wget or curl command from another. Let's try this by entering one of our containers:
docker exec -ti wiki-instar-en /bin/bash
Once we are in we can request the web content from another container:
curl localhost:8081
curl wiki-instar-de:8082
curl wiki-instar-fr:8083
Configuring the Docker Ingress
I am going to use the NGINX reversed proxy to build an ingress to the virtual network. We could create the ingress by spawning a container from the default NGINX image and then editing the configuration file inside the container:
docker run -p 80:80 --network=wikinet --name ingress nginx:stable-alpine
Depending on how you configure your cluster and Gatsby there might be some differences in how you have to configure NGINX to work as a proxy here. I just tried to use the following configuration in a different build and ran into some difficulties with changes I made in Gatsby. Please also check out the NGINX Docker Ingress article if you have problems here as well!
But we can also create the configuration file on our host system and then mount it into the container /etc/nginx/conf.d/default.conf
to persist it. We can start by modifying the default config file that comes with NGINX:
server {
listen 80;
listen [::]:80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
Create a folder on your host system and create the default.conf
file:
mkdir /opt/docker_ingress
nano default.conf
server {
listen 80;
listen [::]:80;
server_name localhost;
location /en {
proxy_pass http://wiki-instar-en:7777/;
}
location /fr {
proxy_pass http://wiki-instar-fr:7778/;
}
location /de {
proxy_pass http://wiki-instar-de:7779/;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
We can now start the NGINX ingress with the configuration file:
docker run -d -p 80:80 -p 443:443 -v /opt/nginx_docker_ingress:/etc/nginx --network=wikinet --name ingress nginx:alpine
Verify that the ingress is running and that you can now finally access your apps from outside of the docker virtual network. You can test it by accessing the docker host IP address followed by the language prefix we defined in NGINX: /en
, /de
, /fr
.
We can also add the following rewrite location block to automatically forward traffic that enters through the root URL to /en/
location / {
rewrite ^/(.*)$ /en/$1 permanent;
}
We can now add some web proxy seasoning to optimize everything:
server {
listen 80;
listen [::]:80;
server_name localhost;
charset koi8-r;
# Gzip Compression
gzip on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
gzip_proxied no-cache no-store private expired;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
gzip_vary on;
location / {
rewrite ^/(.*)$ /en/$1 permanent;
}
location /en/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto http;
proxy_hide_header X-Frame-Options;
proxy_set_header Accept-Encoding "";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_max_temp_file_size 0;
proxy_redirect off;
proxy_read_timeout 240s;
proxy_pass http://127.0.0.1:7777/en/;
}
location /fr {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_hide_header X-Frame-Options;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_max_temp_file_size 0;
proxy_redirect off;
proxy_read_timeout 240s;
proxy_pass http://wiki-instar-fr:7778/;
}
location /de {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_hide_header X-Frame-Options;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_max_temp_file_size 0;
proxy_redirect off;
proxy_read_timeout 240s;
proxy_pass http://wiki-instar-de:7779/;
}
# All static files should be cached forever.
location ~* \.(jpg|png|ico)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
log_not_found off;
}
# JS, CSS and HTML files should never be cached.
location ~* \.(css|js|html)$ {
add_header Cache-Control "public, must-revalidate, proxy-revalidate, max-age=0";
}
# this prevents hidden files (beginning with a period) from being served
location ~ /\. {
access_log off;
log_not_found off;
deny all;
}
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Gatsby App
docker run -d -p 80:80 -p 443:443 -v /opt/nginx_docker_ingress:/etc/nginx --network=wikinet --name nginx nginx:alpine
docker run -d --network host -v /opt/hapi-container-en/public:/wiki_en_ssr/public --name wiki_en wiki-instar-com
docker run -d --network host -v /opt/hapi-container-de/public:/wiki_de_ssr/public --name wiki_de wiki-instar-de
docker run -d --network host -v /opt/hapi-container-fr/public:/wiki_fr_ssr/public --name wiki_fr wiki-instar-fr
server {
listen 80;
listen [::]:80;
server_name localhost;
charset koi8-r;
# Gzip Compression
gzip on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
gzip_proxied no-cache no-store private expired;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
gzip_vary on;
location / {
rewrite ^/(.*)$ /en/$1 permanent;
}
location /en/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto http;
proxy_hide_header X-Frame-Options;
proxy_set_header Accept-Encoding "";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_max_temp_file_size 0;
proxy_redirect off;
proxy_read_timeout 240s;
proxy_pass http://127.0.0.1:7777/en/;
root /opt/docker_ingress/app;
}
location /fr/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto http;
proxy_hide_header X-Frame-Options;
proxy_set_header Accept-Encoding "";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_max_temp_file_size 0;
proxy_redirect off;
proxy_read_timeout 240s;
proxy_pass http://127.0.0.1:7778/;
root /opt/docker_ingress/app;
}
location /de/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto http;
proxy_hide_header X-Frame-Options;
proxy_set_header Accept-Encoding "";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_max_temp_file_size 0;
proxy_redirect off;
proxy_read_timeout 240s;
proxy_pass http://127.0.0.1:7779/;
root /opt/docker_ingress/app;
}
# All static files should be cached forever.
location ~* \.(jpg|png|ico)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
log_not_found off;
}
# JS, CSS and HTML files should never be cached.
location ~* \.(css|js|html)$ {
add_header Cache-Control "public, must-revalidate, proxy-revalidate, max-age=0";
}
# this prevents hidden files (beginning with a period) from being served
location ~ /\. {
access_log off;
log_not_found off;
deny all;
}
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}