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 wantUse
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_app
curl "$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

On this page