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 cover it, SSH_KEY for the deploy@ private key and SSH_HOST.
.github/workflows/deploy.yml
Running feather db upgrade against an empty Postgres in CI is the cheapest migration test available. It catches a migration chain that no longer applies before that 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.