How can I make docker-compose bind the containers only on defined network instead of 0.0.0.0?

DockerDocker Compose

Docker Problem Overview


In recent versions docker-compose automatically creates a new network for the services it creates. Basically, every docker-compose setup is getting its own IP range, so that in theory I could call my services on the network's IP address with the predefined ports. This is great when developing multiple projects at the same time, since there is then no need to change the ports in docker-compose.yml (i.e. I can run multiple nginx projects at the same time on port 8080 on different interfaces)

However, this does not work as intended: every exposed port is still exposed on 0.0.0.0 and thus there are port conflicts with multiple projects. It is possible to put the bind IP into docker-compose.yml, however this is a killer for portability -- not every developer on the team uses the same OS or works on the same projects, therefore it's not clear which IP to configure.

It's be great to define the IP to bind the containers to in terms of the network created for this particular project. docker-compose should both know which network it created as well as its IP, so this shouldn't be a problem, however I couldn't find an easy way to do it. Is there a way or is this something yet to be implemented?

EDIT: An example of a port conflict: imagine two projects, each with an application server running on port 8080 and a MySQL database running on port 3306, both respectively exposed as "8080:8080" and "3306:3306". Running the first one with docker-compose creates a network called something like app1_network with an IP range of 172.18.0.0/16. Every exposed port is exposed on 0.0.0.0, i.e. on 127.0.0.1, on the WAN address, on the default bridge (172.17.0.0/16) and also on the 172.18.0.0/16. In this case I can reach my application server of all of 127.0.0.1:8080, 172.17.0.1:8080, 172.18.0.1:8080 and als on $WAN_IP:8080. If I start the second application now, it starts a second network app2_network 172.19.0.0/16, but still tries to bind every exposed port on all interfaces. Those ports are of course already taken (except for 172.19.0.1). If there had been a possibility to restrict each application to its network, application 1 would have available at 172.18.0.1:8080 and the second at 172.19.0.1:8080 and I wouldn't need to change port mappings to 8081 and 3307 respectively to run both applications at the same time.

Docker Solutions


Solution 1 - Docker

In your service configuration, in docker-compose.yml:

ports:
 - "127.0.0.1:8001:8001"

Reference: https://github.com/compose-spec/compose-spec/blob/master/spec.md#ports

Solution 2 - Docker

You can publish a port to a single IP address on the host by including the IP before the ports:

docker run -p 127.0.0.1:80:80 -d nginx

The above runs nginx on the loopback interface. You can use a similar port mapping inside of a docker-compose.yml file. e.g.:

ports:
  - "127.0.0.1:80:80"

docker-compose doesn't have any special abilities to infer which network interface to use based on the docker network. You'd need to specify the unique IP address to use in each compose file, and that IP needs to be for a network interface on your host. For a developer machine, that IP may change as DHCP gives the laptop/workstation new addresses.

Because of the difficulty implementing your goal, most would either map different ports on the host to different containers, so 13307:3307 for container a, 23307:3307 for container b, 33307:3307 for container c, or whatever number scheme makes sense for you. And when dealing with HTTP traffic, then using a reverse proxy like traefik often makes the most sense.

Solution 3 - Docker

It can be achieved by configuring network in docker-compose file.

Please consider below two docker-compose files. There is still drawback of needing to specify subnet unique across all project you work on at the same time. On the other hand you need to know which service you connecting too - this is why it cannot assign it dynamically.

my-project.yaml:

services:
  nginx:
    networks:
      - my-project-network
    image: nginx
    ports:
      - 80:80

networks:
  my-project-network:
    driver_opts:
      com.docker.network.bridge.host_binding_ipv4: "172.20.0.1"
    ipam:
      config:
        - subnet: "172.20.0.0/16"

my-other-project.yaml

services:
  nginx:
    networks:
      - my-other-project-network
    image: nginx
    ports:
      - 80:80

networks:
  my-other-project-network:
    driver_opts:
      com.docker.network.bridge.host_binding_ipv4: "172.21.0.1"
    ipam:
      config:
        - subnet: "172.21.0.0/16"

Note: that if you have other service binding to *:80 like for instance apache running on host - it will also bind on docker-compose networks' interfaces and you will not be able to use this port.

To run above two services:

docker-compose -f my-project.yaml up -d
docker-compose -f my-other-project.yaml up -d

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNikolai ProkoschenkoView Question on Stackoverflow
Solution 1 - DockerMohammed NoureldinView Answer on Stackoverflow
Solution 2 - DockerBMitchView Answer on Stackoverflow
Solution 3 - DockerJJ RomanView Answer on Stackoverflow