1 Secrets and Config
mitosis edited this page 2026-09-02 08:56:51 +00: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.open-platform.sh/<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. To run it now, open 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 merge on top of same-named secrets and win.
  • 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 builder never sees it while building your app. To share a secret with the builder's throwaway build environment, add a Forgejo Actions Variable (not a secret) named PLAT_DEV at https://git.open-platform.sh/<org>/<app>/settings/actions/variables. Its value is a newline- or comma-separated list of secret names.

The platform seals the listed keys into a separate dev store and injects that store into the builder's environment. The listed keys stay out of the production runtime secret. Naming a nonexistent secret in PLAT_DEV is a hard error, never a silent skip.

What happens underneath

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

What never to do

Warning: 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, and list it in PLAT_DEV if the builder needs it at build time. Never quote the value.

Feature flags: merge dark, flip later

Every app generated from the template ships a flags layer. There is no SDK and no external service. Flags live 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. A release flag must declare a retire date. A test fails the suite once that date passes — delete the expired flag, not the test.

Toggle at runtime (~15s to land, no redeploy). These are the runtime surfaces:

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. You and the reviewer 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.

Two honest edges:

  • The manager toggle surface requires the platform's header-trust auth mode (PLAT_FORWARD_AUTH=1). In the 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.