Using Nginx to Reverse Proxy Docker Containers

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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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.