Flootdocs

Going to production

What goes live when you ship a Floot app, the order it ships in, the rules that hold on every deploy, and a launch checklist.

Everything you have run so far lived on your machine: a local Supabase stack, Mailpit catching the mail, and an app reading .env.local. Going live replaces each of those with a hosted counterpart, and this section walks through every one. Start here for the shape of it, then follow the page for your backend.

What goes live

A Floot app ships as three parts, and each has its own production copy:

PartLocallyIn production
Databasesupabase start, rebuilt by db resetA hosted Postgres that only ever moves forward, one migration at a time
Edge Functionssupabase functions serve, supabase/.env.localDeployed functions reading their own secrets, set on the project
Clientsflutter run, reading .env.localA web build on a static host and store builds, each reading a bundled .env

Around those sit the settings that never live in a migration: the Vault rows your SQL reads, the Auth configuration (Site URL, redirect URLs, providers, the send-email hook), and the outside services the kit talks to, your SMTP provider and, if you use them, Stripe, RevenueCat and the OAuth providers.

Supabase Cloud is the default home for the backend, and Supabase Cloud takes a project from nothing to a working deploy. To run the backend on your own server instead, Self-hosted starts from Supabase's official Docker setup. Whichever you choose, Web builds and hosts the web app, and Mobile gets the iOS and Android apps ready for the stores.

The deploy order

Every release ships in the same order, backend first:

The order follows from who reads what. New functions may call a column the migration adds, so the schema lands first. The clients go last because they call both, and because a store build takes days to reach every phone: for that whole time, old app versions keep calling your newest backend.

The rules that hold on every deploy

Four rules apply whichever backend you choose. Each one exists because breaking it fails silently, often long after the deploy looked fine.

--skip-vault on every db push

supabase/config.toml carries a [db.vault] block so that supabase start can seed five secrets into your local Vault. The same block runs on db push: any push that applies at least one migration first writes every Vault value it can resolve, from your shell, supabase/.env.local or supabase/.env, into the remote Vault. That write is committed before the migrations run, so even a push whose migration then fails leaves production holding your laptop's values.

Pointing the CLI at a production env file does not prevent it: supabase/.env.local still loads and wins over .env.production. So the rule is absolute: pass --skip-vault on every push, and set the Vault by SQL, as Vault by SQL shows.

Expand and contract

Every migration must work with the functions and the app of the release before it, because the previous functions keep running against the new schema for the seconds a deploy takes, and the previous app for as long as people keep it installed.

So a breaking change ships in two releases. The first expands: it adds the new column, table or function alongside the old one, and the code moves to the new shape. A later release contracts: once nothing reads the old shape, it drops it. A rename, for example, is an add and a backfill in one release and a drop in another.

Migrations only move forward. Once one has run in production, you never edit it; a fix is a new migration.

Never copy local env values

.env.local and supabase/.env.local hold values made for your local stack: generated peppers and webhook secrets, the CLI's fixed local keys, URLs such as http://kong:8000 that mean nothing outside Docker. Production gets its own fresh set, generated for it and stored in a password manager or secret store, never in the repository.

The danger is less a wrong value than a shared one. A local secret has been in shell history, in screenshots and on every machine you cloned to; reusing it in production publishes it there.

The app's .env is public

A release build reads .env, bundled into the app as an asset (see Building for release). Anything bundled into a client is readable by anyone who has the client: a store binary unzips, and a web build serves the file to any browser at /assets/.env.

That holds today because the file carries only client-safe values: your Supabase URL and publishable key, the ENABLE_* flags and OAuth client ids. Keep it that way. The secret key, webhook secrets, SMTP credentials and peppers belong in your function secrets and Vault, never in any file the app bundles.

Both env files ship

pubspec.yaml still lists .env.local after you add .env, so a release build bundles both, and a web build serves both. Never put anything in either file that you would not publish.

Launch checklist

Work through it once before the first real user signs up. Each item links to the place that explains it.

Backend

Feature secrets

  • Mail goes through a real provider, from a sender on its verified domain: Moving to a real provider and Mail.
  • If you use Stripe: a live webhook endpoint points at your deployed stripe-webhook, and its signing secret is a function secret: Stripe webhooks.
  • If you use RevenueCat: the webhook points at your deployed revenuecat-webhook, with its authorization secret set on both sides: RevenueCat webhooks.
  • If you offer Sign in with Apple: the client secret is generated, and a reminder is set to regenerate it before it expires, every 6 months: Generate Secret Key.

Content

Clients

  • Each release build bundles a .env holding your production URL and publishable key, and nothing secret: Building for release.
  • The web build serves CanvasKit from your origin, and its host revalidates every file, falls back to index.html for app routes only and sends a report-only CSP: Web.
  • The Site URL is your web origin, and your web app's paths (https://app.myapp.com/**) and your app callback are on the Auth redirect list: Auth redirect URLs.
  • The web app serves both association files as application/json, filled with your Apple team ID and Android signing fingerprints, and iOS has Associated Domains on your web domain: Deep-link domains.
  • Your bundle id and package name are your own, not com.example, and the iOS team is the one that publishes: Your identifiers are permanent and iOS: team and bundle id.
  • Android release builds are signed with your own key, and Google Sign-In knows Play's app signing key: Android: replace the debug signing.
  • The smoke check passes against the live project: Smoke check.

On this page