Exposing and publishing ports

As we've seen in the previous examples, exposing services living in containers to the outside world is a critical component of Docker. Up until this point, we've let the images and the Docker engine do the heavy lifting for us in terms of the actual port mapping. To do this, Docker uses a combination of metadata from container images as well as a built-in system for tracking port allocations. In this recipe, we'll walk through the process for defining ports to be exposed as well as options for publishing ports.

Getting ready

You'll need access to a Docker host and an understanding of how your Docker host is connected to the network. In this recipe, we'll be using the docker1 host that we used in previous recipes. You'll want to make sure that you have access to view iptables rules to verify netfilter policies. If you wish to download and run example containers, your Docker host will also need access to the Internet. In some cases, the changes we make may require you to have root-level access to the system.

How to do it…

While often confused, exposing ports and publishing ports are two totally different actions. Exposing ports is really just a way of documenting what ports a container might offer services on. These definitions are stored in the container metadata as part of the image and can be read by the Docker engine. Publishing ports is the actual process of mapping a container port to a host port. This can either be done automatically using the exposed port definitions, or it can be done manually without the use of exposed ports.

Let's first discuss how ports are exposed. The most common mechanism for exposing ports is to define them in an image's Dockerfile. When you build a container image, you're given the opportunity to define ports to be exposed. Consider this Dockerfile definition that I used to build some of the demo containers for this book:

FROM ubuntu:12.04
MAINTAINER Jon Langemak [email protected]
RUN apt-get update && apt-get install -y apache2 net-tools inetutils-ping curl
ADD index.html /var/www/index.html
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
EXPOSE 80
CMD ["/usr/sbin/apache2", "-D", "FOREGROUND"]

As part of the Dockerfile, I can define ports I wish to expose. In this case, I know that Apache will, by default, offer its web server on port 80 so that's the port I wish to expose.

Note

Note that, by default, Docker always assumes the ports you're referring to are TCP. If you wish to expose UDP ports, you can do so by including the /udp flag at the end of the port definition. For instance, EXPOSE 80/udp.

Now, let's run a container built with this Dockerfile to see what happens:

user@docker1:~$ docker run --name web1 -d jonlangemak/web_server_1
b0177ed2d38afe4f4d8c26531d00407efc0fee6517ba5a0f49955910a5dbd426
user@docker1:~$
user@docker1:~$ docker port web1
user@docker1:~$

As we can see, despite having a defined port to expose, Docker has not actually mapped any ports between the host and the container. If you recall from earlier recipe where a container provided a service, we included the -P flag in the docker run command syntax. The -P flag tells Docker to publish all exposed ports. Let's try running this container with the -P flag set:

user@docker1:~$ docker run --name web1 -d -P jonlangemak/web_server_1
d87d36d7cbcfb5040f78ff730d079d353ee81fde36ecbb5ff932ff9b9bef5502
user@docker1:~$
user@docker1:~$ docker port web1
80/tcp -> 0.0.0.0:32775
user@docker1:~$

Here, we can see that Docker has now automatically mapped the exposed port to a random high port on the host. Port 80 will now be considered published.

In addition to exposing ports through the image Dockerfile, we can also expose them at container runtime. Any ports that are exposed in this manner are combined with the ports exposed in the Dockerfile. For example, let's run the same container again and expose port 80 UDP as part of the docker run command:

user@docker1:~$ docker run --name web1 --expose=80/udp 
-d -P jonlangemak/web_server_1
f756deafed26f9635a3b9c738089495efeae86a393f94f17b2c4fece9f71a704
user@docker1:~$
user@docker1:~$ docker port web1
80/udp -> 0.0.0.0:32768
80/tcp -> 0.0.0.0:32776
user@docker1:~$

As you can see, we have published not only the port from the Dockerfile (80/tcp) but also the port from the docker run command (80/udp).

Note

Exposing ports at container runtime allows you some extra flexibility as you can define port ranges to be exposed. This is not currently possible during image creation with the Dockerfile expose syntax. When exposing a wide range of ports, you can filter the output of the docker port command by adding the container port you are looking for to the end of the command.

While the expose method is certainly handy, it doesn't solve all of our needs. For cases where you want more control over ports and interfaces used, you can bypass expose and directly publish ports when starting a container. While passing the -P flag publishes all exposed ports, passing the -p flag allows you to specify specific ports and interfaces to use when mapping ports. The -p flag can take several different forms with the syntax looking like this:

–p <host IP interface>:<host port>:<container port>

Any of the options may be omitted with the only required field being the container port. For example, here are a couple of different ways you could use this syntax:

  • Specify the host port and container port:
    –p <host port>:<container port>
  • Specify the host interface, host port, and container port:
    –p <host IP interface>:<host port>:<container port>
  • Specify the host interface, have Docker choose a random host port, and specify the container port:
    –p <host IP interface>::<container port>
  • Specify only a container port and have Docker use a random host port:
    –p <container port>

All the published ports we've seen up until this point have used a destination IP address of (0.0.0.0), which means that they are bound to all IP interfaces of the Docker host. By default, the Docker service always binds published ports to all host interfaces. However, as we'll see in the following recipe of this chapter, we can tell Docker to use a specific interface by passing the Docker service the --ip parameter.

Given that we can also define which interface to bind published ports to as part of the docker run command, we need to know which option takes priority. The general rule is that any option defined at container runtime wins. For instance, let's look at an example where we tell the Docker service to bind to the 192.168.10.101 IP address of the docker1 host by passing the following option to the service:

--ip=10.10.10.101

Now, let's run a container in a couple of different ways and see the outcome:

user@docker1:~$ docker run --name web1 -P -d jonlangemak/web_server_1
629129ccaebaa15720399c1ac31c1f2631fb4caedc7b3b114a92c5a8f797221d
user@docker1:~$ docker port web1
80/tcp -> 10.10.10.101:32768
user@docker1:~$

In the preceding example, we see the expected behavior. Ports that are published are bound to the IP address specified in the service level --ip option (10.10.10.101). However, if we specify an IP address at container runtime, we can override the service-level settings:

user@docker1:~$ docker run --name web2 -p 0.0.0.0::80 
-d jonlangemak/web_server_2
7feb252d7bd9541fe7110b2aabcd6a50522531f8d6ac5422f1486205fad1f666
user@docker1:~$ docker port web2
80/tcp -> 0.0.0.0:32769
user@docker1:~$
We can see that we specified a host IP address of 0.0.0.0, which will match all the IP addresses on the Docker host. When we check the port mapping, we see that the 0.0.0.0 specified in the command overrode the service-level default.

It's possible that you won't find a use for exposed ports and instead rely solely on manually publishing them. The EXPOSE command is not a requirement of the Dockerfile for image creation. Container images that don't define an exposed port can be directly published as shown in the following commands:

user@docker1:~$ docker run --name noexpose -p 0.0.0.0:80:80 
-d jonlangemak/web_server_noexpose
2bf21219b45ba05ef7169fc30d5eac73674857573e54fd1a0499b73557fdfd45
user@docker1:~$ docker port noexpose
80/tcp -> 0.0.0.0:80
user@docker1:~$

In the preceding example, the container image jonlangemak/web_server_noexpose is a container that does not expose any ports as part of its definition.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset