1 Secrets and Config
mitosis edited this page 2026-07-29 08:11:35 -05:00

Every app needs API keys and config; none of it belongs in git, issues, or chat. This page covers the two channels the platform gives you: self-serve secrets (Forgejo → sealed → running pod) and code-declared feature flags you can flip at runtime.

App secrets: put them in Forgejo, get them as env vars

Your app repo's Settings → Actions → Secrets is the secret store — https://git.espoautos.com/<org>/<app>/settings/actions/secrets. Add secrets under their natural names (OPENWEATHER_API_KEY, STRIPE_KEY); each becomes an environment variable of the same name in the running app.

The sync-env workflow moves them: it runs automatically on every push to main, or run it now via Actions → sync-env → Run workflow. The run's log prints a reconciliation table (key → scope), and the app rolls to pick up the new env.

Two conventions worth knowing:

  • Multiline or awkward values: put KEY=VALUE lines inside a single secret named APP_ENV — its entries are merged on top of (and win over) same-named secrets.
  • Reserved names are dropped: REGISTRY_TOKEN, FORGEJO_TOKEN / GITEA_TOKEN / GITHUB_TOKEN, APP_ENV itself, and anything PLAT_-prefixed never reach your app's env.

Prod vs dev scope: PLAT_DEV

By default every secret is production-only — the coding agent never sees it while building your app. To share a secret with the agent's throwaway build environment too, add a Forgejo Actions Variable (not a secret) named PLAT_DEV at https://git.espoautos.com/<org>/<app>/settings/actions/variables, whose value is a newline- or comma-separated list of secret names. Listed keys are sealed into a separate dev store injected into the agent's environment — and kept out of the production runtime secret. Naming a nonexistent secret in PLAT_DEV is a hard error, never silently skipped.

What happens underneath

Forgejo Actions secrets are write-only — sync-env is the one place that can read them. It POSTs them to the platform's in-cluster /ci/sync-env endpoint (authorized by proving its run token has push on this exact repo), which classifies each key, seals prod and dev stores as sops-encrypted Secrets in the gitops overlay, and bumps a revision annotation that rolls your pod. The human who triggered the sync is threaded into the gitops audit commit.

What never to do

Never paste a credential into an issue, a PR comment, or an agent brief. Comments are readable by everyone with repo access and live forever in history; secrets set through Settings → Actions → Secrets are write-only even to you. Steer agents with secrets — set the key, list it in PLAT_DEV if the agent needs it at build time — never by quoting its value.

Feature flags: merge dark, flip later

Every app generated from the template ships a flags layer — no SDK, no external service, stored in the app's own Postgres.

Declare in code: edit src/flags.ts in a PR — defineFlags({ "checkout.express": { default: false, description: "...", kind: "release", retire: "2026-08-15" } }). Kinds are release | ops | experiment | permission; release flags must declare a retire date, and a test fails the suite once that date passes — delete the expired flag, not the test.

Toggle at runtime (~15s to land, no redeploy):

Surface What it does
GET /api/flags Resolved flags for the current caller
PUT /api/flags/:key {value, scope} Manager toggle; scope '' (global), team:<name>, or user:<login>; value: null clears
PUT /api/flags/:key/me {value} Caller's own value, for flags declared userOverridable: true
GET /.well-known/plat/flags Manage-gated discovery of all definitions
Env FLAG_<KEY> Emergency kill-switch; beats everything; needs a redeploy, survives DB loss

Precedence, most operational first: env kill-switch → user override → team override → global override → preview auto-light → code default.

Previews auto-light release flags: in a PR preview environment, boolean release flags resolve on, so the reviewer and validator see the dark feature while prod keeps the off default until you flip it. Explicit overrides still win.

The doctrine this enables: prefer a flag over a dependency edge. Reserve issue blocked-by edges for true cross-repo contract dependencies; sequence features within one app by merging each dark behind a release flag plus one "flip and retire" issue — see Building with Issues. One honest edge: the manager toggle surface requires the platform's header-trust auth mode (PLAT_FORWARD_AUTH=1); in default in-app OAuth mode, env kill-switches and code defaults still work but PUT /api/flags/:key is unavailable. Team-scoped overrides are declared but dormant: the edge does not yet send the X-Plat-Teams header, so team rows never match a caller today.