ASGI Apps
Deploy a FastAPI, Starlette, or any ASGI application and own the whole request lifecycle.
@rb.asgi_app deploys a full ASGI application — FastAPI, Starlette, or anything
else that speaks ASGI — onto Cloud Run. The decorated function builds and returns
the app; Rebase packages the source, builds the image, and serves it behind a
stable URL.
Use it when a single request/response function is not enough:
| You want | Use |
|---|---|
| One function over HTTP, with platform auth and run records | @rb.endpoint |
| Many routes, your own middleware, your own auth scheme | @rb.asgi_app |
Minimal App
import rebase as rb
image = rb.Image.python("3.12").uv_pip_install("fastapi")
@rb.asgi_app(project="grid", name="grid-api", image=image)
def grid_api():
from fastapi import FastAPI
web_app = FastAPI()
@web_app.get("/zones/{zone}")
def read_zone(zone: str):
return {"zone": zone}
return web_app
rb.deploy(grid_api)The app is served at /e/<workspace>/<project> on the API host. Routes hang off
that prefix, so /zones/SE3 above is reachable at
/e/<workspace>/grid/zones/SE3.
Configuration
@rb.asgi_app accepts the same deployment controls as functions:
@rb.asgi_app(
project="grid",
name="grid-api",
base_path="/api", # mount the app under a sub-path
auth="api_key", # api_key (default), workspace, or public
image=image,
env={"LOG_LEVEL": "info"},
secrets=[rb.Secret.from_name("grid-credentials")],
volumes={"/data": rb.Volume.from_name("grid-cache")},
min_instances=1, # keep a warm instance
max_instances=10,
concurrency=80,
timeout_seconds=300,
)
def grid_api():
...Authentication
The auth argument works exactly as it does for endpoints — api_key (the
default), workspace, or public. See
Authentication.
Set auth="public" when the app authenticates callers itself:
@rb.asgi_app(project="grid", name="grid-api", auth="public", image=image)
def grid_api():
...The Authorization header is not forwarded
Rebase invokes your app's private Cloud Run service using its own Google service
account, and that credential occupies the Authorization header. Your app
receives a Google-signed ID token, not the caller's token:
{
"iss": "https://accounts.google.com",
"email": "workflow-mvp-api@<project>.iam.gserviceaccount.com",
"aud": "https://rebase-as-....a.run.app"
}This matters because it fails quietly rather than loudly. FastAPI's
HTTPBearer, OAuth2PasswordBearer, and APIKeyHeader(name="Authorization")
all read that header, so they will happily validate the platform's token and
report a caller who was never authenticated.
Carry your own credential in a different header instead:
@rb.asgi_app(project="grid", name="grid-api", auth="public", image=image,
env={"GRID_API_TOKEN": "..."})
def grid_api():
import os
from fastapi import Depends, FastAPI, Header, HTTPException
web_app = FastAPI()
def verify(x_api_token: str = Header(default="")) -> str:
if x_api_token != os.environ["GRID_API_TOKEN"]:
raise HTTPException(status_code=401, detail="bad or missing token")
return "caller"
@web_app.get("/secure")
def secure(caller: str = Depends(verify)):
return {"caller": caller}
return web_appcurl "$APP_URL/secure" -H "X-Api-Token: ..."Custom headers pass through untouched. In production, keep the expected value in
a Secret rather than env.
Writing the App Function
The decorated function is shipped as source and re-executed remotely, which puts two constraints on how you write it.
Import inside the function. Imports at module scope must also resolve on the
machine running deploy, and fastapi usually is not installed there.
Type annotations resolve against module globals, not the function's locals.
FastAPI inspects annotations with typing.get_type_hints, which only sees module
globals — so a name imported inside the function is invisible to it. This
silently misreads the parameter rather than erroring:
# Broken: `Request` was imported inside the function, so FastAPI cannot resolve
# the annotation and treats `request` as a required query parameter.
@web_app.get("/echo")
def echo(request: Request):
...# Works: Header/Query/Body parameters with plain types.
@web_app.get("/echo")
def echo(user_agent: str = Header(default="")):
return {"user_agent": user_agent}Dependencies
Pin dependencies so versions stay reproducible — an unpinned install warns at
deploy time. Pin to something current, though: the base image tracks
fastapi>=0.135.2 and its matching Starlette, and an older FastAPI pinned
against that Starlette fails at startup with TypeError: Router.__init__() got an unexpected keyword argument 'on_startup'.
image = rb.Image.python("3.12").uv_pip_install("fastapi==0.141.1")If a deploy fails with "The user-provided container failed to start and listen
on the port defined by the PORT=8080 environment variable", the app raised
during import or startup; the Cloud Run logs for the rebase-as-* service carry
the traceback.
Retrieve a Deployed App
app = rb.get_asgi_app("grid/grid-api")rebase deploy app.py
