Tim's Blog

Information, Technology, Security, and other stuff.

Using Nginx to Reverse Proxy Docker Containers

Published 2017-04-11

I was mucking around with configuring nginx containers and binding containers to dedicated IP addresses just so I could expose the same web ports, but then I found this amazing Nginx proxy container made by Jason Wilder, and my life has been so much easier ever since.

The easiest way to show how it works is with a docker-compose file:

version: '2'
services:
  nginx-proxy:
    container_name: nginx-proxy
    image: jwilder/nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

This is all it takes to make a basic reverse proxy. The key thing to note here is the volume passed through: this container will watch all of your other containers through the docker socket to automagically configure the proxy for you.

So, if we want to reverse proxy some services, all we need to do is put a VIRTUAL_HOST environment variable onto each host:

version: "3"

services:
  nginx-proxy:
    container_name: nginx-proxy
    image: jwilder/nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

  ghost:
    container_name: ghost
    image: ghost
    volumes:
       - ghost_data:/var/lib/ghost
    environment:
       - VIRTUAL_HOST=ghost.example.com
       - VIRTUAL_PORT=2368
    restart: always

  wordpress:
    container_name: wordpress
    image: bitnami/wordpress:latest
    volumes:
      - wordpress_data:/bitnami/wordpress
      - apache_data:/bitnami/apache
      - php_data:/bitnami/php
    environment:
      - WORDPRESS_USERNAME=example
      - WORDPRESS_PASSWORD=changeme
      - WORDPRESS_EMAIL=foo@example.com
      - WORDPRESS_FIRST_NAME=Foo
      - WORDPRESS_LAST_NAME=Bar
      - WORDPRESS_BLOG_NAME=Foo Blog
      - WORDPRESS_DATABASE_PASSWORD=changeme
      - WORDPRESS_DATABASE_USER=wpuser
      - WORDPRESS_DATABASE_NAME=wp
      - VIRTUAL_HOST: wordpress.example.com
    depends_on:
      - mariadb
    restart: always

  mariadb:
    container_name: mariadb
    image: bitnami/mariadb:latest
    environment:
      - ALLOW_EMPTY_PASSWORD=no
      - MARIADB_ROOT_PASSWORD=changeme
      - MARIADB_DATABASE=wp
      - MARIADB_USER=wpuser
      - MARIADB_PASSWORD=changeme
    volumes:
      - mariadb_data:/bitnami/mariadb
    restart: always

volumes:
  mariadb_data:
    driver: local
  wordpress_data:
    driver: local
  apache_data:
    driver: local
  php_data:
    driver: local
  ghost_data:
    driver: local

This example has Wordpress (bitnami-provided) and a Ghost blog running side-by-side. Wordpress, by default, listens on port 80 so we can just pass in the VIRTUAL_HOST variable by itself. Ghost, by default, listens on port 2368 so we also have to pass in the VIRTUAL_PORT variable for the proxy to use.

This is all that's needed though. No more messing around with reverse proxy configuration, nor do you need to expose any ports externally from the containers, you only need to open ports 80 & 443 from the proxy itself.

If you want to enable TLS/SSL to the services, then a little more config is required, but that can all be found in the documentation.

There are security issues that do need to be taken into consideration though. The simplicity of the proxy pulling information from the docker socket can lead to a compromised proxy to extract information from the other containers, as well as creating new containers on the host. The example above has the read-only (ro) flag set for the volume, but this only mitigates some risk.

Ultimately to be safe you should only use this in test environments, or on machines that you don't really care about getting pwned, but nevertheless it saves a lot of build and configuration time.