Skip to content

Deploy to Supabase

One Hono app, three runtimes. On Supabase the entry is apps/server/src/entry/deno.ts, the database is the project’s Postgres and the bucket is Supabase Storage.

Runtime Database Storage
Edge Function (Deno) Supabase Postgres Supabase Storage

An edge function cannot stream a 50–200 MB request body. That is why publishing is three steps and the bundle never passes through the API: the CLI hashes the zip locally, asks for a signed upload URL, PUTs straight to the bucket, then calls confirm. Only the manifest crosses this process. The same shape is what keeps a large publish off the critical path on every other target too.

The other consequence: migrations do not run on boot here. supabase db push owns the schema.

packages/cli/src/providers/supabase.ts is a flat list of five steps. Each one is printed before it runs; steps that change remote state ask for confirmation unless you pass -y. If supabase is not on PATH, or you pass --dry-run, the whole list is printed and nothing is executed.

Terminal window
npx @open-ota/cli init --provider supabase
Step Command Note
Link this directory to your Supabase project supabase link --project-ref <project-ref> Printed only, never run — a command still holding a <placeholder> is left for you.
Push the schema supabase db push Runs the Open OTA migrations against the linked project’s Postgres.
Create the bundle bucket supabase storage create ss:///ota-bundles Older CLIs have no storage command; create it in the dashboard (Storage → New bucket, public) if it fails.
Set the function secrets supabase secrets set OTA_MASTER_KEY=… OTA_MODE=self STORAGE_BUCKET=ota-bundles The master key is generated by the CLI for this run.
Deploy the Edge Function supabase functions deploy ota --no-verify-jwt The function does its own Bearer auth; Supabase JWT verification would reject CLI tokens.

The provider only provisions. ota init still creates or links the project, writes ota.config.json and wires the native side, whether or not you pass --provider.

Terminal window
supabase link --project-ref <ref>
supabase db push # migrations in ./drizzle
supabase secrets set OTA_MASTER_KEY=... OTA_MODE=self STORAGE_BUCKET=ota-bundles
supabase functions deploy ota --no-verify-jwt # our own Bearer auth, not Supabase JWTs

The Supabase CLI bundles the function with Deno’s own resolver, so point it at this entry — supabase/functions/ota/index.ts:

import "../../../apps/server/src/entry/deno.ts";

Deno resolves specifiers literally and this codebase writes .js for .ts files, so the function needs a deno.json next to it:

{ "unstable": ["sloppy-imports"] }

Set as function secrets. STORAGE_DRIVER=supabase selects the Supabase Storage adapter, which signs uploads through /storage/v1/object/upload/sign/... and serves downloads from the public object URL.

Variable Value
DATABASE_URL The project’s Postgres connection string.
OTA_MASTER_KEY 32 random bytes, base64 (openssl rand -base64 32).
STORAGE_DRIVER supabase
SUPABASE_URL Project URL. Required by this driver.
SUPABASE_SERVICE_ROLE_KEY Service role key. Required by this driver.
STORAGE_BUCKET ota-bundles
PUBLIC_URL The function’s public URL, if you use preview links or remote MCP.

Full descriptions are on the configuration page.

The Edge Function does not serve static files. Run the dashboard locally against the deployed API with ota console, or deploy apps/dashboard/dist as a static site.

src/app.ts is identical on all three targets and is covered by the server test suite, including a run of the real migrations against a real Postgres engine in-process. The signing code is Web Crypto only, so it runs unchanged under Deno.