9

I hope the title is descriptive enough. I am trying to execute my node app (that uses mongo and mysql) in docker. I am using docker-compose to start the app and docker-compose.yml file below:

version: "3.3"
services:
  app:
    container_name: app
    restart: always
    build: .
    volumes:
      - ./:/app
    ports:
      - "3000:3000"
    links:
      - mongo
      - mysql
  mongo:
    container_name: mongo
    image: mongo
   ports:
      - "27017:27017"
  mysql:
    container_name: mysql
    image: mysql
    ports:
      - "3306:3306"

Whenever I try to start this using docker-compose up I get the following error:

ERROR: for mysql  Cannot start service mysql: driver failed programming external connectivity on endpoint mysql (785b03daaa662bb3c344025f89fd28f49eabb43104b1c9a16ab425ab5120309f): Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use

ERROR: for mysql  Cannot start service mysql: driver failed programming external connectivity on endpoint mysql (785b03daaa662bb3c344025f89fd28f49eabb43104b1c9a16ab425ab5120309f): Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use
ERROR: Encountered errors while bringing up the project.

I did a little bit of research and it seems that gitlab-runner is using the mysql service. My understand was that if I run this setup through docker container they are isolated from the host system so I won't have any port conflicts. The only ports that I am exposing are the ones in my Dockerfile - in my case 3000. Am I missing something in my docker-compose.yml? What else could be wrong?

Crosswind
  • 940
  • 1
  • 10
  • 26
  • 2
    you have to change this `"3306:3306"` to `XXXX:3306` where XXXX is other port that is free. – Edwin Jan 16 '18 at 13:07

3 Answers3

14

Stop binding to local ports and let docker-compose pick an ephemeral port for you. In your case, your application can get to the default ports without any help. If you are following the 12 Factor App approach, then use environment variables like in the following snippet.

version: "3.3"
services:
  app:
    restart: always
    build: .
    volumes:
      - ./:/app
    ports:
      - 3000  # will be bound to an ephemeral port
    environment:
      MONGODB_URL: mongodb://mongo/db  # can reach port 27017 without help
  mongo:
    image: mongo
    ports:
      - 27017

This is the primary reason to make your applications configurable via environment variables or command-line flags.

You can use docker-compose port to get the ephemeral port if you need to access a docker application from the host. I routinely use a shell function like the following:

get_exposed_port() {  # SERVICE PORT
    docker-compose port $1 $2 | cut -d: -f2
}
D.Shawley
  • 58,213
  • 10
  • 98
  • 113
  • 1
    Note that it is typically unnecessary to expose the mongo port. Services in the same network are able to access each other by default; the `ports` section in `mongo` service exports the port to *host*. – Franklin Yu Aug 27 '18 at 21:53
  • @FranklinYu - correct. This is why the "app" container can connect to "mongo" on the default port. The only time that you need to grab the ephemeral port is to access the container from outside of the docker network (e.g., from localhost) – D.Shawley Aug 28 '18 at 17:24
  • Here is the short syntax documentation about ports, in the case someone needs it: https://docs.docker.com/compose/compose-file/compose-file-v3/#ports – user3322829 Jan 26 '21 at 10:42
7

Another option available as of version 3 of docker compose files is to specify a different port exposed on the host via environment variables.

So one way to do it might be like this:

version: "3.3"
services:
  mysql:
    container_name: mysql
    image: mysql
    ports:
      - "${MYSQL_PORT_NUMBER:-3306}:3306"

Then, if the environment variable is defined, that value will be used. Otherwise, the default 3306 will be used.

A second feature of version 3 docker compose files is that they will read environment variables from a .env file if one exists. The .env file must be in the directory where you run docker-compose from.

Finally, to debug all of this, one can use docker-compose config to emit the variable expanded docker compose file. Doing so with a .env that looks like this:

MYSQL_PORT_NUMBER=3307

gives this result:

$ docker-compose -f mysql-port-conflict.yaml config
services:
  mysql:
    container_name: mysql
    image: mysql
    ports:
    - published: 3307
      target: 3306
version: '3.3'

which shows that MySQL will be made available on port 3307 of the host. Of course, any apps that want to connect to MySQL will also need to know about ${MYSQL_PORT_NUMBER}.

HTH

jeffmcc
  • 263
  • 3
  • 9
3

In your docker-compose.yml file you are exposing ports from your pods on your hosts' network space by declaring them in the ports array, such as:

ports:
  - "3306:3306"

If you omit this part of the configuration, your containers will still be able to reach each other privately, but the ports won't be bound in your host machine, avoiding the port collision you are facing.

If you require to expose your ports to the host for some or all your services, you'll have to handle the collisions yourself by changing the bound port on the host side. For instance, to avoid port collision on port 3306 you could simply do:

ports:
  - "3307:3306"
whites11
  • 12,008
  • 3
  • 36
  • 53
  • If I simply delete the ports-section it still won't work however with a different error. I am assuming that's an issue with my code though and not related to the question. Changing it to "3307:3306" works whatsoever. Thanks! – Crosswind Jan 16 '18 at 13:19