How I Set Up Database Migrations for my Serverless Flask App Deployed with Zappa
This blog post assumes you are familiar with
- Flask
- Flask-Migrate (alembic revisions for Flask apps)
- Zappa (serverless deployment on aws)
Setup
My application follows the create_app
pattern outlined in the Flask Flaskr example blog tutorial. Because of this, we need to make some adjustments to the usual deployment process when using Zappa to deploy our application.
Since we cannot call the create_app()
function directly from __init__.py
with Zappa, we will create a supplementary run.py
file that instantiates our app, we will them point to this in zappa_settings.json
. Note that yt_pubsub_handler
is the name of the project. You can see it on GitHub.
We can now point Zappa to this app variable:
Zappa now knows where to look for our app!
Database Migrations
If you are not familiar with database migrations, there are generally two options: upgrade
and downgrade
. An upgrade
is when you make a new change to your database a structure, and a downgrade
is when you revert that change. We need to be able to do this with our application database that is hosted on aws - but we can only really do this in production, since we don’t want to expose database credentials and permissions to devs. We also would not want to allow anyone to run the changes locally, since they should go through the approval process on GitHub first.
The Problem
Flask-Migrate exposes a great CLI, but we cannot run CLI commands with Zappa. So we need a workaround - we need to use the Flask-Migrate
API to create some helper functions that will allow us to upgrade and downgrade our database.
The Solution
Note that I have already Introduced Flask-Migrate
into my create_app()
function and have run flask db init
to generate a migrations/
folder at the root of my project directory.
Implementation
Functions
Now we need to write some helper functions in order to run the CLI commands flask db upgrade
and flask db downgrade
via Zappa’s python interface.
Notice that these functions take in an app
variable, which is a reference to the Flask app
object itself, that we first instantiated in run.py
. So now we need to access these via the run.py
file so that we can pass that app reference to these functions. Without the app_context()
our functions will not know which database they should be interacting with. Note that current_app
will not work in this scenario. So let’s extend our run.py
file:
Creating a Migration
We can now create a branch and alter our database. To demonstrate, I made a change to yt_pubsub_handler/models.py
, which Flask-Migrate
automatically picks up.
The flow I use typically includes the following steps:
- Start a local dev session
- Edit the
models.py
file - Open a second terminal pane, ensure
export FLASK_APP=yt_pubsub_handler
is also set here - Run
flask db migrate
to automatically create a revision in themigrations/versions/
folder- Review this thoroughly since it is auto-generated and may contain flaws
- Commit changes and open a PR to merge the changes into production
- Once merged and deployed with
zappa update production
, execute the migration with:
For downgrade simply replace with run_alembic_downgrade
.
Conclusion
Here we used Flask-Migrate’s API with some additional functionization to work with the Zappa serverless deployment framework. We can now make changes to our application database with ease.