skip to Main Content

I am developing a FastAPI app. It is running on Uvicorn in a Docker container using docker-compose.

I want to include some files other than *.py to trigger the auto reload while in development.

According to the docs Uvicorn needs the optional dependency WatchFiles installed to be able to use the --reload-include flag, which would enable me to include other file types to trigger a reload. However, when WatchFiles is installed (with Uvicorn confirming by printing this info at start up: Started reloader process [1] using WatchFiles) no auto reloads happen at all. Mind you, this is independent of changes to the run command, with or without the include flag.

Without WatchFiles installed, Uvicorn’s default auto reload works as intended for just *.py files.

What I’ve got

This is the Dockerfile:

FROM python:3.10

WORKDIR /tmp

RUN pip install --upgrade pip

COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade -r requirements.txt

WORKDIR /code

CMD ["uvicorn", "package.main:app", "--host", "0.0.0.0", "--port", "80", "--reload"]

This is the docker-compose.yml:

version: "3.9"

services:
  fastapi-dev:
    image: myimagename:${TAG:-latest}
    build:
      context: .
    volumes:
      - ./src:/code
      - ./static:/static
      - ./templates:/templates
    restart: on-failure
    ports:
      - "${HTTP_PORT:-8080}:80"

(I need a docker-compose file because of some services required later on.)

The most basic FastAPI app:

from fastapi import FastAPI, HTTPException

app = FastAPI()


@app.get('/')
async def index():
    raise HTTPException(418)

Mind you, this is probably of no concern as the problem does not seem to be related to FastAPI.

requirements.txt:

fastapi~=0.85
pydantic[email]~=1.10.2
validators~=0.20.0
uvicorn~=0.18
watchfiles
python-decouple==3.6
python-multipart
pyotp~=2.7
wheezy.template~=3.1

How did I try to resolve this issue?

I tried using command: uvicorn package.main:app --host 0.0.0.0 --port 80 --reload in docker-compose.yml instead of CMD [...] in the Dockerfile, which unsurprisingly changed nothing.

I created a file watch.py to test if WatchFiles works:

from watchfiles import watch

for changes in watch('/code', force_polling=True):
    print(changes)

And…in fact it does work. Running it from the container in Docker CLI prints all the changes made. (python -m watch) And btw it works just as fine async/using asyncio. So it is probably nothing to do with the file system/share/mount within Docker.

So…

How do I fix it? What is wrong with Uvicorn(?) I need to check for other file types e.g. *.html in /templates. Do I have to get WatchFiles to work or are there other ways? If I do, how?

2

Answers


  1. I just had the same problem and the problem is with WatchFiles.

    In the watchfiles documentation it is understood that the detection relies on file system notifications, and I think that via docker its events are not launched when using a volume.

    Notify will fall back to file polling if it can’t use file system notifications

    So you have to tell watchfiles to force the polling, that’s what you did in your test python script with the parameter force_polling and that’s why it works:

    for changes in watch('/code', force_polling=True):
    

    Fortunately in the documentation we are given the possibility to force the polling via an environment variable.
    Add this environment variable to your docker-compose.yml and auto-reload will work:

    services:
      fastapi-dev:
        image: myimagename:${TAG:-latest}
        build:
          context: .
        volumes:
          - ./src:/code
          - ./static:/static
          - ./templates:/templates
        restart: on-failure
        ports:
          - "${HTTP_PORT:-8080}:80"
        environment:
          - WATCHFILES_FORCE_POLLING=true
    
    Login or Signup to reply.
  2. You can also include flag "–reload-dir /working-directory-name" and it will automatically reload the app.
    Note, that you can also start your server in runtime from the python code:

    if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host=settings.MICROSERVICE_HOST,
        port=settings.MICROSERVICE_PORT,
        reload=True,
        reload_dirs=['/microservice']
    )
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search