Recently, I’ve been working to migrate my own personal infrastructure off of Kubernetes, given that it has become quite expensive to run for just a single hosted website. For example, on Linode, the smallest cluster (3 nodes), along with a load balancer, and several disks, was approaching 50$ a month. Indeed, what did I actually use my cluster for? I used it for hosting one-off websites for a couple of days, and then tearing them down, I used it for my own personal website, and I used it for a host of CRON jobs that have to run every hour or every couple of days. The one-off websites I could easily migrate to vercel, and my personal website I migrated to Cloudflare Pages + static site generation (11ty), but the CRON jobs were a bit trickier. Luckily, I found Modal, a service for serverless compute which allows running compute on a schedule (with a bit of configuration).

All of my cron jobs were python scripts - so they already fit with Modal’s supported languages. The first thing to do was to install the Modal pip package, which is as simple as running pip install modal. Then sign up for an account on the modal website: https://modal.com/. You can then log in using the CLI with modal setup.

The next step was to create a model app. In the example, I’ll run a simple script which makes a GET request to a URL every hour.

import modal
import requests


app = modal.App(name="example-cron-job")

@app.function()
def main():
    requests.get("https://example.com")

Unfortunately, that’s not enough, since “requests” is not a built-in python module. To fix this, we need to create an image on which Modal can run:

import modal
import requests

app = modal.App(name="example-cron-job")
image = modal.Image.debian_slim(python_version="3.10").pip_install("requests")

@app.function(image=image)
def main():
    requests.get("https://example.com")

Now that we have the image, we can setup the schedule:

import modal
import requests

app = modal.App(name="example-cron-job")
image = modal.Image.debian_slim(python_version="3.10").pip_install("requests")

@app.function(image=image, schedule=modal.Cron("0 * * * *"))
def main():
    requests.get("https://example.com")

Here, the schedule is set to run every hour. The final step is to deploy the app:

modal deploy example-cron-job.py

This will deploy the app to Modal’s infrastructure, and it will run every hour. The pricing is a bit hard to estimate, since jobs are chaged by CPU/Memory-seconds, however it comes with a pretty generous free tier of 30$/month, and in the 20-30 odd jobs that I’ve run so far, I haven’t even used 0.01$, so it’s quite cheap.