seekrit
Docs/Fly.io

Sync to Fly.io

Fly injects an app's secrets into a Machine when it boots, so there is no earlier point at which seekrit could supply them. A binding owns one Fly app's secret set — the same slot fly secrets set writes.

At a glance
What seekrit writesAn app's secrets, delivered to Machines as environment variables
Addressed byThe Fly app name — one secret set per app, shared by every Machine in every region
Connection carriesThe token alone. No account, team, region, or token kind
Token permissionA deploy token scoped to one app (fly tokens create deploy), or an org token
Takes effectNext Machine boot. Existing Machines keep their values until fly secrets deploy
Value visibilityWrite-only at Fly — fly secrets list shows names and digests, not values

New to sync? Read Third-party sync first — the decryption grant, name mapping, deletions, and failure handling are the same on every destination.

1. Create a token

fly tokens create deploy -a storefront-production

That mints a token scoped to a single app, which is enough to write its secrets and the least-privilege choice when a connection serves one destination. fly tokens org covers every app in an organization — reach for it only when one connection genuinely serves many apps.

Paste it whole, FlyV1 prefix included. Fly's two token shapes travel under different auth schemes, and seekrit picks the right one by reading the token — so there is nothing else to configure here. No account, team, or region, and no token kind to state the way Railway needs one:

printf '%s' "$FLY_API_TOKEN" | seekrit sync connect \
  --name acme-fly --provider fly

2. Bind an environment

A Fly app has one secret set, shared by every Machine in every region, so the app name is the whole destination — there are no targets to pick. Fly's own convention is that staging and production are separate apps, and that is the split a binding maps onto:

seekrit sync verify acme-fly --provider fly --fly-app storefront-production

seekrit sync enable --connection acme-fly --provider fly \
  --fly-app storefront-production \
  --app storefront --env production --acknowledge-decryption

--fly-app is Fly's app; --app is the seekrit application the environment belongs to. They are different things, which is why the flag is not just --app.

Verify fetches the app (GET /v1/apps/{app}) rather than reading its secrets — the same call flyctl makes on every deploy, so every token flavour that can push is known to be allowed it. A verify stricter than the push would reject setups that work. A token with read but not write access surfaces on the first push, in the run ledger, per name.

note

Pushed secrets are staged until Machines restart. Fly injects secrets when a Machine boots, so a push updates the app's secret set while already running Machines keep the values they started with. Machines created after the push have them; existing ones pick them up on fly secrets deploy -a storefront-production, or on any deploy.

seekrit will not roll your Machines for you. Rolling a Machine set is a deployment — a lease per Machine, each one's config resubmitted, health checks between batches — and a secrets push is the wrong place to be doing it. The failure mode is an app taken down to deliver an environment variable. A bare Machine restart is not a substitute either: it carries no secrets version, which is why flyctl itself does not use one here.

How a push behaves

  • One request for the whole environment. POST /v1/apps/{app}/secrets takes a single { values: { NAME: value } } map of up to 100 names, applied as a merge: a name with a value is set, a name with null is unset, and a name that is absent is left alone.
  • Merge semantics are what make this safe to point at an app that already has secrets. Fly's own FLY_* secrets, another binding's names, and anything you set by hand survive a push untouched, and a removal unsets just that name.
  • Sets and removals travel separately, so a rejected removal cannot report landed secrets as failures.
  • A run is capped at 100 requests, small on purpose: Fly's Machines API allows roughly one request per second per action, bursting to three. Since a normal environment is one or two calls, that ceiling only bounds something pathological.
  • Fly never sees a value it can echo back. Error text is built from Fly's own message fields, never from a request body.

Troubleshooting

SymptomCauseFix
Rate-limited on Test connectionFly allows about one request a secondWait a moment and try again. seekrit tells this apart from a refusal, because the advice is the opposite
401 on every nameThe token was revoked, or it is an app token for a different appMint a new token with fly tokens create deploy -a <app>
404 on the appThe app name is wrong, or the app was deletedApp names are lowercase letters, numbers, and dashes. fly apps list prints the exact one
Verify passes, pushes failA read-scoped token — verify uses the read call flyctl makes on deployUse a deploy or org token
Secrets pushed, app still on the old valuesFly injects at Machine bootfly secrets deploy -a <app>, or deploy. seekrit deliberately rolls nothing
Run reported partial, 429 in the errorFly rate-limited the tokenNothing to do — seekrit honors Retry-After and retries the rest

See also