Fly.io has become a favorite platform for developers who want Heroku-like simplicity with more control. Its global deployment model and developer-friendly CLI make it a great fit for web apps. But for Python cron jobs and scheduled scripts, Fly.io has meaningful limitations that make it a poor fit for most use cases.
This guide covers how Fly.io cron jobs work, what the real limitations are, and what to use instead if you just want to run a Python script on a schedule.
How Fly.io Handles Scheduled Tasks
Fly.io doesn't have a native "cron job" feature. Instead, you have a few options:
Option 1: Fly Machines with restart_policy + schedule
Fly.io supports Machine scheduling via its API and fly.toml. As of 2024, you can configure a Machine to run on a schedule using the Fly Machines API:
{
"schedule": "daily",
"restart": {
"policy": "no"
}
}
Limitation: The schedule options are coarse (hourly, daily, monthly) — no custom cron expressions. You can't say "every 15 minutes" or "Monday at 9 AM."
Option 2: Run a Persistent Worker with an Internal Scheduler
Deploy a Fly app with a Python process that uses schedule or APScheduler internally:
# app.py — runs as a persistent Fly Machine
import schedule
import time
def my_daily_job():
print("Running daily job...")
# your logic here
schedule.every().day.at("09:00").do(my_daily_job)
while True:
schedule.run_pending()
time.sleep(60)
Problem: This requires a Fly Machine to be running 24/7. At minimum scale (shared-cpu-1x, 256MB), that's ~$2.20/month. You're paying for a server that mostly does nothing.
Option 3: Dockerfile + Fly.io + Crontab Inside Container
Run a Docker container on Fly that uses the Linux crontab internally:
FROM python:3.11-slim
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY script.py .
COPY crontab /etc/cron.d/myjob
RUN chmod 0644 /etc/cron.d/myjob
CMD ["cron", "-f"]
# crontab file
0 9 * * * python /app/script.py >> /var/log/cron.log 2>&1
This works, but now you're managing:
- A Dockerfile
- Crontab syntax inside the container
- Log collection (Fly's log viewer shows container stdout, not cron logs by default)
- Fly.io deployment and scale-to-zero configuration
Realistic setup time: 45 minutes to 2 hours.
The Real Cost of Running Cron Jobs on Fly.io
For a script that runs once daily:
| Resource | Cost |
|---|---|
| Shared CPU 1x, 256MB RAM (minimum) | ~$2.20/month |
| 1 GB persistent volume (if needed) | $0.15/month |
| Bandwidth | Negligible for most scripts |
| Total | ~$2.35–5/month |
This is for a machine that runs 24/7 to execute one 10-second script per day. You're paying for 99.99% idle time.
With scale-to-zero configured, the machine sleeps, but it takes 1–3 seconds to wake up — which means your job doesn't run at the exact scheduled time.
Fly.io Cron vs. Purpose-Built Python Cron Platforms
| Feature | Fly.io Cron | LiteLambda |
|---|---|---|
| Custom cron expressions | ⚠️ Limited (daily/hourly only natively) | ✅ Full cron syntax |
| pip packages | ✅ (via Dockerfile) | ✅ (list them, no Dockerfile) |
| Execution logs per run | ⚠️ Container logs only | ✅ Per-execution dashboard |
| Failure alerts | ❌ (manual setup) | ✅ Email + Telegram built-in |
| Pay-per-execution | ❌ (always-on machine) | ✅ |
| Setup time | 45 min–2 hrs | 5 minutes |
| AI code assistant | ❌ | ✅ |
| Playwright support | ✅ (container-based) | ✅ |
| Monthly cost (1 daily script) | ~$2.35+ | $0–$4.99 |
When Fly.io Makes Sense for Cron
Fly.io is a good choice for scheduled tasks when:
- You already have a Fly.io app and want the cron to share its environment, database connection, or private network
- You need very long-running jobs (Fly supports running containers for hours or days)
- You want global deployment and need the job to run close to a specific geographic region
For standalone Python scripts that don't need to live inside a Fly.io private network, the setup complexity and cost aren't justified.
Tutorial: Run a Python Script on a Schedule Without Fly.io
Here's the same result with dramatically less setup using LiteLambda.
Scenario: A Python script that checks your Fly.io app's uptime via HTTP and sends a Telegram alert if it's down.
import requests
import os
from datetime import datetime
def handler(event, context):
"""
Health check for Fly.io app — runs every 5 minutes.
Sends Telegram alert if the app is down.
"""
app_url = os.environ["APP_URL"]
bot_token = os.environ["TELEGRAM_BOT_TOKEN"]
chat_id = os.environ["TELEGRAM_CHAT_ID"]
try:
resp = requests.get(app_url, timeout=10)
is_up = resp.status_code == 200
except requests.RequestException:
is_up = False
if not is_up:
message = f"⚠️ {app_url} is DOWN at {datetime.utcnow().strftime('%H:%M UTC')}"
requests.post(
f"https://api.telegram.org/bot{bot_token}/sendMessage",
json={"chat_id": chat_id, "text": message}
)
return {
"url": app_url,
"status": "up" if is_up else "down",
"checked_at": datetime.utcnow().isoformat()
}
Deploy on LiteLambda:
- Create a new cron job at /crons/new/
- Paste the code
- Add package:
requests - Add env vars:
APP_URL,TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID - Set schedule:
*/5 * * * *(every 5 minutes) - Save and test with Run Manually
This gives you a more reliable uptime monitor than running the same script inside your Fly app — because if Fly itself has an issue, your monitor on a separate platform will still catch it.
Migrating From Fly.io Cron to LiteLambda
1. Extract your script from the Dockerfile
If your Fly.io cron runs inside a container, pull out the Python script. The logic itself doesn't change.
2. Convert environment variables
Copy your secrets from fly secrets list and paste them into LiteLambda's Env Vars section.
3. Convert the cron expression
Fly.io uses standard cron syntax in its API. Your expression translates directly to LiteLambda.
4. Handle the entry point
Wrap your script in a handler function:
# Before (standalone script run by cron)
import requests
def main():
# your logic
if __name__ == "__main__":
main()
# After (LiteLambda)
import requests
def handler(event, context):
# your logic (call main() or put logic here)
result = main()
return {"status": "ok", "result": result}
def main():
# unchanged
pass
5. Test and go live
Click Run Manually to verify the output. Activate the schedule. Decommission your Fly Machine.
Summary
Fly.io is excellent for persistent web services and apps that need global edge deployment. For scheduled Python scripts, it requires Docker knowledge, container management, and you pay for idle compute.
If your goal is just to run a Python script on a schedule:
| Goal | Best Platform |
|---|---|
| Simple scheduled Python script | LiteLambda |
| Part of existing Fly.io app ecosystem | Fly.io persistent worker |
| Complex multi-step pipeline | Apache Airflow or Prefect |
| Enterprise AWS environment | Lambda + EventBridge |