1. Overview

When we dockerize our applications, we usually need to expose one port. The application uses that port to interact with other containers or the outside world. Sometimes, one port is not sufficient. One or more additional ports may be required to serve other purposes. For example, in a Spring Boot application, we need a separate port to publish management endpoints to monitor the application using the actuator.

In this article, we’ll see how to declare more than one port to expose and how to bind the exposed ports with the ports of the host computer in order to achieve the above.

2. Declaring Ports

First, we need to declare the ports to be exposed. We can do that while building the docker image. It’s also possible to declare ports while running a container based on the image. Let’s see how we do that.

We’ll start with an example of a sample Spring Boot application – my-app. Throughout the article, we will use the same example to understand the concepts. Our application has only one GET endpoint, which returns “Hello buddy“. It also has the Spring actuator enabled. The application runs on port 8080, and the management endpoints run on port 8081. So, when the application is running on the local computer, these commands work:

$ curl http://localhost:8080
Hello buddy

$ curl http://localhost:8081/actuator/health
{"status":"UP"}

2.1. Declaration in Dockerfile

As our application my-app publishes its endpoints in two ports, 8080 and 8081, we need to expose both ports in our Dockerfile. The EXPOSE verb in the Dockerfile exposes ports:

FROM openjdk:8-jdk-alpine
EXPOSE 8080
EXPOSE 8081
ARG JAR_FILE=target/my-app-0.1.jar
ADD ${JAR_FILE} my-app.jar
ENTRYPOINT ["java","-jar","/my-app.jar"]

However, when we build the image using this Dockerfile:

$ docker build -t my-app:latest .

This doesn’t actually open the ports because the author of the Dockerfile has no control over the network the container will be running on. Rather, the EXPOSE command acts as documentation. With this, the person who runs a container understands which ports of the container need to be published in the host computer to communicate with the application.

We can also specify the protocol – TCP or UDP – for communicating on this port:

EXPOSE 8080/tcp
EXPOSE 8081/udp

If we don’t specify anything, it takes TCP as default.

The command also supports port declaration in a range:

EXPOSE 8000-8009

The above command tells that the application needs to open 10 ports starting from 8000 to 8009 for communication.

2.2. Declaration in docker run Command

Let’s assume we already have a docker image for my-app that exposes only one port 8080 using the EXPOSE command in its Dockerfile. Now, if we want to expose the other port, 8081, we should use the –expose parameter along with the run command:

$ docker run --name myapp -d --expose=8081 my-app:latest

The above command runs a container named myapp from the image my-app and exposes 8081 along with port 8080. We can check this using:

$ docker ps
CONTAINER ID   IMAGE           COMMAND                  CREATED             STATUS              PORTS              NAMES
2debb3c5345b   my-app:latest   "java -jar /my-app.j…"   5 seconds ago       Up 3 seconds        8080-8081/tcp      myapp

It is important to understand that this parameter only exposes – but does not publish – the ports in the host computer. To understand it more clearly, let’s execute:

$ docker port myapp

This doesn’t print anything because no ports have been opened and mapped in the host computer. Therefore, we’re not able to access the application even though it’s running inside the container:

$ curl http://localhost:8080
curl: (7) Failed to connect to localhost port 8080: Connection refused

We may also opt to expose a range of ports in the same way:

$ docker run --name myapp -d --expose=8000-8009 my-app:latest

3. Publishing Ports

We’ve already learned to expose ports for a dockerized application. Now it’s time to publish them.

3.1. Publishing in Run Command

Let’s reuse the example of the my-app image from the previous section. It has two ports exposed in its Dockerfile8080 and 8081. While running the container based on this image, we can publish all the exposed ports at once using the -P argument:

$ docker run --name myapp -d -P myApp:latest

The above command opens two random ports in the host computer and maps them with ports 8080 and 8081 of the Docker container. It behaves the same way if we expose a range of ports.

To check the mapped ports, we use:

$ docker port myapp
8080/tcp -> 0.0.0.0:32773
8081/tcp -> 0.0.0.0:32772

Now, the application is accessible using port 32773, and the management endpoints are accessible through 32772:

$ curl http://localhost:32773
Hello buddy
$ curl http://localhost:32772/actuator/health
{"status":"UP"}

Instead of allocating random ports, we may choose specific ports in the host computer by using the -p parameter:

$ docker run --name myapp -d -p 80:8080 my-app:latest

The above command publishes only port 8080 and maps with port 80 in the host server. It doesn’t make the actuator endpoints accessible from outside the container:

$ curl http://localhost:80
Hello buddy
$ curl http://localhost:8081/actuator/health
curl: (7) Failed to connect to localhost port 8081: Connection refused

To publish multiple port mappings, we use the -p parameter multiple times:

$ docker run --name myapp -d -p 80:8080 -p 81:8081 my-app:latest

This way, we also keep control of which ports of the container opens up to the outside.

3.2. Publishing in docker-compose

If we use our application in docker-compose, we can provide a list of ports that needs to be published in the docker-compose.yml file:

version: "3.7"
services:
  myapp:
    image: my-app:latest
    ports:
      - 8080
      - 8081

If we launch this setup, it assigns random ports of the host server with the given ports:

$ docker-compose up -d
Starting my-app_myapp_1 ... done
$ docker port  my-app_myapp_1
8080/tcp -> 0.0.0.0:32785
8081/tcp -> 0.0.0.0:32784

However, it’s possible to provide the specific choice of ports:

version: "3.7"
services:
  myapp:
    image: my-app:latest
    ports:
      - 80:8080
      - 81:8081

Here, 80 and 81 are the ports of the host machine, whereas 8080 and 8081 are the container ports.

4. Conclusion

In this article, we have discussed different ways of exposing and publishing more than one port in a Docker container. We have seen what exposing actually means and also how to gain full control over the publishing of the ports between the container and the host computer.

Comments are closed on this article!