The best way to understand this topic is with an example. Let's start an application in a Docker container.
The application runs inside the container on port 8080 and should be accessible on the Docker host at 9090. We deviate in the
example from the default port 8080 of the host, so that one can better understand the ports of host and container in our
example. The start is done via the following command:
docker run -p 9090:8080
Now we can list the running containers with the following command:
docker ps
eb77b9f37140 … 0.0.0.0:9090->8080/tcp
The listing usually contains Container ID, Image, Command, Created, Status, Ports and Names. For us are interesting in the
especially the ports. In the example we get the route 0.0.0.0:9090, but we didn't specify it like this. specified.
Doesn't matter - it's standard, isn't it? Well, it depends on where we are, because 0.0.0.0 means, that the port is open for all
addresses. To better understand what the problem is, we need to look at the network layer.
Network interfaces
The following figure illustrates the structure of a non-Docker-enabled host system. That is, Docker is running in a VM on the
host. First, we have our container - considered on its own. So the whole thing is quite simple for now:
The network interface eth0 as well as lo (loopback) are open for port 8080. eth0 offers this interface to the outside.
The next figure shows the communication of the docker-engine. The docker-engine manages the containers and provides the
interface for communication - docker0. The container is connected to docker0 via another virtual interface vethXY. Thus,
requests travel to docker0, via vethXY finally to the eth0 interface of our container.
In order for our request to be routed to docker0 docker-proxy are used. These listen on the desired ports and forward
accordingly:
… /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 9090 -container-ip 172.17.0.2
-container-port 8080
Here we see now also where the magic 0.0.0.0 comes from. To clarify the problem again, the following figure shows the structure
of the host machine:
The IP 0.0.0.0 is attached to the interface of the VM and is not released to the outside. Thus, we can access the application
under the port from our browser, but a third party from the outside cannot. The port 9090 is not shared directly on our host
machine but in the VM.
Now we move our test application to a Production Server from the cloud, a Linux-based, docker-enabled host:
The VM is gone since our host can run docker directly. Now the docker proxies listen directly on our host interface and expose
ports that we might not want to be visible to the outside. We can check this using nmap
nmap my.server -p 9090
Starting Nmap 7.80 ( https://nmap.org ) at 2020-05-28 14:40 CEST
Nmap scan report for my.server
Host is up (0.056s latency).
PORT STATE …
9090/tcp open …
Port 9090 is therefore open to the whole world - consciously or unconsciously. This should be prevented, otherwise there is a
potential security gap. The already mentioned lo (loopback) interface back is suitable for this.
Secure the ports
The whole thing is not very difficult. We simply tell the container to listen to the lo interface instead of 0.0.0.0.
docker run -p 127.0.0.1:9090:8080
With this restriction, the container is still accessible inside our system on port 9090, but it is blocked to the outside. Let's
check this again:
ps -ef | grep docker-proxy
… /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 9090 -container-ip 172.17.0.2 -container-port 8080
nmap my.server -p 9090
Starting Nmap 7.80 ( https://nmap.org ) at 2020-05-28 15:04 CEST
Nmap scan report for my.server
Host is up (0.061s latency).
PORT STATE …
9090/tcp closed …
Port 9090 is therefore closed to the outside and only accessible internally. This already looks much better.
Conclusion
Getting started in the docker world is easy and makes it possible to provide services in a quick and simple way. To make this is
also so simple, a lot happens under the hood, which can, however, lead to surprises. If you leave a secure test environment, you
have to deal with the issue of security anyway. Opening the ports to the outside is certainly not always intentional and
therefore a security risk if you use docker without verifying the way common tutorials do it.