Terminus
Understanding Port Mapping in Docker Compose

Understanding Port Mapping in Docker Compose

The short answer

In Docker, a port refers to a communication endpoint between the host and a container through which a containerized application can send and receive data.

In Docker Compose, the ports of a service can be mapped under the [.inline-code]ports[.inline-code] property as follows:

 version: '3'

services:
  service_name:
    build: .
    ports:
      - "[host:]container[/protocol]"

Where:

  • [.inline-code]host[.inline-code] specifies the host port or port range. If not specified, it uses any available host port.
  • [.inline-code]container[.inline-code] specifies the container port or port range.
  • [.inline-code]protocol[.inline-code] restricts the container ports to specified protocols ([.inline-code]tcp[.inline-code] or [.inline-code]udp[.inline-code]). If not specified, it uses any available protocol.

For example: 

version: '3'

services:
  api:
    build: .
    ports:
      - "8000:8000/udp"

In this example, the UDP port [.inline-code]8000[.inline-code] of the host is mapped to the UDP port [.inline-code]8000[.inline-code] of the [.inline-code]api[.inline-code] service, which means that any requests sent to the host machine on port the [.inline-code]8000[.inline-code] will be forwarded to the port [.inline-code]8000[.inline-code] of the container using the UDP protocol.

[#easily-recall-with-ai] Easily retrieve this syntax using Warp AI feature [#easily-recall-with-ai]

If you’re using Warp as your terminal, you can easily retrieve this syntax using the Warp AI feature:

Entering [.inline-code]docker compose ports[.inline-code] in the AI question input will prompt a human-readable step by step guide including code snippets.

[#map-multiple-ports] Mapping multiple ports at once [#map-multiple-ports]

In Docker, it often happens that a single service runs more than one application, which requires multiple ports to be mapped in order to function correctly.

To map multiple ports at once, you can use the following syntax: 

ports:
  - "[host:]container[/protocol]"
  - "[host:]container[/protocol]"
  - …

For example: 

version: '3'

services:
  website:
    build: .
    ports:
      - "80:80/tcp"
      - "22:22/tcp"

In this example, the HTTP port [.inline-code]80[.inline-code] of the host is mapped to the port [.inline-code]80[.inline-code] of the container, and the SSH port [.inline-code]22[.inline-code] of the host is mapped to the port [.inline-code]22[.inline-code] of the container, both over the TCP protocol.

[#specify-a-port-range] Specifying a port range [#specify-a-port-range]

In Compose, you can use port ranges to specify multiple host ports at once. Docker will then assign the first available host port from the list, followed by the second, and then so on.

To specify a range of host ports, you can use the following syntax:

"[host_start-host_end:]container[/protocol]"

For example:

version: '3'

services:
  api:
    build: .
    ports:
      - "8080-8082:8080"

In this example, Docker will assign the first available host port between [.inline-code]8080[.inline-code], [.inline-code]8081[.inline-code], and [.inline-code]8082[.inline-code] to the container port [.inline-code]8080[.inline-code].

Note that similarly, you can also specify port ranges for the container as well.

For example:

version: '3'

services:
  api:
    build: .
    ports:
      - "8080:8080-8082"

[#map-ports-automatically] Mapping a container port to any available host port [#map-ports-automatically]

Mapping a container port to any available host port is useful when you don’t want to manage host ports manually, such as during local development. By default, if you don’t specify a host port, Docker will map the first unassigned port to the service.

For example:

version: '3'

services:
  api:
    build: .
    ports:
      - "3000"

In this example, the container port [.inline-code]3000[.inline-code] will be mapped to any open and available port on the host.

[#bind-hosts-ports-to-an-ip-address] Binding host ports to a specific IP address [#bind-hosts-ports-to-an-ip-address]

By default, Docker binds the specified host port to all network interfaces (i.e. [.inline-code]0.0.0.0[.inline-code]), which allows services to be accessible from any external IP address.

To bind the host ports to a specific network interface, you can the following syntax:

"[ip_address:host:]container[/protocol]"

For example: 

version: '3'

services:
  api:
    build: .
    ports:
      - "127.0.0.1:8000:8002/udp"

In this example, only the requests originating from the IP address [.inline-code]127.0.0.1[.inline-code], which designates the same machine (or localhost), and sent to the port [.inline-code]8000[.inline-code] of the host over the UDP protocol will be forwarded to the port [.inline-code]8000[.inline-code] of the [.inline-code]api[.inline-code] service.

Note that to verify which ports are exposed, you can use the [.inline-code]docker ps[.inline-code] command to list all the active Docker containers and their respective port mappings.

[#the-long-form-syntax] Mapping ports using the long form syntax [#the-long-form-syntax]

Docker Compose supports a long syntax which provides a more detailed way to specify port mappings as compared to the concise aforementioned short syntax.

You can specify port mappings using the long syntax as follows: 

version: '3'

services:
  api:
    build: .
    ports:
      - target: "<container_port>"
        host_ip: <host_ip>
        published: "<published_port>"
        protocol: [udp|tcp]
        mode: host

Where:

  • [.inline-code]target[.inline-code] specifies the container port or port range. 
  • [.inline-code]host_ip[.inline-code] specifies the host IP address binding. If not set, the host port will be available for all network interfaces ([.inline-code]0.0.0.0[.inline-code]).
  • [.inline-code]published[.inline-code] specifies the host port or port range. 
  • The [.inline-code]protocol[.inline-code] restricts the container ports to specified protocols ([.inline-code]tcp[.inline-code] or [.inline-code]udp[.inline-code]). If not specified, it uses any available protocol. 
  • The [.inline-code]mode[.inline-code] with [.inline-code]host[.inline-code] value specifies that this port configuration is used for publishing a host port on each node. If not set, the [.inline-code]mode[.inline-code] will use the default value [.inline-code]ingress[.inline-code], which is used for load balancing and is not intended for the host port mappings. 

For example: 

version: '3'

services:
  api:
    build: .
    ports:
      -  target: "8080"
         host_ip: 127.0.0.1
         published: "8002"
         protocol: tcp
         mode: host
      -  target: "8080"
         host_ip: 127.0.0.1
         published: "8003-8004"
         protocol: tcp
         mode: host

In the above example, two different host port mappings have been specified for the container port [.inline-code]8080[.inline-code].

Mapping ports using environment variables

In Compose, you can dynamically map ports using environment variables and the variable substitution syntax as follows:

"${VAR:-default}:${VAR:-default}"

Where:

  • [.inline-code]${VAR}[.inline-code] refers to the environment variable named [.inline-code]VAR[.inline-code].
  • [.inline-code]default[.inline-code] is an optional fallback value used in case the [.inline-code]VAR[.inline-code] variable is undefined or empty.

For example:

version: '3'

services:
  api:
    build: .
    ports:
      - "${HOST_PORT}:${CONTAINER_PORT:-8080}”
  webapp:
    build: webapp
    expose:
      - ${CONTAINER_PORT:-8080}

In this example:

  • The [.inline-code]HOST_PORT[.inline-code] environment variable is used to specify the host port value.
  • The [.inline-code]CONTAINER_PORT[.inline-code] environment variable is used to specify the service internal and external ports, with a fallback port value of [.inline-code]8080[.inline-code].

When you run the [.inline-code]docker-compose up[.inline-code] command, the service defined in the Compose file will replace the value of the environment variables.

You can refer to the official documentation to learn how to set environment variables in Docker Compose.

[#expose-ports-internally] Exposing ports internally with [.inline-code]expose[.inline-code] [#expose-ports-internally]

In Docker Compose, the [.inline-code]expose[.inline-code] property is used to define container ports that are exposed for communicating internally with other containers on the same network, and that are not mapped to the host ports.

expose:
  - "<ports>"

Where:

  • [.inline-code]ports[.inline-code] specifies a list of ports or port ranges.

For example:

version: '3'

services:
  api:
    build: .
    expose:
      - "3000"
      - "8000-8002"
  webapp:
    build: webapp
    expose:
      - "3000"

In this example, the [.inline-code]api[.inline-code] service exposes the ports [.inline-code]3000[.inline-code], and port range [.inline-code]8000-8002[.inline-code], and the [.inline-code]webapp[.inline-code] service exposes the port [.inline-code]3000[.inline-code]. These ports can be used for communicating with other containers available on the same network, and it is secure as the ports are not exposed externally.