fastapi
fastapi copied to clipboard
Best practice to run FastAPI on Cloud Run with server port as $PORT
First Check
- [X] I added a very descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the FastAPI documentation, with the integrated search.
- [X] I already searched in Google "How to X in FastAPI" and didn't find any information.
- [X] I already read and followed all the tutorial in the docs and didn't find an answer.
- [X] I already checked if it is not related to FastAPI but to Pydantic.
- [X] I already checked if it is not related to FastAPI but to Swagger UI.
- [X] I already checked if it is not related to FastAPI but to ReDoc.
Commit to Help
- [X] I commit to help with one of those options 👆
Example Code
"""
A sample Hello World server.
"""
import uvicorn
import os
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.route('/')
def hello(request: Request):
"""Return a friendly HTTP greeting."""
message = "It's running!"
"""Get Cloud Run environment variables."""
service = os.environ.get('K_SERVICE', 'Unknown service')
revision = os.environ.get('K_REVISION', 'Unknown revision')
return templates.TemplateResponse('index.html', context={
"request": request,
"message": message,
"Service": service,
"Revision": revision})
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=int(os.environ.get('PORT', 8000)), log_level="info")
Description
I've been using Google Cloud Code in VSCode and PyCharm and the example they have for Python is a Flask app. I've been trying to rewrite the example app but with FastAPI. To have it running on Cloud Run you have to start the server with the port binded to an OS env variable called PORT. My question is:
- Should I start uvicorn programmaticaly like in the example or,
- Should I have a "normal" FastAPI app main.py file and use the CMD or ENTRYPOINT to bind the $PORT variable?
Note: ENTRYPOINT ["uvicorn", "main:app", "-port", "$PORT"]
won't work because the CMD in exec form won't substitute the variable to its value, as documented here. It must be in shell form ENTRYPOINT uvicorn main:app --port $PORT
Operating System
Linux
Operating System Details
No response
FastAPI Version
0.78.0
Python Version
Python 3.10.4
Additional Context
The Cloud Code Flask sample code: https://github.com/GoogleCloudPlatform/cloud-code-samples/tree/v1/python/cloud-run-python-hello-world
Do not run uvicorn alone. Read this docs from their page. https://www.uvicorn.org/deployment/
They recommend to run it with gunicorn and to use UvicornWorker:
"Run gunicorn -k uvicorn.workers.UvicornWorker
for production"
Do not run uvicorn alone. Read this docs from their page. https://www.uvicorn.org/deployment/
They recommend to run it with gunicorn and to use UvicornWorker: "Run
gunicorn -k uvicorn.workers.UvicornWorker
for production"
From the FastAPI documentation:
My question is about how should I pass the $PORT variable to the server (uvicorn/gunicorn) not about the server itself to use.
AFAIK you are not able to reference host environment variables in your docker file, so the best way to achieve this is the example you gave. In reality, there isn't really a difference between using the a CLI command vs starting Uvicorn from within a Python file. Using a CLI would also start a Python process, which then would call the exact code as you have in your example (e.g. uvicorn.run()
), they are interchangeable.
Thanks for your feedback @JarroVGIT. What made me open this question was the fact that the entire documentation of FastAPI doesn't mention the option to start uvicorn from the python file. What about a new entry on the docs about this topic?
I think (but am not sure!) that is because in the CLI you can have the -reload option that is lacking in the direct way, but for production usecases this wouldn’t make sense anyway :)
You can use reload
on uvicorn.run()
. The thing is that the application path needs to be a string, and not the application object.
if __name__ == "__main__":
uvicorn.run("main:app", reload=True)
Ref.: https://www.uvicorn.org/deployment/#running-programmatically
Interesting, didn’t know you could pass a reload parameter, that will come in handy! Although my main pattern is to pass the application object itself, I might need to tweak my workflow a bit :)
another derivative you can do is.
# runserver.py
from uvicorn import Config, Server
from fastapi import FastAPI
app = FastAPI()
if __name__ == "__main__": # pragma: no cover
server = Server(
Config(
"runserver:app",
host="0.0.0.0",
port=9002,
reload=True,
),
)
# do something you want before running the server
# eg. setting up custom loggers
server.run()
you may also want to consider using pydantic[dotenv]
to fetch the env variables.
Having multiple ways to achieve the same goal can be more confusing than helpful. 😅
@Kludex
You are right,
Though I think this variant is worth mentioning. As he might be looking for a configuration step before running the server.
such as this scenario. https://github.com/tiangolo/fastapi/issues/1276#issuecomment-615877177
where loguru fails to intercept the first 3 entries in the log.
but yeah this might be too much answer for what has been asked.
I have cloned the Google's example repository for Cloud Code but with FastAPI here: https://github.com/alexsantos/cloud-run-fastapi It is working both with the Dockerfile and with Cloud Build for Python.
I have cloned the Google's example repository for Cloud Code but with FastAPI here: https://github.com/alexsantos/cloud-run-fastapi It is working both with the Dockerfile and with Cloud Build for Python.
@alexsantos
thank you for sharing this! I'm curious how the Dockerfile + docker-compose.yml would look like if 1 had to link a cloud db? (at the moment, I'm trying to connect w/ AlloyDB, but when I am deploying it to CloudRun I receive a Error: Invalid value for '--port': '$PORT' is not a valid integer
error on the cloud run logs)
Any advice would be appreciated!
I have cloned the Google's example repository for Cloud Code but with FastAPI here: https://github.com/alexsantos/cloud-run-fastapi It is working both with the Dockerfile and with Cloud Build for Python.
@alexsantos thank you for sharing this! I'm curious how the Dockerfile + docker-compose.yml would look like if 1 had to link a cloud db? (at the moment, I'm trying to connect w/ AlloyDB, but when I am deploying it to CloudRun I receive a
Error: Invalid value for '--port': '$PORT' is not a valid integer
error on the cloud run logs) Any advice would be appreciated!
Hello and sorry for the delay: why are you using a docker-compose with a cloud DB?