When you use the “ports” setting – whether in docker-compose or docker run – to publish ports to the host, you actually have a choice between publishing those ports on the loopback interface (127.0.0.1) or all interfaces (0.0.0.0). By default, i.e. if only a port is specified on the host side of the equation, the port is published on all interfaces, and so accessible from both the host itself and from the network.
Very little is made of this fact on the docker-compose file documentation. In fact, the only way to tell, is to look at the examples given. Which makes me think I might be off on a strange tangent but I can’t find any fault with my thinking, so for what it’s worth, I’m sharing. If you’re shaky on publish, expose and what exactly one or the other entails, see Lou Martin Caraig’s excellent explanation.
Why does it matter? When would you use this? Isn’t the entire point of publishing ports to make what you’re publishing accessible to the world?
Yes, and no. If you use a reverse proxy in front of the applications being published, you only really want the application to be accessed via the proxy. If the published ports are on 0.0.0.0 (and not shielded by firewall rules), the option is there for visitors to sidestep the proxy and talk directly to the application.
Is that a concern? Short answer: I don’t know. It certainly creates opportunities for evasion, e.g. if I use the proxy’s log as the main indicator of what traffic I’m seeing. In addition, many applications won’t react well to being called with a not-configured-for scheme or port and maybe an IP address instead of a host name. Some simply suggest a misconfiguration, others start spilling errors and revealing configuration settings. Very helpful, but also a bit indiscreet.
It goes without saying that this should probably be covered by firewall rules anyway. Which may explain why there is so little said about it. Even so, this solution is simple and elegant and takes five minutes to implement. A fair bit faster than learning iptables, if you haven’t gotten round to it yet.
Here’s how. If your “port” section is set up to forward port 80 on the container to port 8080 on the host, like so:
ports: - "8080:80"
docker port command reveals…
$ docker port my_container_name 80/tcp -> 0.0.0.0:8080
But if you make it explicit that you want publish-to-local, like so (yes, they’re are two colons in that line, apparently docker knows which means what):
ports: - "127.0.0.1:8080:80"
Then you can have
docker port confirm that it is indeed only available on localhost:
$ docker port my_container_name 80/tcp -> 127.0.0.1:8080
Compare before-and-after with access both through and around the proxy and you should find that direct requests now go unanswered.