Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild
Back to Blog Explainer

Python in the Cloud: Four Workload Shapes and How Each One Deploys

Sean

Platform Writer

Sep 10, 2026
8 min read

Most Python deployment problems are a mismatch between what the code is and where it was put. A scheduled script does not need a web server. A web application does not run correctly under a scheduler. A long-lived worker will be killed by anything that expects requests. Getting this right at the start removes most of the difficulty, and it comes down to identifying which of four shapes your code actually is.

Python in the Cloud: Four Workload Shapes and How Each One Deploys

The shapes are: a web application, a scheduled job, an always-on worker, and a notebook. They have almost nothing in common operationally, and the deployment question is nearly settled once you name yours.

Table of contents

Shape one: the web application

Anything built on Flask, Django, FastAPI or similar. It listens on a port, handles requests, and should be running at all times.

The thing to understand is that the development server is not the production server. Every Python web framework ships one, every framework’s documentation says not to deploy it, and it gets deployed constantly. It is single-threaded, it has no process management, and it will fall over.

In production you want a proper application server:

# WSGI, for Flask and Django
gunicorn app:app --bind 0.0.0.0:$PORT --workers 4

# ASGI, for FastAPI and async Django
uvicorn app:app --host 0.0.0.0 --port $PORT --workers 4

Three details that account for most first-deploy failures:

  • Bind to 0.0.0.0, not 127.0.0.1. Inside a container, localhost means the container, and nothing outside can reach it. This is the single most common cause of a service that starts cleanly and cannot be reached.
  • Read the port from the environment. Most platforms tell you which port to listen on; hard-coding 8000 works locally and not there.
  • Worker count is roughly two per CPU core, plus one. More workers on a small instance means more memory used and no more throughput.

Shape two: the scheduled job

A script that runs, does something, and exits. Scraping, reporting, sending a digest, syncing two systems overnight.

The wrong instinct is to deploy it as a service with a while True and a sleep inside. That works and it means the code is running twenty-four hours a day to do four seconds of work, you have no record of whether a run succeeded, and a crash at three in the morning stops it silently until somebody notices the reports stopped.

A scheduled job wants a scheduler, and the properties that matter:

  • Each run is recorded, with its exit code and output.
  • A failure is visible without anyone checking.
  • Runs do not overlap if one takes longer than the interval.
  • The schedule is configuration rather than something compiled into a loop.

If the only available option is a long-running process, at least make it observable: log the start and finish of each cycle, exit non-zero on failure so a restart policy notices, and write a heartbeat somewhere you can check.

Shape three: the always-on worker

A process that stays alive on purpose — consuming a queue, holding a websocket connection, listening for events, running a bot.

This one looks like a web service and is not, in one important way: nothing sends it HTTP requests, so any platform that decides a process is healthy based on responding to a health check will consider it dead. Some hosts will scale it to zero for the same reason, which for a queue consumer means the queue stops being consumed.

What a worker needs:

  • To be run as a background process type, not a web process.
  • A restart policy, because it will die eventually and should come back.
  • Graceful shutdown handling. When the platform sends a termination signal, finish the current item and then exit, rather than losing it.
  • Somewhere to record progress, because with no requests there is nothing to look at unless it logs.

If your platform only offers web services, the usual workaround is to run a minimal HTTP endpoint alongside the worker purely to satisfy health checks. It is a hack, it works, and it is a good indicator that you should check whether a worker process type is available.

Shape four: the notebook

Exploratory or analytical work. Genuinely a different category, because the code is not the artefact — the output is.

The deployment mistake here is treating a notebook as an application. Notebooks are for exploration, and a notebook running on a schedule in production is a recurring incident. When analysis needs to run regularly, extract it into a script, pin the dependencies, and make it shape two.

The legitimate version of hosted notebooks is a managed environment for people doing analysis, sized for the data and shut down when idle, because idle notebook servers holding large amounts of memory are one of the great quiet expenses in cloud bills.

The details that apply to all four

Regardless of shape, the same handful of things determine whether a Python deployment is reproducible.

Pin dependencies properly. A requirements.txt full of unpinned package names installs different code on different days, and the resulting failure appears on a deploy that changed nothing. Use a lockfile, or at minimum pin exact versions.

Match the Python version deliberately. State it in your project configuration rather than accepting whatever the build image defaults to, and check that it matches your local version. Subtle behaviour differences between minor versions are real.

Treat the filesystem as temporary. Anything written to local disk disappears on the next deploy unless you have attached a persistent volume. Uploads belong in object storage; a SQLite file on an ephemeral disk is a bug with a delayed fuse.

Configuration comes from the environment. Not from a file in the repository. Secrets especially, and secrets particularly should not be in a settings module that gets committed.

Log to standard output. Not to a file the platform cannot see. Structured lines, at a sensible level, so that when something fails you have the failing request and the failing build in the same place.

Choosing where it runs

With the shape identified, the target is close to decided:

  • Web application: a platform that builds from a repository and runs a web process, with a live route and certificates handled. A container platform if you need a specific system dependency.
  • Scheduled job: a scheduler with run history. Failing that, a small service with a real scheduling library and logging you trust.
  • Always-on worker: a background process type with a restart policy, deliberately not behind a health check that expects HTTP.
  • Notebook: a managed analysis environment, sized to the data and stopped when idle.

Most projects turn out to need two of these — a web application and a worker, or a web application and a nightly job — which is the actual reason to care about running them on one platform rather than three. The database, the environment variables and the logs should be in one place.

How this fits the rest of the stack

Once the shape is named, the remaining question is what the pieces cost together rather than separately, and that is easier to see than to estimate: the RunxBuild hosting calculator prices the service, the database, the storage and the bandwidth as individual lines, which is also the quickest way to see what adding a worker beside a web application actually changes.

RunxBuild deploys Python from a GitHub repository with build logs, a live route, environment variables, custom domains, runtime logs, metrics and rollback to a previous deploy, on a ladder that starts at $4 for a Dev plan and $6 for Basic, with managed Postgres or MySQL beside it on the same private network. Persistent storage attaches to a service when the filesystem genuinely needs to survive a redeploy.

Useful related references:

FAQ

How do I run Python in the cloud?

Identify the workload shape first. A web application needs an application server such as gunicorn or uvicorn bound to the platform’s port. A scheduled script needs a scheduler with run history. A long-lived consumer needs a background process type. Matching the shape to the target removes most deployment problems.

Why can nobody reach my Python app after deploying?

Almost always because it binds to 127.0.0.1 rather than 0.0.0.0. Inside a container, localhost refers to the container itself, so nothing outside can connect. The second most common cause is hard-coding a port instead of reading it from the environment.

Should I use gunicorn or uvicorn?

Gunicorn for WSGI frameworks such as Flask and traditional Django. Uvicorn for ASGI frameworks such as FastAPI and async Django. A common production arrangement runs uvicorn workers under gunicorn, which gives ASGI support with gunicorn’s process management.

Can I run a Python script on a schedule in the cloud?

Yes, and a real scheduler is better than a loop with a sleep in it. A scheduler records each run and its exit code, makes failures visible, and prevents overlapping runs. A script sleeping in a loop gives you none of those and fails silently.

Why does my Python app lose files after each deploy?

Because the filesystem is ephemeral by default and is recreated on every deploy. Uploads belong in object storage and databases belong in a database. If local disk really is required, attach a persistent volume before relying on it.

#python cloud#deploy python#python hosting#gunicorn#python web app