Elevating IaC Workflows with Spacelift Stacks and Dependencies 🛠️

Register for the July 23 demo →

Docker

How to Expose a Docker Port – Tutorial & Examples

docker expose

Spacelift and Docker

Customize your workflow by bringing your own Docker image and using it as a runner to accelerate deployments that leverage third-party tools.

Book a demo

Docker’s networking model isolates your containers from your host system. Containers exist in their own networking namespaces and have a separate port range.

Docker doesn’t automatically expose ports outside of containers. To access a container over your host’s network, you must expose and publish the target container port yourself. This allows you to then direct traffic to the container from your host’s networking interfaces.

Dealing with Docker ports can become confusing due to subtleties in the terminology. In this article, you’ll learn what it means to expose a port and how it differs from the separate publish (also known as bind) operation. We’ll show some detailed examples for both exposing and publishing container ports.

We will cover:

  1. What is exposing a port in Docker?
  2. How to expose a port in a Dockerfile
  3. How to expose ports when creating a Docker container
  4. Exposing Docker ports – example
  5. How to expose multiple Docker ports
  6. How to publish exposed ports

What is exposing a port in Docker?

Exposing a Docker port means advertising it as actively used by the containerized workload. Docker shows information about the ports that containers expose, allowing you to decide which ports you’ll bind to your host.

It’s important to realize that exposing a port does not automatically publish it to your host. In Docker, exposed ports are purely metadata. They document the ports that containerized apps listen on.

For example, a Dockerfile for a web server could include the following instruction:

EXPOSE 80

This means the app running in the container listens on port 80, so the user should bind that port to their host in order to access the server from outside the container. However, the instruction does not result in a port being automatically bound when the container starts.

Exposing ports can appear redundant to some Docker newcomers, as you can publish ports without exposing them first. However, expose remains an important documentation mechanism that makes your containers easier to manage and reason about.

Exposed port listings instantly tell you which ports should be bound to a container while also revealing any host ports that have been unnecessarily assigned to containers that won’t use them. It’s also possible to automatically publish all exposed ports when you start a container, which saves time when deploying new app instances.

Exposing a port vs publishing a port

As we’ve outlined above, exposing a port is a passive action that merely indicates a container is capable of listening to that port. External communications to the port must be separately enabled by publishing it to your host. This process maps a host port number to the port inside the container.

Users—and other tutorials—frequently conflate these processes. Publishing a port is often described as “exposing” it because it makes the port accessible via your host’s networking stack. However, this terminology is inaccurate because it overlooks the possibility of either exposing a Docker port without publishing it or publishing a port that is not exposed.

  • Expose a port: Set metadata that indicates an app or service in the container will listen on that port.
  • Publish/bind a port: Allocate a host port to a container port, allowing you to communicate with the container from your host’s networking interface.

Now let’s study some examples of exposing and publishing Docker ports.

How to expose a port in a Dockerfile

There are two main ways to expose a port in Docker—either within a Dockerfile, using the EXPOSE instruction shown above, or by setting the --expose flag when you start a container with docker run.

To expose a port using a Dockerfile, you should use the EXPOSE instruction for each port that you want to advertise.

EXPOSE 80

By default, the port will be exposed with TCP. You can expose UDP by explicitly requesting it:

EXPOSE 80/udp

To expose a port with both TCP and UDP, repeat the EXPOSE instruction twice, once for each mode:

EXPOSE 80/tcp
EXPOSE 80/udp

Docker will automatically expose all the listed ports when you start new containers from a Docker image that uses EXPOSE instructions in its Dockerfile.

How to expose ports when creating a Docker container

On occasion, you might want to manually expose a port when starting a container. You can do this by passing the --expose flag to your docker run command.

$ docker run my-container:latest --expose 80

You can repeat the flag to expose multiple ports. Similarly to the EXPOSE instruction shown above, you can also request TCP and/or UDP exposure using the <port>/tcp and <port>/udp syntax. When only a port number is indicated, TCP is selected.

In practice, --expose is infrequently used. The ports a container can listen on normally match those defined in the image’s Dockerfile. Setting additional port exposures when a container’s started isn’t generally helpful, as they’ll have no effect if the application doesn’t actually use those ports.

Exposing Docker ports - example

Here’s a quick demo that shows you how to expose ports, and check their values.

Exposing ports with Dockerfile EXPOSE

The following is a simple Dockerfile that defines an image with an exposed port:

FROM alpine:latest
EXPOSE 80
CMD ["sleep", "300"]

Build your image, then start a container from it:

$ docker build -t demo-image:latest .
$ docker run -d --name demo demo-image:latest

Using Docker run expose

