skip to Main Content

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?

2

Answers


  1. Chosen as BEST ANSWER

    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


  2. 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:).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search