Flexible, automagical reverse proxy for Docker.
RProxy goes beyond nginx-proxy to provide a more versatile
solution that covers both multiple HTTP services on multiple
ports, as well as TCP services. It can also distribute incoming
HTTP traffic based on a path prefix or hostnames.
This probably shouldn't be used for medium- or large- scale
deployments, or for anything where high availability, and other
entreprise/production concerns, are needed. For these, better
solutions such as Consul, etcd, Serf, SkyDNS,
SmartStack, ZooKeeper… should be used.
Just run the docker image:
$ docker pull passcod/rproxy $ docker run -d --net=host -v /var/run/docker.sock:/var/run/docker.sock passcod/rproxy
If you are using Docker 1.1.x and below, there is a bug in libcontainer
--net=host to be used, so you'll need to either upgrade
to Docker 1.2.0 or above, or run the Docker daemon using
Additionally, it exposes
1/tcp by default and makes the HAProxy
statistics HTTP service available from there at all times, so
you may want to firewall this.
RProxy recognises containers that advertise a
variable. This is formatted as a URL. There can be multiple such
URLs, separated by commas.
tcp. Behind the scenes, this
sets HAProxy's mode. More information on the low-level is
available further below.
hostis used to differentiate HTTP services. This should be
set to the actual hostname the service will be used at, as
traffic will be filtered according to this, so setting it to
placeholder names will not match and give confusing results.
The matching is done as a suffix, so
foo.example.com. For TCP services, host has no effect,
but should still be provided to use as an identifier.
portis both the service's port inside the container and
outside RProxy. I consider there to be little reason to use a
different port, so that's not an option. For TCP, this is the
only way to differentiate between services — TCP services on
the same port will be load-balanced, regardless of the
If the port is omitted, it defaults to 80 for HTTP and 4 for
TCP (the first unassigned common port).
pathis used as a further filter for HTTP services. It
matches a prefix path. It has no effect on TCP services.
# Directs HTTP traffic for host example.com and port 80 to # this drunk/mayer instance. $ docker run -Pde RPROXY=http://example.com drunk/mayer # Directs HTTP traffic for host example.com and port 8080 to # this sharp/galileo instance. $ docker run -Pde RPROXY=http://example.com:8080 sharp/galileo # Directs TCP traffic for port 666 to this cranky/doom instance. $ docker run -Pde RPROXY=tcp://main.doom:666 cranky/doom # Load-balances HTTP traffic for host api.example.com port 80 # to these angry/morse instances. $ docker run -Pde RPROXY=http://api.example.com angry/morse $ docker run -Pde RPROXY=http://api.example.com angry/morse $ docker run -Pde RPROXY=http://api.example.com angry/morse # Directs HTTP traffic for example.com:80/new/... to # this loving/fermat instance. $ docker run -Pde RPROXY=http://example.com/new loving/fermat # An IRC bouncer $ docker run -Pde RPROXY=tcp://znc:6667,tcp://znc:6697 mad/znc # A SSHd on multiple ports for e.g. firewall punching $ docker run -Pde RPROXY=tcp://ssh:22,tcp://ssh:443 jovial/sshd
Under the covers
RProxy uses HAProxy with a configuration generated using
docker-gen and a custom Ruby script via a docker-gen-to-YAML
template, so that whenever a container is started or stopped,
the configuration changes and HAProxy is reloaded.
Each scheme (TCP and HTTP) is given a single
section, bound to every port necessary for that scheme, and from
acls are used to route things around. Each
has a small memory cost, but uses negligible additional CPU. The
difference between the frontends come from the diversity of the
routing criterion (TCP uses port filters only, while HTTP may
use both port, host, and path filters), and from the speed
of the matching. TCP is faster. When using HTTP mode, the
entire buffer has to be waited on and parsed before the filters
can be checked, as these are Layer 7 concerns. Ports are a
Layer 4 concern, and can be checked earlier, without waiting for
nor parsing a full buffer.
Each config (a config is a distinct
item and its associated containers) is given a single
and the associated containers are listed within that as
to be load-balanced (by default, using the
That's all there is to the proxy side. The Docker side has a few
The RProxy image is run with
--net=host, which means it uses
the host's network stack. That makes it dead simple to set up
for simple infrastructure, as you don't need to mess with
anything else to get it to listen to the outside world. In more
complex scenarios you may want not to use
instead use your own networking solution. Be aware however in
that case that the image only
1/tcp, and that
ports used will change depending on which containers run.
For docker-gen to pick up the IP address of a container, it
must have an exposed or published port. It doesn't matter
which, and it doesn't even matter if that isn't the correct
port (which is why the fake examples above use
operates only using explicit instructions, i.e. if a config
isn't specified in the
RPROXYenv variable for the port you
want RProxy to handle, it won't magically pick it up. It won't
even bother guessing. You have to tell it to.
The host and path filters, and really the entire HTTP stack,
are there mostly for convenience. If you need truly flexible
reverse proxying or filtering for some ports, it is completely
ok to have a secondary reverse proxy (e.g. nginx) sitting behind
RProxy. However, you'll be in charge of routing things to the
containers they belong to yourself, unless you want to be crazy
and route the secondary proxy's traffic back into RProxy.
That's probably totally possible but really really untested.
- RProxy is released in the Public Domain!
- Pull requests are welcome!
- Comments and bug reports are awesome!
- This is my first docker-related project and may be full of bugs!