Skip to main content
The shape that works: test on a runner with real service containers, then SSH in and run the same deploy/deploy.sh you would run by hand. The server builds its own images, so CI needs no registry and holds no application secrets — two repository secrets total, SSH_KEY (the private key for deploy@) and SSH_HOST.
.github/workflows/deploy.yml
Running feather db upgrade against an empty Postgres in CI is the cheapest migration test there is. It catches a migration chain that no longer applies before the chain reaches production.

Hardening the CI key

A key that can run any command is a key that can read your .env. Lock it to one command with a forced command in the server’s ~deploy/.ssh/authorized_keys:
The workflow’s ssh argument is then ignored and the key cannot open a shell.
Add feather check and feather security-check to the test job. Both exit non-zero on failure, so a convention violation or a weak secret stops the deploy rather than shipping.