'How to run FastAPI app on multiple ports?

I have a FastAPI application that I am running on port 30000 using Uvicorn programmatically. Now I want to run the same application on port 8443 too. The same application needs to run on both these ports. How can I do this within the Python code?

Minimum Reproducible code:

from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/healthcheck/")
def healthcheck():
    return 'Health - OK'

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=30000)

I want to something like

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", ports=[30000,8443])

Explanation: My application will be running on my organizations Azure Kubernetes Service. Apps running on port 30000 are reserved for Internal HTTP traffic and apps running on 8443 are mapped to 443 of Kubernetes Service to be exposed to external traffic.

Further Details: I will be creating a Docker Container out of this application and the idea is to include

CMD ["python3", "app.py"]

at the end to run the application. I am looking for a solution that would either provide a way to change the python code ( uvicorn.run(app, host="0.0.0.0", ports=[30000,8443]) ) or a change to the CMD command in the Dockerfile like This GitHub Issue Comment - gunicorn -k uvicorn.workers.UvicornWorker -w 1 --bind ip1:port1 --bind ip2:port2 --bind ip3:port3



Solution 1:[1]

The following solution worked for me. It runs one gunicorn process in the background and then another process to bind it to two ports. One of them will use HTTP and one can use HTTPS.

Dockerfile:

FROM python:3.7
WORKDIR /app
COPY requirements.txt .
RUN pip3 install -r requirements.txt
COPY . .
ENTRYPOINT ./docker-starter.sh
EXPOSE 30000 8443

docker-starter.sh:

gunicorn -k uvicorn.workers.UvicornWorker -w 3 -b 0.0.0.0:30000 -t 360 --reload --access-logfile - app:app & gunicorn --access-logfile - -k --ca_certs ca_certs.txt uvicorn.workers.UvicornWorker -w 3 -b 0.0.0.0:8443 -t 360 --reload --access-logfile - app:app

The python app can remain minimal:

from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/healthcheck/")
def healthcheck():
    return 'Health - OK'

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0")

Solution 2:[2]

One of the options is to use docker-compose and transfer the port in a variable environment. You just need to deploy multiple instances of your application. The version is not a production ready, just a minimal example.

docker-compose.yml
Dockerfile
main.py
requirements.txt

main.py:

from fastapi import FastAPI
import uvicorn
import os

app = FastAPI()


@app.get("/healthcheck/")
def healthcheck():
    return 'Health - OK'


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=int(os.getenv('APP_PORT')))

Dockerfile:

FROM python:3.8-slim

WORKDIR /usr/src/app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

CMD [ "python", "./main.py" ]

docker-compose.yml:

version: '2'

services:
  internal-app:
    image: internal-app
    environment:
      APP_PORT: "3000"
    build:
      context: .
      dockerfile: ./Dockerfile
    restart: unless-stopped
    network_mode: host

  external-app:
    image: external-app
    environment:
      APP_PORT: "8443"
    build:
      context: .
      dockerfile: ./Dockerfile
    restart: unless-stopped
    network_mode: host

requirements.txt:

uvicorn
fastapi

To start - docker-compose up -d --build

2 applications will start, each on its own port.

Solution 3:[3]

i would suggest the running multiple replicas for internal and external communication

uvicorn.run(app, host="0.0.0.0", port=int(os.getenv('PORT')))

So one of your deployments will be running with port 30000 and 8443 in kubernets.

Accordingly you can create the two services one for external and one internal communication.

Ingress traffic will get a route to those pod running on 8443 while internal service call will get route to those pod running on 30000.

Ingress > service 1 > deployment 1  with port 8443 > pods

Internal traffic > service 2 > deployment 2 with port 30000 > pods

in both deployment, you can change the Port by setting environment.

Plus point :

  • Your deployment of PODs won't fail if your single port fails or application crashes during internal calls or external calls (as two deployments will be running)

Solution 4:[4]

In my case, I used the same command above but with a small change. I needed to expose other routes on private port.

app = FastAPI()
app2 = FastAPI()

Then, in the run.sh file, I have:

uvicorn app.main:app --reload --host 0.0.0.0 --port $PORT & uvicorn app.main:app2 --reload --host 0.0.0.0 --port $PORT_INTERNAL_APP

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Anirban Saha
Solution 2 asanisimov
Solution 3 Harsh Manvar
Solution 4 BrokenBenchmark