Answers · Deploying

How do I deploy a Rails app from GitHub Actions?

Run kamal deploy as a workflow step. Kamal needs three things in the runner: an SSH key that can reach the server, registry credentials, and RAILS_MASTER_KEY. All three go in as repository secrets and are read by .kamal/secrets. Gate the deploy job behind the test job so a red build never ships.

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.

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:

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

Check it actually shipped

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.

Related questions

How do I deploy a Rails 8 app without Heroku?

Use Kamal, which ships with Rails 8. You give it a server over SSH and a container registry in config/deploy.yml, run kamal setup once, and kamal deploy after that. It builds an image, pushes it, and swaps containers with no downtime. A $5 VPS replaces the PaaS.

How do I fix no space left on device on a deploy server?

Old container images are the cause, not your app. Every deploy pushes a new image and the server keeps the old ones. Run docker image prune -a -f to reclaim the space, then check with df -h. On a 25GB VPS this usually returns several gigabytes, and it is worth doing before you are locked out rather than after.

How do I open a Rails console on a production server?

Run kamal app exec --interactive --reuse 'bin/rails console'. That attaches to the running container rather than booting a new one, so you see live data. One Shot ships no admin UI on purpose, so the console is the supported way to inspect and fix records, and everything you touch must go through an Account.