Hypercorn with "--reload" and Docker volumes

3.4k views Asked by At

I am running Hypercorn with --reload inside a Docker container. The file I am running is kept in a volume managed by Docker Compose.

When I change the file on my system, I can see that the change is reflected in the volume, e.g. with docker compose exec myapp /bin/cat /app/runtime/service.py.

However, when I change a file in this way, Hypercorn does not restart as I would have expected. Is there some adverse interaction between Hypercorn and the Docker volume? Or am I expecting something from the --reload option that I should not expect?

Example files are below. My expectation was that modifying runtime/service.py from outside the container would trigger Hypercorn to restart the server with the modified version of the file. But this does not occur.

Edit: I should add that I am using Docker 20.10.5 via Docker Desktop for Mac, on MacOS 10.14.6.

Edit 2: This might be a Hypercorn bug. If I add uvicorn[standard] in requirements.txt and run python -m uvicorn --reload --host 0.0.0.0 --port 8001 service:app, the reloading works fine. Possibly related: https://gitlab.com/pgjones/hypercorn/-/issues/185

entrypoint.sh:

#!/bin/sh
cd /app/runtime
/opt/venv/bin/python -m hypercorn --reload --bind 0.0.0.0:8001 service:app

Dockerfile:

FROM $REDACTED

RUN /opt/venv/bin/python -m pip install -U pip
RUN /opt/venv/bin/pip install -U setuptools wheel

COPY requirements.txt /app/requirements.txt
RUN /opt/venv/bin/pip install -r /app/requirements.txt

COPY requirements-dev.txt /app/requirements-dev.txt
RUN /opt/venv/bin/pip install -r /app/requirements-dev.txt

COPY entrypoint.sh /app/entrypoint.sh

EXPOSE 8001/tcp

CMD ["/app/entrypoint.sh"]

docker-compose.yml:

version: "3.8"
services:
  api:
    container_name: api
    hostname: myapp
    build:
      context: .
    ports:
      - 8001:8001
    volumes:
      - ./runtime:/app/runtime

runtime/service.py:

import logging
import quart

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

app = quart.Quart(__name__)

@app.route('/')
async def handle_hello():
    logger.info('Handling request.')
    return 'Hello, world!\n'

@app.route('/bad')
async def handle_bad():
    logger.critical('Bad request.........')
    raise RuntimeError('Oh no!!!')
2

There are 2 answers

0
Lars Blumberg On BEST ANSWER

Here is a minimal, fully working example which does auto-reload using hypercorn:

docker-compose.yaml

services:
  app:
    build: .
    # Here --reload is used which works as intended!
    command: hypercorn --bind 0.0.0.0:8080 --reload src:app
    ports:
      - 8080:8080
    volumes:
      - ./src:/app/src

Dockerfile

FROM python:3.10-slim-bullseye
WORKDIR /app                                                          
RUN pip install hypercorn==0.14.3 quart==0.18.0
COPY src ./src
EXPOSE 8080
ENV QUART_APP=src:app
# This is the production command; docker-compose.yaml overwrites it for local development
CMD hypercorn --bind 0.0.0.0:8080 src:app

src/__init__.py

from quart import Quart

app=Quart(__name__)

@app.route('/', methods=['GET'])
def get_root():
    return "Hello world!"

Run via docker-compose up and notice how hypercorn reloads once __init__.py got modified.

8
ti7 On

You likely need to use a volume mount to get the reload functionality!

This is because when you build the container, it bakes whatever you have locally into it. Further changes only affect your local filesystem.


This is arguably not intended-use (as the container becomes dependent on external files!), but likely useful for faster testing/debugging

You could also directly edit the container by connecting to it, which you may find suits your needs.