## The order

1. **Create** the new key in the provider's dashboard. The old one keeps working.
2. **Set** it, in `.kamal/secrets` or your credentials file.
3. **Deploy.** Both keys are now valid; only one is in use.
4. **Verify** the app is actually using the new one.
5. **Revoke** the old key.

```bash
bin/kamal secrets print
bin/kamal deploy
```

Step four is the one people skip, and it is why step five occasionally causes an incident: if the
deploy did not pick up the new value, revoking removes the key that was actually working.

## Verifying without printing the secret

```bash
bin/kamal app exec 'bin/rails runner "puts ENV[%q(STRIPE_SECRET_KEY)].to_s.last(4)"'
```

Last four characters is enough to tell two keys apart and is not enough to be worth protecting. Never
print a whole key into a terminal, a CI log or a chat window, all three of which keep history.

## Keys that cannot be rotated this way

**`RAILS_MASTER_KEY`.** It is not a provider credential; it is the only thing that decrypts
`config/credentials.yml.enc`. Rotating it means re-encrypting the file, which means generating a new
key and a new file together and deploying both. There is no window where both are valid.

**Webhook signing secrets.** Rotating one means Stripe signs with the new secret immediately, so
there is a brief window where in-flight deliveries fail. They retry for three days, so the practical
answer is to rotate and let the retries backfill rather than trying to avoid the gap.

## Rotate on a trigger, not a calendar

The useful triggers: someone with access leaves, a key appeared somewhere it should not have, or the
provider says so. Rotating on a schedule for its own sake mostly generates opportunities to get the
order wrong.

## Prevent the leak that causes the emergency

```bash
bin/check
```

That runs a secret scan over the tree, so a key pasted into a file fails the build rather than
reaching the repository history, where rotation becomes the only remedy.
