Turning a Python Script Into a 24/7 Paid Service

·5 min read·Updated
pythonworkersschedulingbeginnershow-to

You have a script. It checks a page, posts to an API, tidies a spreadsheet — and it works when you run it. Getting it to run continuously somewhere isn't really a hosting question: a script runs once and exits, and a service doesn't. The gap is five small changes: put the work in a loop with a sleep, make each cycle independent, exit loudly when something is genuinely broken, flush your output so you can see what happened, and keep any state on a volume. Do those and you deploy it as a worker with no ports — nothing to route, nothing to keep awake, and restarts handled for you, so you don't need cron or a process manager.

Continuous operation requires credit on your balance. On a new account, the first app gets a short trial run of this loop, then stops until you add credit.

The shape

import os, sys, time, traceback

INTERVAL = int(os.environ.get("INTERVAL_SECONDS", "300"))

def do_one_cycle():
    ...  # your existing script body

print(f"worker starting interval={INTERVAL}s", flush=True)

while True:
    started = time.monotonic()
    try:
        do_one_cycle()
    except Exception:
        traceback.print_exc()
    elapsed = time.monotonic() - started
    time.sleep(max(0, INTERVAL - elapsed))

That's genuinely most of it. Four details are doing real work, though, and they're the difference between a script that runs and a service you can leave alone.

1. One bad cycle shouldn't end the process

The try around the cycle is the important line. Without it, a single network blip ends your loop — and although the platform restarts the process, you've turned a transient failure into a restart cycle rather than a logged hiccup.

Catching per-iteration means the worker rides out the internet being briefly unavailable, which it will be.

2. Exit anyway when something is unrecoverable

The opposite case matters just as much. If your API key is missing or rejected, no amount of retrying helps — and a process that keeps running while failing forever is worse than one that stops, because nothing here is watching to tell you.

if not os.environ.get("API_KEY"):
    sys.exit("API_KEY is not set - set it as an environment variable on the app")

Exiting non-zero puts a code in the event history and makes the failure visible instead of silent. Handle transient errors, escalate permanent ones.

3. Flush your output, or you'll see nothing

Python buffers stdout when it isn't attached to a terminal, so a worker can run for an hour and log nothing until the buffer fills — and if it crashes first, you lose it entirely.

Either pass flush=True on your prints, or set PYTHONUNBUFFERED=1 as an environment variable on the app. The second is one setting and covers everything, including libraries.

Then log one line per cycle, including the boring ones. "Checked 12 items, 0 new" is what tells you the worker is alive; silence tells you nothing at all, and container logs rotate, so keep it to one line rather than a paragraph.

4. Sleep last, and account for the work

sleep(INTERVAL) after a cycle that took two minutes gives you a cycle every seven minutes, not every five. Subtracting the elapsed time keeps the cadence honest.

If the job needs to happen at a specific time of day rather than on an interval, remember the container's clock is UTC unless you say otherwise — that's the whole timezone story.

5. State goes on a volume, and restarts are normal

Your worker will restart — deploys, configuration changes, the occasional crash. Write anything it needs to remember to a volume, and design so a restart is harmless:

  • Record what you've done, not what you're about to do. "Last processed ID 4102" survives a restart; "currently processing" doesn't.
  • Make cycles idempotent. If the same item gets picked up twice because the process died mid-cycle, nothing bad should happen.
  • Don't rely on a graceful shutdown. There's about a second between the stop signal and the kill, so a handler that tries to finish the current batch usually won't. Short, repeatable cycles beat careful cleanup.

That last point is why the loop shape works so well here: if every cycle is small and safe to repeat, a restart costs you at most one cycle's work.

Deploying it

Nothing exotic. Upload the folder as a zip and the build agent recognises Python and works out your dependencies — no Dockerfile needed. Don't add a port binding: a worker has no inbound traffic, so there's nothing to route and nothing to keep awake.

Set your interval and any keys as environment variables so you can tune them without redeploying. INTERVAL_SECONDS in particular is worth having, because the right value is usually discovered rather than known.

Do you need cron instead?

Sometimes people reach for a scheduler out of habit. Where your process runs continuously, the loop is the scheduler.

Cron-shaped thinking is still useful when the work must happen at exact times, or when runs are hours apart and you'd rather not have a process idling in between. More on both patterns.

FAQ

How do I make a Python script run continuously?

Wrap the work in a while True loop with a sleep, handle errors per cycle, and deploy it as an app with no ports.

Do I need cron?

No. The sleep in your continuously running loop schedules the work.

Why can't I see any log output?

Python buffers stdout when it isn't a terminal. Set PYTHONUNBUFFERED=1 or pass flush=True.

What if my script crashes?

The process is restarted for you. Catch transient errors inside the loop so a blip doesn't restart anything, and exit deliberately on unrecoverable ones.

Where do I keep state between runs?

On a volume. Anything in the container's own filesystem is discarded when it's recreated, which happens on every deploy.

Do I need a Dockerfile?

Not for Python. Uploading a zip lets the build agent detect the runtime and dependencies for you.

Wrapping up

The distance between a script and a service is smaller than it looks, and most of it is defensive rather than clever: survive a bad cycle, stop loudly when stopping is correct, say what you did, and assume you'll be restarted at an inconvenient moment. Get those right and the thing you wrote for your own machine becomes something that quietly runs for months — which is, usually, the entire goal.