0

I would like to build one image and run multiple containers against same image with containers running on different ports

I have following docker file

FROM python:3.9
ARG port
RUN mkdir /code
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./mock_s /code/mock_s
ENTRYPOINT ["uvicorn", "mock_s.main:app", "--port", "$port"]

and docker compose file

version: "3"
services:
  mock-server-1:
    container_name: mock-s1
    build: 
      context: .
      args:
        port: ${MOCK_SERVER_HOST_PORT_1}
      ports:
        - "${MOCK_SERVER_HOST_PORT_1}:8003"

For brevity, I am not showing code for mock-server-2, 3 and so on. but it only differs by reference to port variable ${MOCK_SERVER_HOST_PORT_1}, ${MOCK_SERVER_HOST_PORT_2} and so on

.env file is

MOCK_SERVER_HOST_PORT_1=8003
MOCK_SERVER_HOST_PORT_2=8004

but on docker compose up I get following error

Error: Invalid value for '--port': '${port}' is not a valid integer.

This indicates ${port} is not expanded when container is not run.

Any thoughts what might be wrong here?

katch
  • 820
  • 1
  • 7
  • 24

2 Answers2

0

So problem is in how Entrypoint works.

There are two modes exec and shell form (How do I use Docker environment variable in ENTRYPOINT array?)

By default it runs in exec mode where there is no variable substitution

So to substitute variable one needs to run shell as Entrypoint and not "your" exe

So this is what I did

ENTRYPOINT ["sh", "-c","uvicorn mock_sfapp.main:app --port ${SERVER_PORT}"]

Another problem in that setup is the substitution of ${port} which is a ARG in dockerfile does not seem to work, One needs to set an ENV variable to fix this.

Something like this ENV SERVER_PORT=$port

katch
  • 820
  • 1
  • 7
  • 24
  • `ARG` is only available during build-time, so that explains why you have to use `ENV`. If you run multiple containers, why do you need to use different "internal" ports at all? Why isn't it sufficient to map different ports on the host to the same port number in the different containers? – frippe Jan 05 '22 at 14:56
  • yup dont need to map different port nos inside container, they can remain same. in the example they are same. thanks for the ARG explaination – katch Jan 06 '22 at 04:38
0

It's easiest and perfectly safe to pick a single port number and hard-code it in the Dockerfile.

# do not pass an ARG port
EXPOSE 8000  # optional but considered good practice
CMD ["uvicorn", "mock_s.main:app", "--port", "8000"]

In your Compose setup the second ports: number must be the fixed container port 8000 but the first host port can be anything you'd like.

version: "3.8"
services:
  mock-server-1:
    build: .
    ports:
      - "${MOCK_SERVER_HOST_PORT_1}:8000" # 2nd number matches fixed number in image

If you're connecting between containers, the Compose service name can be used as a host name, and you always use the fixed port; Compose ports: aren't considered or required. Each container internally has its own IP address and it's not a problem if you have multiple services that happen to listen on the same port.

version: '3.8'
services:
  mock-server-1:
    build: ./server1
    ports: ['8001:8000']  # listens on port 8000 internally
  mock-server-2:
    build: ./server2
    ports: ['8002:8000']  # also listens on port 8000 internally
    environment:
      - SERVER_1_URL=http://mock-server-1:8000

In general you shouldn't use Dockerfile ARG for anything that you might need to change at deployment time, and especially for things where Docker has a way to remap the container resource to something else. So you probably shouldn't use ARG for ports (Compose ports: can remap them), user IDs (user:), or filesystem paths (volumes:).

David Maze
  • 130,717
  • 29
  • 175
  • 215
  • As I understand, from your example, uvicorn runs on port 8000 and we expose MOCK_SERVER_HOST_PORT_1 to outside world by wiring to 8000. so what would be the purpose of EXPOSE 8000 in dockerfile then? Is it for someone just running a separate container from dockerimage instead of using dockercompose ? – katch Jan 06 '22 at 04:53
  • 1
    `EXPOSE` was necessary in first-generation Docker networking to tell Docker which ports to make available to other containers. In modern Docker it does almost nothing and it's just documentation, but it's still considered a good practice. – David Maze Jan 06 '22 at 11:53
  • this solution removes the hacks like ENV that I had in place. Also makes solution more true to how docker should be used. Focus of my docker was not to make both containers to talk, but keep them running as independent containers – katch Jan 06 '22 at 17:49