# Modal Scheduled Jobs ## Overview Modal supports running functions automatically on a schedule, either using cron syntax or fixed intervals. Deploy scheduled functions with `modal deploy` and they run unattended in the cloud. ## Schedule Types ### modal.Cron Standard cron syntax — stable across deploys: ```python import modal app = modal.App("scheduled-tasks") # Daily at 9 AM UTC @app.function(schedule=modal.Cron("0 9 * * *")) def daily_report(): generate_and_send_report() # Every Monday at midnight @app.function(schedule=modal.Cron("0 0 * * 1")) def weekly_cleanup(): cleanup_old_data() # Every 15 minutes @app.function(schedule=modal.Cron("*/15 * * * *")) def frequent_check(): check_system_health() ``` #### Cron Syntax Reference ``` ┌───────────── minute (0-59) │ ┌───────────── hour (0-23) │ │ ┌───────────── day of month (1-31) │ │ │ ┌───────────── month (1-12) │ │ │ │ ┌───────────── day of week (0-6, Sun=0) │ │ │ │ │ * * * * * ``` | Pattern | Meaning | |---------|---------| | `0 9 * * *` | Daily at 9:00 AM UTC | | `0 */6 * * *` | Every 6 hours | | `*/30 * * * *` | Every 30 minutes | | `0 0 * * 1` | Every Monday at midnight | | `0 0 1 * *` | First day of every month | | `0 9 * * 1-5` | Weekdays at 9 AM | ### modal.Period Fixed interval — resets on each deploy: ```python # Every 5 hours @app.function(schedule=modal.Period(hours=5)) def periodic_sync(): sync_data() # Every 30 minutes @app.function(schedule=modal.Period(minutes=30)) def poll_updates(): check_for_updates() # Every day @app.function(schedule=modal.Period(days=1)) def daily_task(): ... ``` `modal.Period` resets its timer on each deployment. If you need a schedule that doesn't shift with deploys, use `modal.Cron`. ## Deploying Scheduled Functions Schedules only activate when deployed: ```bash modal deploy script.py ``` `modal run` and `modal serve` do not activate schedules. ## Monitoring - View scheduled runs in the **Apps** section of the Modal dashboard - Each run appears with its status, duration, and logs - Use the **"Run Now"** button on the dashboard to trigger manually ## Management - Schedules cannot be paused — remove the schedule and redeploy to stop - To change a schedule, update the `schedule` parameter and redeploy - To stop entirely, either remove the `schedule` parameter or run `modal app stop ` ## Common Patterns ### ETL Pipeline ```python @app.function( schedule=modal.Cron("0 2 * * *"), # 2 AM UTC daily secrets=[modal.Secret.from_name("db-creds")], timeout=7200, ) def etl_pipeline(): import os data = extract(os.environ["SOURCE_DB_URL"]) transformed = transform(data) load(transformed, os.environ["DEST_DB_URL"]) ``` ### Model Retraining ```python @app.function( schedule=modal.Cron("0 0 * * 0"), # Weekly on Sunday gpu="H100", volumes={"/data": data_vol, "/models": model_vol}, timeout=86400, ) def retrain(): model = train_on_latest_data("/data/training/") torch.save(model.state_dict(), "/models/latest.pt") ``` ### Health Checks ```python @app.function( schedule=modal.Period(minutes=5), secrets=[modal.Secret.from_name("slack-webhook")], ) def health_check(): import os, requests status = check_all_services() if not status["healthy"]: requests.post(os.environ["SLACK_URL"], json={"text": f"Alert: {status}"}) ``` > The webhook URL is read from a Modal Secret (`SLACK_URL`), not hardcoded or taken > from untrusted input. Keep notification endpoints in Secrets and avoid POSTing to > URLs constructed from user-supplied data.