## What the runner needs

| Secret | Used for |
|---|---|
| `SSH_PRIVATE_KEY` | Reaching the server. Give it its own key, not your personal one. |
| `KAMAL_REGISTRY_PASSWORD` | Pushing and pulling the image. |
| `RAILS_MASTER_KEY` | Decrypting credentials at boot on the server. |

Those names are read by `.kamal/secrets`, which is the only file that maps environment variables to
what Kamal sends. It is not committed, so whatever CI exports has to match what that file expects.

## The order that matters

Test first, deploy second, in the same workflow with a `needs:` dependency. A separate workflow that
triggers on push deploys in parallel with the test run, which means a broken build can reach
production while its own test job is still red.

```bash
bin/check
bin/kamal deploy
```

`bin/check` is the whole gate: RSpec, RuboCop, Brakeman, bundler-audit, the importmap audit and a
secret scan. If it passes locally it passes in CI, because it is the same script.

## Build on the runner or on the server

Kamal builds the image wherever it runs. On a GitHub-hosted runner that means a cold Docker layer
cache on every run, which is slow. Two ways out: enable a registry cache so layers are reused between
runs, or use a self-hosted runner on the deploy server itself, where the cache is already warm and
the push never leaves the machine.

## Do not deploy every branch

Guard the job on the default branch. A workflow that deploys on any push turns a draft pull request
into a production release:

```yaml
if: github.ref == 'refs/heads/main'
```

## Check it actually shipped

```bash
curl -s https://yourdomain.com/up -o /dev/null -w '%{http_code}\n'
```

A green workflow means the commands exited zero. It does not mean the new container is serving. The
health check in the deploy covers that, but a request from outside confirms DNS and the proxy too.
