← All Articles

Nested Nginx configuration for Docker stacks

Siavash KhalvatiSiavash Khalvati
May 4th 17Updated Jul 8th 22

nested-nginx-config-for-docker

Our Nginx config file uses our CustomConfig convention which means it uses the Liquid template language developed by Shopify and used by many websites. There are many good resources on the web on how to use the Liquid syntax.

Today, I'm going through serving your docker services in a way that one container serves / and the other one serves /app

This is a simple one to give you some idea as to how you can find your way. Once you start getting the hang of it, you can make more intricate configurations.

I'm going to demonstrate using a sample stack with two services: one is called web and the other one is called app. And I want the web to serve the root i.e. / and the app one serve /app.

I have tried to explain this via the nginx configuration file.

All the values like this {{ workers }} are the values that get swapped when you click on preview, so for instance if you have this line in your config:

{% for service_container in service_containers %}

You can try adding a comment like bellow

# {{ service_containers }}

and after clicking on the preview button you will see that has been expanded to its value. This is good for understanding the format.

Now back to my example:

This is my service file:

services:
  web:
    git_url: YOUR_WEB_GIT_REPOSITORY
    git_branch: GIT_BRANCH
    ports:
    - container: 3000
      http: 80
      https: 443
    command: bundle exec rails server -e _env:RAILS_ENV
    build_command: /bin/sh -c "bundle exec rake db:schema:load"
    deploy_command: /bin/sh -c "bundle exec rake db:migrate"
    build_root: "."
    env_vars:
      RAILS_ENV: production
  app:
    git_url: YOUR_APP_GIT_REPOSITORY
    git_branch: GIT_BRANCH
    ports:
    - container: 3000
      http: 80
      https: 443
    command: bundle exec rails server -e _env:RAILS_ENV
    build_root: "."
    env_vars:
      RAILS_ENV: production
databases:
- mysql

This is my Nginx config which includes the explanation, you may need to read it a few times to get familiar with.

Note:

I have taken out the parts that we don't touch to make it easier to follow

user nginx;
worker_processes {{ workers }};
error_log /var/log/nginx.log;

events {
    worker_connections 1024;
    accept_mutex off;
}

http {
    root /etc/cloud66/webroot;
    
    ...

    {% for service_container in service_containers %}
    {% for upstream in service_container.upstreams %}
    {% if upstream.port.http or upstream.port.https %}
    upstream {{ upstream.name }} {
        {% for private_ip in upstream.private_ips %}
        server {{private_ip}}:{{upstream.port.container}};
        {% endfor # upstream.private_ips %}
    }
    {% endif # upstream.port.http or upstream.port.https %}
    {% endfor # service_container.upstreams %}
    {% endfor # service_containers %}

    {% if websocket_support == true %}
    map $http_upgrade $connection_upgrade {
        default Upgrade;
        '' close;
    }
    {% endif %}

    {% if cors_enabled == true %}
    # Cross domain resource
    ...
    {% endif # cors_enabled == true %}
    #First capturing the app services upstream name and assign it to WEB_APP to use it later on in the config

    # ==------BEGIN-USER-DEFINED-----==
    {% for service_container in service_containers %}
    {% if service_container.service_name == "app" %}
    {% for upstream in service_container.upstreams %}
    {% capture WEB_APP %}{{ upstream.name }}{% endcapture %}
    {% endfor # service_container.upstreams %}
    {% endfor # service_containers %}
    # ------END-USER-DEFINED-----

    {% for service_container in service_containers %}
    # Prevent creating a server block for app service as it is going to be added as a location block for web instead
    # ------BEGIN-USER-DEFINED-----
    {% if service_container.service_name == "app" %}
    {% continue # to skip the iteration when is for app %}
    {% endif %}
     # ------END-USER-DEFINED----- 
    {% for upstream in service_container.upstreams %}

    {% if upstream.port.http != blank %}
    server {
        listen {{ upstream.port.http }};

        ...

        {% if maintenance_mode_active and upstream.port.http == 80 %}
        location / {
            root /etc/cloud66/pages;
            rewrite ^(.*)$ /cloud66_maintenance.html break;
        }
        {% else %}
        location / {
            {% if websocket_support == true %}
            # Next three lines implement websocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            {% endif %}
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_pass http://{{ upstream.name }};
            break;
        }
        # Add this block only if the whole block belongs to web service.
        # ------BEGIN-USER-DEFINED-----
        {% if service_container.service_name == "web" %}
        # This is the location added for app service
        location /app {
            {% if websocket_support == true %}
            # Next three lines implement websocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            {% endif %}
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            #Here WEB_APP is the app service captured upstream
            proxy_pass http://{{ WEB_APP }};
            break;
        }
        {% endif %}
        # ------END-USER-DEFINED-----
        {% endif %}
    }
    {% endif # if upstream.port.http != blank %}

    {% if allow_ssl == true %}
    {% if upstream.port.https != blank %}
    server {
        listen {{ upstream.port.https }};
        ssl on;
        ...

        {% if maintenance_mode_active and upstream.port.https == 443 %}
        location / {
            root /etc/cloud66/pages;
            rewrite ^(.*)$ /cloud66_maintenance.html break;
        }
        {% else %}
        location / {
            {% if websocket_support == true %}
            # Next three lines implement websocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            {% endif %}
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Proto https;
            proxy_redirect off;
            proxy_pass http://{{ upstream.name }};
            break;
        }
        
        # Add this block only if the whole block belongs to web service.
        # ------BEGIN-USER-DEFINED-----
        {% if service_container.service_name == "web" %}
        # This is the location added for app service
        location / {
            {% if websocket_support == true %}
            # Next three lines implement websocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            {% endif %}
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Proto https;
            proxy_redirect off;
            #Here WEB_APP is the app service captured upstream
            proxy_pass http://{{ WEB_APP }};
            break;
        }
        {% endif %}
        # ------END-USER-DEFINED-----
        {% endif %}
    }
    {% endif # if upstream.port.https != blank %}
    {% endif # if allow_ssl == true %}

    {% endfor # service_container.upstreams %}
    {% endfor # service_containers %}
    }

    ...
}

I hope this help you understand what is happening in the config.

This is just a very simple example to showcase Cloud 66's Nginx config template.

Heads up:

If client use http://example.com to use your website in the example above the app container will receive http://example.com/app not http://example.com/

Happy coding with nginx and containers!


Try Cloud 66 for Free, No credit card required