Schedule Prefect deployments to run on Modal
Learn how to use Modal as a push-pool to schedule, run, and debug your prefect flows
Prefect is the worlds best orchestrator. Modal is the worlds best serverless compute framework. It only makes sense that they would fit together perfectly.
In this short guide, we’ll walk you through building your first prefect deployment that runs on modal every hour, and build out a scalable framework to grow your project.
You can see all the functioning code here: https://github.com/Ben-Epstein/prefect-modal
We’ll walk step-by-step through the following:
- Create a prefect account
- Create a modal account
- Create your github repo
- Give prefect access to your github repo
- Connect prefect and modal
- Configure CI/CD
Let’s quickly look at an architecture diagram of what we’re building.
We’ll be building a full serverless infrastructure framework for scheduling and provisioning workflows. GitHub will act as the source of truth, Prefect will be our orchestrator, and Modal will be our serverless infrastructure, paying for only the CPU time we use.
Let’s quickly recap on some important terms:
- Flow: Some python function in a file, decorated with
@flow
, that is an entrypoint to do some task in prefect - Run: A instance of a flow, executed at some time with some payload.
- Deployment: A manifest around a flow, including where it should run, when it should run, and any variables it needs to be able to run. This can “schedule” your flows, creating runs when needed.
- Work pool: A collection of hardware that Prefect can talk to in order to schedule a given flow-run.
1. Create Prefect Account
Simply go to the prefect sign-in page. We suggest using GitHub to manage your account, but any will do!
2. Create Modal Account
Simply go to the modal sign-up page. Easiest to use the same account, but that’s your call!
3. Create your GitHub repo
We’re going to create a simple GitHub repo with our prefect flows. You can follow along here: https://github.com/Ben-Epstein/prefect-modal
This repo will have:
- Our prefect.yaml deployment config
- Standard python tooling
- CI/CD
Let’s start with a Makefile to keep things easy
We’ll configure and manage our environment with uv
, which will get configured when you run make setup
Start by running uv init --lib --name prefect_modal --python 3.12
which will create your python project.
If uv creates a python-version
that is pinned to a micro (ie 3.12.6) remove the micro, and make it 3.12. This causes issues downstream if not
Next, we’ll add our dependencies by running uv add modal 'prefect[github]'
We need to make one small change to the pyproject.toml
created by uv
. At the bottom, add the following
And then we’ll initialize our prefect environment with uv run prefect init
On the first question, select git
Now, because this is meant to be a scalable repo, we’ll create a submodule just for our workflows.
this is where we’ll keep different flows.
Let’s take a look at that prefect.yaml file that was generated.
The important sections here are pull
, which defines the repo and branch to pull from, as well as deployments
, which define the flows
that will be run
at some schedule
on a work_pool
. The work pool is going to be modal, but we’ll get to that.
Let’s also create our flow now. touch src/prefect_modal/flows/flow1.py
You can do anything in your flow, but we’ll keep it simple
4. Give prefect access to your github repo
First, let’s auth into prefect: uv run prefect auth login
— follow the instructions provided.
We’ll do this via a Personal Access Token (PAT). We will use a classic token here but you can also configure a fine-grained token. Go your your token settings here: https://github.com/settings/tokens and click “Generate a new token (classic)”
Give it a name, an expiration date, and repo access. Then, copy that token.
Now we’ll give prefect that token. First run
uv run prefect block register -m prefect_github
then
uv run prefect block create prefect_github
This command will return a URL. Click that URL to give your block a name and paste in the PAT you got from GitHub. Call the block name prefect-modal
to follow along with the repo.
Let’s go back to our prefect.yaml under pull:
and make a few changes.
Prefect creates the yaml with the key access_token
. Update that key in the yaml to the section to use the key name credentials
instead of token
Don’t forget to change the repository name to yours!
We’ll add another section now to get ahead of it, which configures how you install your package and what your working directory is
Typically uv sync
creates a new virtual environment, but we can’t have that for prefect’s sake. It needs to be root python. You’ll see below how to tell uv to use the root python version
What we’ve done here is tell prefect how to download the code from github into the modal pod before running the flow. This is important: we are telling modal to load the code from main
. This means that if you push new code to main, the next run of a flow in modal will change to whatever is in main. To keep this stable, you can set that to a release branch, for example, only updating that release branch when you’re stable and ready.
Let’s add a few more things to our deployment
in order to get it ready: entrypoint
and schedule
. We’ll also add a description:
We can add more schedules as desired. Simply add another list element. This one runs once a day at 12PM EST.
Because we reference the entrypoint as a module and not a path, it can be called from anywhere (so long as it’s installed). This is much more flexible
5. Connect prefect and modal
What we’re doing here is giving Prefect access to your modal account to provision a serverless instance to run your flows and tasks. Modal will be our work pool
in Prefect terms, remaining off when unused, and spinning up the infrastructure we need when a new run of a flow is scheduled. Prefect makes this incredibly easy.
First, authenticate into modal and create a token
uv run modal setup
then
uv run modal token new
then finally
uv run prefect work-pool create --type modal:push --provision-infra pref-modal-pool
That final command, with name pref-modal-pool
is custom, you can name it whatever you want. But we’ll take that and update our prefect.yaml
with it.
This UV_PROJECT_ENVIRONMENT
is how we tell uv to use the root python. We could pip install
without uv but it would be significantly slower (minutes vs seconds) and you wouldn’t get to leverage your lockfile, resulting in unexpected behavior!
We can create multiple work queues across the same pool, for example if we have another job using this pool. This can let you handle concurrency and swim-lane priorities, but we’ll use the default queue name that comes with every new pool.
When we deploy this to prefect, it will now know to schedule all of the flow runs for this particular deployment on our modal work pool!
At this point, you could git add . && git commit -m "prefect modal" && git push
to get our code into GitHub, and then uv run prefect deploy --all
to get our deployment live, but we want to take the final step and automate Prefect deployments.
6. Configure CI/CD
Our last step. Since our code must exist in GitHub in order for modal to download, install, and run it, we want github to also be our source of truth in terms of telling Prefect what to actually do with our code.
This is simple, with additional details here: https://docs.prefect.io/v3/deploy/infrastructure-concepts/deploy-ci-cd
We first need to add 2 secrets to our GitHub repo: PREFECT_API_KEY
and PREFECT_API_URL
For the PREFECT_API_URL, the format is as follows
https://api.prefect.cloud/api/accounts/{account_id}/workspaces/{workspace_id}
Go to your prefect dashboard and from the URL you will find the account it and the workspace id.
Note: the prefect console url uses /account
and /workspace
but the api uses the plurals (see above).
First, go to your API Keys and generate a new set.
Add this key as a secret in your github repo. Go to your repo, then settings
→ secrets and variables
→ actions
Now, we’ll add a simple github workflow. Create a new folder in the root of your github repo: mkdir -p .github/workflows
and then create your workflow touch .github/workflows/prefect_cd.yaml
and add the following
Now, what’s make prefect-deploy
? We’re going to add a new command to our Makefile to keep things very easy
You can also run that locally at any time to deploy the flows in your deployment. This keeps everything consistent.
Before pushing our code, let’s run make prefect-deploy
once to make sure everything works. Prefect will also suggest any changes to our prefect.yaml which we can accept before pushing.
We said yes and let prefect override our deployment spec with a few changes. This will let our CD run without any interruptions or prompts. If we go to our Prefect console, we’ll now see it!
There are no runs yet. We can see a command at the bottom of our terminal from make prefect-deploy
to run our deployment-flow, but since our code isn’t in GitHub yet, this will fail. Let’s push up our code, and checkout the action, which should now pass!
And we will see that in the Prefect console as well.
Now, at 12pm the flow will run! But let’s see it run in realtime 😄
First, open up your modal dashboard: https://modal.com/apps/ on your screen next to your prefect dashboard of your flow:
Now, back at your terminal, run uv run prefect deployment run main/prefect-modal-example
You should almost instantly see the run start in your Prefect dashboard. After 30 sec - 1 min, you should see it run in modal! You can click on it to see the logs.
Clicking into the app and then clicking Logs on the left will show everything that’s happening inside the flow run!
You can click on a specific log line to get more details
If you copy the URL in that log, you’ll see the Prefect side
You’re done! 🚀
As you make changes to either the code or deployment file, prefect will redeploy and modal will automatically pick up changes. Schedule away!
Was this page helpful?