blumeops/docs/how-to/configuration/rotate-fly-deploy-token.md
Erich Blume 2148714584 C0: retire Todoist blumeops-tasks; point task discovery at heph
Replace the Todoist-backed blumeops-tasks mise task with
`heph list --project Blumeops --json` (hephaestus, now at v1 prototype
on gilbert). Update task-discovery, rotation-reminder, and zk
references across docs; note the zk zettelkasten is migrating into
heph docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 21:32:10 -07:00

5 KiB

title modified last-reviewed tags
Rotate the Fly.io API Token 2026-05-04 2026-05-04
how-to
fly-io
secrets

Rotate the Fly.io API Token

How to rotate the Fly.io API token used to deploy flyio-proxy. The token lives in 1Password at op://blumeops/fly.io admin/add more/deploy-token and is consumed by mise run fly-deploy and the deploy-fly Forgejo workflow (via the FLY_DEPLOY_TOKEN secret).

When to rotate

  • Every 75 days (heph recurring task)
  • After any compromise / accidental disclosure
  • If fly deploy starts returning auth errors

Fly.io tokens default to a 20-year expiry, but a short rotation cadence limits the blast radius of an undetected leak. Token expiry is set to 90 days (longer than the rotation window), leaving a 15-day buffer if a rotation is delayed.

Scope

Use fly tokens create org, not deploy.

Scope What it grants Practical blast radius (this org)
deploy Manage one app and its resources Same single-app surface as org for current setup
org Manage one org and its resources Adds: ability to create new apps (billing abuse) and read org-level metadata
readonly Read one org Not enough to deploy
Personal access token Full account Excessive

The personal Fly org currently contains a single app (blumeops-proxy), so the marginal blast radius of org over deploy is small. The benefit of org is that fly status works without a Metrics token unavailable: ... context canceled warning. That warning happens because fly status always tries to fetch org-level metrics-token info, and an app-scoped deploy token can't query the org. The warning is benign but persistent and could mask a real future failure.

If a second Fly app is ever added to this org, reconsider — at that point the marginal scope cost of org grows.

Procedure

1. Authenticate flyctl with the current token

fly auth login

(Browser-based. Required to mint a new token, since the existing deploy token can't create tokens.)

2. Mint the new token and store it

The token is shown only once at creation, so combine the mint and the 1Password write into a single command. Pick the form for your shell.

fish:

op item edit on5slfaygtdjrxmdwezyhfmqsq "add more.deploy-token=(fly tokens create org --org personal --name 'blumeops-proxy deploy '(date +%Y-%m-%d) --expiry 2160h)" --vault vg6xf6vvfmoh5hqjjhlhbeoaie

bash / zsh:

op item edit on5slfaygtdjrxmdwezyhfmqsq "add more.deploy-token=$(fly tokens create org --org personal --name "blumeops-proxy deploy $(date +%Y-%m-%d)" --expiry 2160h)" --vault vg6xf6vvfmoh5hqjjhlhbeoaie

(2160h = 90 days, paired with the 75-day rotation cadence for a 15-day buffer.)

If you'd rather paste manually:

fly tokens create org --org personal --name "blumeops-proxy deploy $(date +%Y-%m-%d)" --expiry 2160h
op item edit on5slfaygtdjrxmdwezyhfmqsq 'add more.deploy-token=<paste-new-token>' --vault vg6xf6vvfmoh5hqjjhlhbeoaie

op validator gotcha: If op item edit returns Password item requires ps value, the item's primary password field is empty. The 1Password CLI validator rejects edits to a Password-category item with no primary password, even when you're only touching a section field. Set a placeholder once and future rotations will work:

op item edit on5slfaygtdjrxmdwezyhfmqsq 'password=unused - see deploy-token field' --vault vg6xf6vvfmoh5hqjjhlhbeoaie

3. Sync to Forgejo Actions

The deploy-fly workflow reads the same token from a Forgejo Actions secret named FLY_DEPLOY_TOKEN, populated by the forgejo_actions_secrets ansible role:

mise run provision-indri -- --tags forgejo_actions_secrets

4. Verify

mise run fly-deploy

A successful deploy confirms the new token works locally. Watch for the metrics-token warning — it should be absent with an org-scoped token. If still present, the rotation produced a deploy-scoped token by mistake.

Then trigger the CI workflow (push a no-op commit touching fly/, or dispatch manually) to confirm Forgejo Actions has the new secret.

5. Revoke the old token

fly tokens list
fly tokens revoke <old-token-id>

Debugging

fly deploy returns "unauthorized"

Token is invalid (expired, revoked, or wrong scope). Repeat the procedure.

Metrics token unavailable: ... context canceled after rotation

The new token was created with deploy scope, not org. Either accept it (cosmetic) or re-mint with fly tokens create org.

Forgejo Actions deploy fails but local works

The Forgejo secret wasn't synced. Re-run mise run provision-indri -- --tags forgejo_actions_secrets and confirm the secret value in Forgejo matches 1Password.