Hot-reloading on a dockerized flask app

At Hootsuite, my team was working with a dockerized flask app. Because we hadn’t set up hot-reloading, when we were making updates to the flask app and testing locally, we had to manually tear down the containers and bring them back up between each change. This took some time, and was mildly disruptive to our […]

At Hootsuite, my team was working with a dockerized flask app. Because we hadn’t set up hot-reloading, when we were making updates to the flask app and testing locally, we had to manually tear down the containers and bring them back up between each change. This took some time, and was mildly disruptive to our workflow, so I worked on setting up hot-reloading.

Table of Contents

Prerequisites

Setting up the flask app

First, let’s create a directory to house the code.

mkdir hot-reloading
cd hot-reloading

Create a requirements.txt file.

touch requirements.txt

And add flask to the requirements.txt file

https://medium.com/media/e3aa3714a22157ff16c65b1df612511c/href

Now let’s make the actual flask app.

https://medium.com/media/7c98386da99a3c23475565b80396e10a/href

Check that the app is working.

export FLASK_APP=app.py
flask run

We export FLASK_APP to tell flask where to find our application. flask run has to be run in the same terminal as export FLASK_APP=app.py is run.

Example output:

* Serving Flask app "app"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Visit the site, and you should see something like this:

Creating the dockerized flask app

If you skipped the previous step, you can use this starter code

Create the dockerfile for our flask app

We’ll have a separate dockerfile for Nginx, so let’s call this Dockerfile-flask.

https://medium.com/media/52d28eaa866a813600c7a44d1d4ad00d/href

We start with the base Python 3 image.

FROM python:3

Create and designate /app as the work directory.

RUN mkdir /app
WORKDIR /app

Expose the port for our app.

EXPOSE 5000

Because of how docker caches, it’s best to first copy the files that we don’t expect to change often. For this project, that’s the requirements.txt file.

COPY requirements.txt .

Install the requirements.

RUN pip install -r requirements.txt

Copy the rest of the files.

COPY . .

Run uwsgi with a config file.

CMD [ "uwsgi", "--ini", "app.ini" ]

Create the app.ini config for uwsgi

https://medium.com/media/58d29afa32e6581c3e55d657beb1220f/href

Add uwsgi to the requirements.txt file

https://medium.com/media/6cc9554a0e8f91284ae59771d9e755d6/href

Create the dockerfile for Nginx

https://medium.com/media/adbcccac7b50998ebcd3ccebc2bf396f/href

Start with the Nginx image.

FROM nginx:latest

Expose port 80 for Nginx.

EXPOSE 80

Replace the default config with a custom config

RUN rm /etc/nginx/conf.d/default.conf
COPY app.conf /etc/nginx/conf.d

Create the app.conf for Nginx

https://medium.com/media/6a905e5c6f28c738104afa499df53812/href

Create the docker-compose yaml

https://medium.com/media/888bd4593a194b7647207bbcaf8cab73/href

image: defines the name of the image, which can be anything we want

context: tells docker where to look for files to build the image

dockerfile: defines the dockerfile to use, which is just the dockerfiles we created in the previous steps.

volumes:
- "./:/app"

This mounts the root directory of our project on the host machine (./) onto a directory named app under the root directory in the container (/app).

ports:
- 5000:80

This maps port 5000 on our machine to port 80 on the Nginx container. This is important because Nginx listens on port 80 by default, but our app is running on port 5000.

You should now be able to reach the app on localhost:5000 after running docker-compose up

Adding hot-reloading

If you skipped the previous steps, you can use this starter code

uwsgi has a touch-reload configuration which allows us to designate a file to cause an update whenever the touch command is applied on it. However, we can only supply one file, so we use watchman to detect updates in our python files, and run touch <FILE> to trigger the uwsgi reload. I’ll be using a dedicated file named uwsgi-reload. To do this, add touch-reload = uwsgi-reload to the app.ini file.

https://medium.com/media/84d3000d6eb6ab44fa4258b4776c0680/href

To check for updates to our python files, we can run watchman-make -p '**/*.py' -s 1 — run ‘touch uwsgi-reload’.

-p '**/*.py' specifies the pattern for which files to watch for changes. Specifically, we’re looking for any file that ends in .py (*.py) at any level (**/) in our project.

-run 'touch uwsgi-reload' tells watchman to run touch uwsgi-reload whenever it detects an update. This will then trigger uwsgi to reload, since it’s watching the uwsgi-reload file.

-s 1 tells watchman to wait until there is 1 second without any updates before running touch uwsgi-reload. This essentially means that it will wait for you to stop typing for 1 second before triggering the reload, so it’s not constantly triggering reloads as you’re typing.

Now, if we run the watchman-make command after docker-compose up, the project will reload whenever you make an update to a .py file. We can use make to run this automatically instead.

https://medium.com/media/8600510edc9bb65cc869bfc298760aaa/href

We run docker-compose up with the -d flag so it runs in a detached state. This allows the terminal to hit the watchman-make command without shutting down the containers.

With this makefile, we can start the project with hot-reloading with make start, and stop it with make stop (after terminating the watchman-make command).

Now if you run make start, you should be able to see your changes in real time. Try changing the 'Hello World!' to something else!

Finalized hot-reloading dockerized flask app

Creation of dockerized flask app based on this article


Hot-reloading on a dockerized flask app was originally published in Hootsuite Engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.

Source: Hootsuite