How good is your understanding of what the
EXPOSE instruction does in a Dockerfile?
Do you know the difference "expose" and "publish"?
Getting two containers to talk to each other should be a simple task, and yet, as a newbie to the Docker ecosystem, this is harder than it seems.
Do you have to use
-p? Or maybe both?
Bridges, interfaces, ports, tunnelling… Networking is hard.
What if — instead of guessing at the problem, and hoping you'll magically find a solution — you can confidently make two containers talk to each other.
By the end of this article, you'll understand the difference between exposing a port and publishing a port. You'll find out why
EXPOSE doesn't lead to the result you want, and what else you can use it for.
Let's start by getting the most common misconceptions out of the way.
Exposing a port on the container doesn't make it accessible from the public network.
To be able to reach a container from the public network, you have to publish its port(s) to the host network with the
Exposing a port won't publish any ports. If you have a web server listening on port 80, you still need to make sure to publish and map the port to a port on the host network.
You can only publish ports at container run time. Docker will then check if the specified ports on the host are available. As an image builder, you don't know which ports will be available on the host, so it doesn't make sense to specify that in a Dockerfile.
If a published port isn't exposed, Docker will automatically expose the port for you. A published port is by definition an exposed port. The reverse isn't true — an exposed port isn't always a published port.
Pop quiz! Container A and container B are running on the same bridge network. Container A exposes port 80 and container B doesn't expose any ports. What type of communication is possible between the two containers?
A) A cannot talk to B, and B cannot talk to A
B) A can talk to B, but B cannot talk to A
C) A cannot talk to B, but B can talk to A
D) A can talk to B, and B can talk to A
Go ahead. I'll wait.
If you answered C, you lost. The right answer is D.
Both containers can talk to each other, regardless whether they expose any ports or not.
That's because containers connected to the same bridge network automatically open all ports to each other. This is true for the default bridge network provided by Docker, and for any user-defined bridge network with the default settings.
Exposing a port makes no difference in this case.
If you're trying to figure out why your web server cannot communicate with the database, you can discard this as a possible issue. Better yet, if you exposed a port in your DB container hoping that your web server can make a connection, get rid of it now to avoid confusion. Instead, you might want to try these 4 troubleshooting steps.
It's easy to think that if you don't expose a port, a container is more secure. No exposed ports means a smaller attack surface right?
Remember from the previous example that exposing a port is not the same as opening it. An exposed port doesn't automatically mean it will be open at runtime. The reverse is also true — for a port to be open, it doesn't have to be exposed at build time.
The networking capabilities of a container are established at run time, instead of build time. The person who built the image has no control over the network in which the container will run. It's the responsibility of the person running the container to secure it from the outside world.
Therefore, not using
EXPOSE doesn't restrict access to a container. You shouldn't rely on it to make it more secure.
EXPOSE doesn't do any of the above things, then what does it do?
EXPOSE is a way of providing documentation to an image. The instruction inside a Dockerfile is part of the image metadata.
EXPOSE is how the person who built the image communicates to the person who will run the container, which port the service inside the container will listen to. The person running the container then knows which ports to publish.
You can view the configuration and metadata for an image or container with
docker inspect. When ports are exposed, they show up inside the configuration JSON. If any tools or scripts rely on that, exposing a port is a useful way to have them pick that information up.
As an image consumer, this information can be useful in certain situations. When you pull a database image (Redis, PostgreSQL, etc.) from the registry, you can inspect it and find out which ports you need to publish.
If you run a container with the
--publish-all) flag, Docker will grab all exposed ports and map them to random high-order ports on the host.
-P flag can be useful when you don't want to poke into the image to figure out what ports you need to publish. To find out which ports Docker has picked at random, you can run
docker port or
As far as I know, this is the only use case for
EXPOSE where it does something practical instead of only providing documentation.
The verb "expose" can be confusing and misleading in what it does. Intuitively, when I hear a port is exposed, I think that port is open and the container is accessible to the outside world. That's not true.
By now, you know that exposing a port doesn't do much in a practical sense. When most people talk about making a container accessible from the host, they mean publishing a port.
You know that all it takes for two containers to be able to talk to each other is to connect them to the same bridge network.
If you were using
EXPOSE for the things it doesn't do, then I encourage you to open up your Dockerfiles now and remove it. Save your future self and others from unnecessary confusion.
If you're using Docker Desktop for Mac/Windows, and want to connect to the container from the host, you also need to publish and map the ports. Because Docker uses a VM on these operating systems, the docker0 bridge network isn't visible to the hosts. On Linux, containers are accessible from the host without the need to publish their ports. ↩︎
When you create a network, you can pass additional settings to the bridge network driver. One such option is Inter Container Connectivity, which — as you guessed — configures whether containers should have their ports open to each other. This option is enabled by default, but you can disable it. ↩︎