Skip to main content

Docker Compose Networking

Victoria Harbour, Hong Kong

I previously created 3 apps that are proxied by NGINX - all together 4 docker container waiting to be deployed. I now want to create a Docker Compose file to be able to start all of them up with a single command.

Docker Compose

My first attempt to accomplish this looks like that - every wiki container runs the hapi web server and serves the content of wiki/public which is mapped to a folder on my host system that contains the static web content from my app (that is source controlled by Gitlab and build in a Gitlab CI Pipeline). The NGINX proxy is then used to help out with the domain assignment and TLS certification:

version: '3.8'
services:
  wiki_en:
    image: my.gitlab.com:12345/wiki/wiki_container:latest
    container_name: wiki_en
    ports:
      - '127.0.0.1:7777:8888'
    restart: unless-stopped
    volumes:
      - /opt/wiki/wiki-en/public:/wiki/public

  wiki_fr:
    image: my.gitlab.com:12345/wiki/wiki_container:latest
    container_name: wiki_fr
    ports:
      - '127.0.0.1:7778:8888'
    restart: unless-stopped
    volumes:
      - /opt/wiki/wiki-fr/public:/wiki/public

  wiki_de:
    image: my.gitlab.com:12345/wiki/wiki_container:latest
    container_name: wiki_de
    ports:
      - '127.0.0.1:7779:8888'
    restart: unless-stopped
    volumes:
      - /opt/wiki/wiki-de/public:/wiki/public

  ingress:
    image: nginx:stable-alpine
    container_name: ingress
    network_mode: host
    restart: unless-stopped
    volumes:
      - /opt/wiki/docker_ingress:/etc/nginx/conf.d

Note that I am running into an issue where NGINX has to access the public dir of each app (long story). This directory lies outside of the app container on my host system. This is why I have to forward ports for each app to my host network where NGINX is running. To prevent my apps from leaking onto the external network I will bind them to 127.0.0.1 on my host system.

When I start this composite it will automatically create a virtual network for me - named after the folder that contains my docker-compose.yml file (wiki):

docker network ls
NETWORK IDNAMEDRIVERSCOPE
d61bea58e174bridgebridgelocal
c8cc528dc050hosthostlocal
a982d8cfae58nonenulllocal
545b0454b47fwiki_defaultbridgelocal

Inspecting the network shows me that all applications have been attached to it at start up:

docker inspect network wiki_default
[
  {
    "Name": "wiki_default",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "Ingress": false,
    "Containers": {
      "44665": {
        "Name": "wiki_de",
        "MacAddress": "02:42:ac:12:00:04",
        "IPv4Address": "172.18.0.4/16",
        "IPv6Address": ""
      },
      "bca6e": {
        "Name": "wiki_fr",
        "MacAddress": "02:42:ac:12:00:03",
        "IPv4Address": "172.18.0.3/16",
        "IPv6Address": ""
      },
      "e98fdb": {
        "Name": "wiki_en",
        "MacAddress": "02:42:ac:12:00:02",
        "IPv4Address": "172.18.0.2/16",
        "IPv6Address": ""
      }
    }
  }
]

Manual Network Override

You can override the default network name by adding a project name:

docker-compose -p wiki_project up -d

You can also specify the network in your docker-compose.yml file. But before I start with that - let's figure out a way how I can remove the requirement for my NGINX ingress to be attached to my host network.

For example we can try to mount the public directory of each app into the NGINX container and serve static content from there. I then also have to bring the NGINX container into my custom network and have it access my apps through the Docker DNS service.

The NGINX configuration file I use together the NGINX container will then look like this:

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

    root                                        /opt/wiki/wiki-en/public;

    location /en/ {
        add_header                              Cache-Control  "public, must-revalidate, proxy-revalidate, max-age=0";
        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://wiki_en:7777/;
    }

    location /fr/ {
        add_header                              Cache-Control  "public, must-revalidate, proxy-revalidate, max-age=0";
        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://wiki_fr:7778/;
        root                                    /opt/wiki/wiki-fr/public;
    }

    location /de/ {
        add_header                              Cache-Control  "public, must-revalidate, proxy-revalidate, max-age=0";
        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://wiki_de:7779/;
        root                                    /opt/wiki/wiki-de/public;
    }

    error_page  404              /de/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;
    }
}

Note that now, instead of having a proxy pass to http://127.0.0.1:7777/, http://127.0.0.1:7778/, http://127.0.0.1:7779/ I am using the container names http://wiki_en:8888, http://wiki_fr:8888, http://wiki_de:8888.

The root directories are left as they were:

/opt/wiki/wiki-en/public
/opt/wiki/wiki-fr/public
/opt/wiki/wiki-de/public

But now they are pointing inside the NGINX container and have to be mounted in from my host system. And I now need to expose the port 80 for the NGINX service:

version: '3.8'
services:
  wiki_en:
    image: my.gitlab.com:12345/wiki/wiki_container:latest
    container_name: wiki_en
    networks:
      - gateway
    restart: unless-stopped
    volumes:
      - /opt/wiki/wiki-en/public:/wiki/public

  wiki_fr:
    image: my.gitlab.com:12345/wiki/wiki_container:latest
    container_name: wiki_fr
    networks:
      - gateway
    restart: unless-stopped
    volumes:
      - /opt/wiki/wiki-fr/public:/wiki/public

  wiki_de:
    image: my.gitlab.com:12345/wiki/wiki_container:latest
    container_name: wiki_de
    networks:
      - gateway
    restart: unless-stopped
    volumes:
      - /opt/wiki/wiki-de/public:/wiki/public

  ingress:
    image: nginx:stable-alpine
    container_name: ingress
    networks:
      - gateway
    ports:
      - '80:80'
    restart: unless-stopped
    volumes:
      - /opt/wiki/docker_ingress:/etc/nginx/conf.d
      - /opt/wiki/wiki-en:/opt/wiki/wiki-en
      - /opt/wiki/wiki-fr:/opt/wiki/wiki-fr
      - /opt/wiki/wiki-de:/opt/wiki/wiki-de

networks:
  gateway: {}