If you don’t have control of the image’s Dockerfile, you can manually expose ports when starting a container instead:

$ docker run -d --name demo-run --expose 443 demo-image:latest

Viewing the exposed ports

To see the ports that are exposed for a container, use the docker ps command:

$ docker ps
CONTAINER ID   IMAGE               COMMAND       CREATED              STATUS                  PORTS             NAMES
2fdfd038a368   demo-image:latest   "sleep 300"   2 seconds ago        Up Less than a second   80/tcp, 443/tcp   demo-run
dd50cd05e515   demo-image:latest   "sleep 300"   About a minute ago   Up About a minute       80/tcp            demo

How to expose multiple Docker ports

The containers running on your host will be displayed. The ports each container has exposed are shown in the PORTS column of the output.

As we’ve seen above, you can expose multiple ports for a container by repeating the EXPOSE Dockerfile instruction or --expose flag for docker run.

In cases where both EXPOSE and --expose apply to a single container, all the referenced ports will be exposed. You can see this in the demo output from docker ps above, where the demo-run container exposes both port 80 (from its image’s Dockerfile) and port 443 (from the docker run command line).

How to publish exposed ports

Exposed ports need to be published to your host before external traffic can reach your container. You need to map the container’s ports to host port numbers. This is achieved using the -p flag for docker run:

$ docker run -d --name nginx -p 80:80 nginx:latest

This example maps your host’s port 80 to port 80 inside the container. You can then reach the containerized app using your host’s network interface:

$ curl http://localhost:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Published ports show in the PORTS column of the docker ps command’s output. Unlike exposed ports—which only show the port number used by the container, such as 80/tcp—published ports show the mapping between the host port and the container port:

$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED             STATUS             PORTS                               NAMES
cafc1f042132   nginx:latest   "/docker-entrypoint.…"   About an hour ago   Up About an hour   0.0.0.0:80->80/tcp, :::80->80/tcp   nginx

Remapping published ports

The port you assign from your host doesn’t have to be the same one that the container listens to. In this example, you use host port 8080 to connect to the container port 80:

$ docker run -d --name nginx -p 8080:80 nginx:latest
$ curl http://localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Publishing ports to a specific host interface (e.g. localhost)

The port publishing examples shown above bind the container port to all the networking interfaces on your host. This makes the container accessible to the other devices on your network.

This can be a security risk for services that should only be exposed to neighbors running directly on your host. Fortunately, you can publish ports to specific host interfaces using host_address:host_port:container_port syntax:

$ docker run -d --name nginx -p 127.0.0.1:8080:80 nginx:latest

In this example, the port 8080 on the Docker host 127.0.0.1 (i.e. localhost) is mapped to port 80 in the container. Traffic that arrives at the port from a different IP address—such as your host’s public network interface—won’t reach the container.

Automatically publishing exposed ports

So far, our port expose and publish demos have been completely independent of each other. We exposed ports and published them using two manual steps. You can publish any port—whether it’s exposed or not—or you can choose not to publish already exposed ports.

However, as exposing a port signals that the container uses it, in most cases, you will want to publish all the ports that a container exposes. You can achieve this when starting a container with docker run by setting the -P (--publish-all) flag:

$ docker run -d -P --name demo demo-image:latest

This avoids having to check which ports the image exposes, then publish them manually with explicit -p flags. The “publish all” option randomly maps a host port to each of the exposed container ports.

The container’s docker ps output will confirm which port mappings have been assigned:

$ docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED             STATUS             PORTS                                     NAMES
d66f376e3860   demo-image:latest   "sleep 300"              2 seconds ago       Up 1 second        0.0.0.0:32768->80/tcp, :::32768->80/tcp   demo

The container port 80, defined in the EXPOSE instruction in our demo Dockerfile, has been randomly assigned the host port 32768 due to starting the container with the -P flag included.

Key points

This article has walked you through how to expose and publish ports for your Docker containers. To summarize, an exposed port is metadata that indicates the port number is used by the application within the container. Exposed ports are then published to create a binding to your host that makes the container accessible.

Publishing a binding is a runtime operation when containers are started, whereas exposed ports are defined by image authors within Dockerfiles. Understanding this distinction will help you correctly manage ports across your Docker containers and images.

We encourage you also to explore how Spacelift offers full flexibility when it comes to customizing your workflow. You have the possibility of bringing your own Docker image and using it as a runner to speed up the deployments that leverage third party tools. Spacelift’s official runner image can be found here.

The Most Flexible CI/CD Automation Tool

Spacelift is an alternative to using homegrown solutions on top of a generic CI. It helps overcome common state management issues and adds several must-have capabilities for infrastructure management.

Start free trial

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide