Deploy FastAPI on Cloudflare Python Workers (2026)
Deploy FastAPI on Cloudflare Python Workers (2026) — Cloudflare declared Python Workers generally available on 21 Sep 2026. You can now run FastAPI at the edge with the built-in workers.asgi bridge, manage deps with uv + pywrangler, and reach Workers AI, Hyperdrive, R2, and the rest of the platform without a separate uvicorn host. This guide is a working quickstart plus optional static frontend and one AI call.
PyInns already covers same-process SPA serving with FastAPI app.frontend(), containerized production with FastAPI + Docker + PostgreSQL, and streaming LLM tokens with FastAPI SSE. Those stay on classic hosts. This post is the Workers path: ASGI entrypoint, wrangler.jsonc, and edge bindings.
TL;DR
- Python Workers GA (Sep 2026): FastAPI via
from workers import asgithenDefault = asgi.entrypoint(app) wrangler.jsonc:compatibility_flags: ["python_workers"],compatibility_datenear todaypyproject.toml:requires-python >=3.13,fastapi; dev groupworkers-py,workers-runtime-sdk- Local:
uv run pywrangler dev→curl http://localhost:8787/ - Optional: Static Assets binding + catch-all (Workers equivalent of
app.frontend()); Workers AI viarequest.scope["env"].AI.run; Hyperdrive +aiomysql/asyncpgfor TCP DBs
Why Workers instead of uvicorn + Docker?
| Concern | Classic FastAPI (uvicorn/Docker) | Python Workers |
|---|---|---|
| Process model | Long-lived ASGI server you operate | Request-scoped Worker; Cloudflare runs the ASGI bridge |
| Cold path / scale | You size replicas and health checks | Global edge isolate; pay for CPU time |
| Bindings | Env vars + your own clients | Native env (AI, Hyperdrive, R2, D1, Queues, …) |
| Static SPA | app.frontend() / disk StaticFiles |
Assets binding + catch-all proxy (files not in the Worker bundle) |
| Python version | Whatever you install | Workers expect >=3.13 in current docs |
Keep Docker + Postgres when you need heavyweight local disks, long-lived WebSocket fan-out you already operate, or drivers Workers still do not support. Use Workers when you want FastAPI next to Cloudflare bindings with minimal ops.
Prerequisites
- Node tooling for Wrangler (via
workers-py/pywrangler) - uv installed
- A Cloudflare account (deploy later; this tutorial stops at local
pywrangler dev)
Official package page (paraphrased here; always re-check dates/flags): FastAPI on Cloudflare Workers. GA announcement: Python Workers are now generally available.
Minimal project layout
.
├── pyproject.toml
├── wrangler.jsonc
└── src
└── main.py
1. FastAPI app + ASGI entrypoint
FastAPI speaks ASGI. On Workers you do not start uvicorn yourself — you hand the app to the runtime bridge. The short form uses asgi.entrypoint. The longer form subclasses WorkerEntrypoint and calls asgi.fetch when you need more control over env.
# src/main.py
from fastapi import FastAPI
from workers import asgi
app = FastAPI(title="PyInns Cloudflare Workers demo")
@app.get("/")
def read_root() -> dict[str, str]:
return {"Hello": "World"}
@app.get("/api/health")
def health() -> dict[str, str]:
return {"status": "ok"}
# Short form (recommended for most apps)
Default = asgi.entrypoint(app)
# Equivalent long form if you need the class:
# from workers import WorkerEntrypoint
# class Default(WorkerEntrypoint):
# async def fetch(self, request):
# return await asgi.fetch(app, request, self.env)
The exported name Default is the Worker entrypoint Wrangler loads. Do not omit it — without an entrypoint the isolate has nothing to call.
2. wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-fastapi-app",
"main": "src/main.py",
// Keep this near deploy day; Workers gates features on the date
"compatibility_date": "2026-09-23",
"compatibility_flags": ["python_workers"]
}
The python_workers flag is required. Bump compatibility_date when you intentionally adopt newer runtime behavior — do not copy an ancient date from a blog sample without checking.
3. pyproject.toml
[project]
name = "my-fastapi-app"
version = "0.1.0"
description = "FastAPI on Cloudflare Python Workers"
requires-python = ">=3.13"
dependencies = [
"fastapi",
]
[dependency-groups]
dev = [
"workers-py",
"workers-runtime-sdk",
]
workers-py gives you the pywrangler CLI wrapper; workers-runtime-sdk provides the in-Worker workers module types/runtime pieces used locally. Add only packages that Workers actually support (pure Python / available Pyodide or PyEmscripten wheels) — see Cloudflare’s supported packages list before pinning heavy native wheels.
4. Run locally
uv sync
uv run pywrangler dev
# other terminal
curl http://localhost:8787/
# {"Hello":"World"}
curl http://localhost:8787/api/health
# {"status":"ok"}
Default local port is 8787 (Wrangler). If the port is taken, check the pywrangler / Wrangler banner for the actual URL.
Optional — static frontend via Assets (Workers app.frontend)
On a normal VPS, app.frontend() reads files from disk. On Workers, put the SPA build in something like ./public/ and bind Cloudflare Static Assets so the files live in the global asset store — not inside the Worker bundle.
{
"name": "my-fastapi-app",
"main": "src/main.py",
"compatibility_date": "2026-09-23",
"compatibility_flags": ["python_workers"],
"assets": {
"directory": "./public/",
"binding": "ASSETS",
"run_worker_first": true
}
}
run_worker_first: true sends every request to FastAPI first so /api/... wins. Then add a catch-all that proxies unmatched paths to the binding:
# append to src/main.py (after API routes; catch-all last)
from fastapi import Request
from fastapi.responses import Response
@app.get("/api/hello")
async def api_hello() -> dict[str, str]:
return {"message": "Hello from the API"}
@app.get("/{path:path}")
async def frontend(path: str, request: Request):
env = request.scope["env"]
asset_url = f"https://assets.local/{path}"
resp = await env.ASSETS.fetch(asset_url)
body = await resp.bytes()
return Response(content=body, status_code=resp.status, headers=resp.headers)
Drop a built index.html (and hashed JS/CSS) under public/. /api/hello stays JSON; /index.html and other asset paths come from ASSETS. More detail: Workers Static Assets.
Optional — one Workers AI call
GA also made common AI libraries (including openai, LangChain, and MCP clients) workable on the same runtime, and ships langchain-cloudflare when you want chains aimed at Workers AI. The smallest practical demo uses the binding directly:
from fastapi import FastAPI, Request
from workers import asgi
app = FastAPI()
@app.get("/api/ai-ping")
async def ai_ping(request: Request):
env = request.scope["env"]
# Model id will change — pick a current Workers AI model from the dashboard/docs
result = await env.AI.run(
"@cf/meta/llama-3.1-8b-instruct",
{
"messages": [
{"role": "system", "content": "Reply in one short sentence."},
{"role": "user", "content": "Say hello from a Python Worker."},
]
},
)
return {"result": result}
Default = asgi.entrypoint(app)
Enable Workers AI on the Worker (Wrangler ai binding / dashboard) before calling env.AI. For token streaming over HTTP on a classic host, reuse the patterns in the SSE LLM guide — wire the generator to Workers AI only after you confirm streaming support for your chosen model API.
Databases — Hyperdrive + TCP drivers (brief)
Before GA, Python Workers lacked TCP sockets, so typical MySQL/Postgres drivers did not work. With the Workers Connect API and Hyperdrive, documented paths include aiomysql and asyncpg through a Hyperdrive connection string. Configure a Hyperdrive binding in Wrangler, read it from request.scope["env"], and use the async driver Cloudflare documents for that binding — do not assume every PyPI DB package is available. For a full Docker Compose Postgres stack on your own VMs, stick with the Docker + PostgreSQL production setup.
Deploy checklist (when you are ready)
uv run pywrangler deploy(or the Wrangler deploy flow your team standardizes)- Confirm
compatibility_flagsandcompatibility_datein production config - Bind only what you use (AI, Hyperdrive, Assets, secrets)
- Smoke-test
/api/healthand any catch-all frontend paths on the*.workers.devURL
This draft stops short of production deploy steps on purpose — validate locally first.
Wrap-up
Python Workers GA turns FastAPI into an edge-native option: export Default = asgi.entrypoint(app), set python_workers in wrangler.jsonc, pin Python >=3.13 with fastapi + the workers dev tools, and iterate with uv run pywrangler dev. Add Static Assets when you need the Workers equivalent of app.frontend(), call Workers AI from request.scope["env"] for inference next to your routes, and use Hyperdrive when you need MySQL/Postgres over TCP. Pair this with PyInns’ SPA, Docker/Postgres, and SSE posts depending on whether you stay on classic hosts or move the API to the edge.