Docker: reverse proxy

When running multiple dockerized webservers, instead of a single web server, you can use a reverse proxy to forward requests to the correct containers. When we look at Docker containers each virtual host is a separate container. Every web application has its own container with its own web server instance.

The reverse proxy is listening for incoming HTTP(S) requests and forward them to your containers. The thing we’re looking for:

  • A reverse proxy process
  • A process which knows your web application containers
  • A process which updates your reverse proxy with the correct configuration

Docker-gen knows your containers and will render a configuration file based on a template. However, docker-gen needs to have read access to your Docker socket, because it needs to monitor the start and stop of containers. Before docker-gen can do anything you need to feed it with a Go template. This template will create a configuration file for an nginx reverse proxy:

  • Docker-gen knows your containers
  • Docker-gen will create an upstream / server for each container with a VIRTUAL_HOST environment variable
  • Docker-gen will re-create the config each time you stop / start a container

The template file will go through each container which has the environment variable VIRTUAL_HOST set and print out a relevant upstream{} entry for it. If there are multiple containers running with the same VIRTUAL_HOST, then they will be written to the same entry. This allows us to upgrade containers with zero downtime.

Reverse proxy template

First create a templates directory and place a docker-gen template file (reverse-proxy.tmpl) for nginx there.

# reverse-proxy.tmpl
{{ range $host, $containers := groupBy $ "Env.VIRTUAL_HOST" }}
upstream {{ $host }} {
 
  {{ range $index, $value := $containers }}
      {{ with $address := index $value.Addresses 0 }}
      server {{ $address.IP }}:{{ $address.Port }};
      {{ end }}
  {{ end }}
 
}
 
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
 
server {
    gzip_types text/plain text/css application/json application/x-javascript
               text/xml application/xml application/xml+rss text/javascript;
 
    server_name {{ $host }};
 
    location / {
	proxy_pass http://{{ trim $host }};
        include /etc/nginx/proxy_params;
    }
}
 
{{ end }}

Docker-compose

# docker-compose up -d (-d = run services in background)
# docker-compose.yml
# Customise the paths of the volumes for your own needs
version: '2'
 
services:
  proxygen:
    image: jwilder/docker-gen
    container_name: proxygen
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./templates:/templates
      - ./conf/conf.d:/conf
    command: -watch -notify-sighup=proxy /templates/reverse-proxy.tmpl /conf/proxy.conf
 
  proxy:
    image: nginx
    container_name: proxy
    volumes:
      - ./conf/conf.d:/etc/nginx/conf.d:ro
    ports:
      - '80:80'
      - '443:443'

Adding upstream servers

When you start a new container you can easily add the following environment variables:

  • VIRTUAL_HOST sets the virtual hostname of your service

Whenever you start a container with the VIRTUAL_HOST environment variable, the proxy container will forward all requests belonging to this hostname to your container.

Directory structure