This document provides a complete end-to-end guide for building, testing, and deploying a spec-driven Ruby on Rails 8 application using Docker, Kamal, and Amazon Lightsail, with automated CI/CD via GitHub Actions.
Workflow:
Edit business spec → Commit → GitHub Actions (validate + test) → Deploy via Kamal → App updates
[Placeholder: Complete step-by-step project initialization including Rails 8 setup, initial configuration, database setup, and basic application structure. This section will cover creating the Rails app, configuring essential files, setting up the business spec system, and preparing the project for spec-driven development.]
rails new spec_driven_app --database=sqlite3
cd spec_driven_appbin/devgit init
git add .
git commit -m "Initial commit - spec driven Rails app"git remote add origin git@github.com:apperph/github-actions-test.git
git branch -M main
git push -u origin mainssh -i ~/Downloads/rails-test-key.pem ubuntu@<LIGHTSAIL_IP>- Default user:
ubuntu - Ensure Docker is installed
- Open port 80
bundle add kamalservice: spec_driven_app
image: ghcr.io/apperph/github-actions-test
servers:
web:
- 13.213.250.166
proxy:
ssl: false
host: 13.213.250.166
registry:
server: ghcr.io
username: apperph
password:
- KAMAL_REGISTRY_PASSWORD
builder:
arch: amd64
env:
clear:
PORT: 80
RAILS_ENV: production
secret:
- RAILS_MASTER_KEY
ssh:
user: ubuntu
keys_only: true
config: trueconfig/specs/business.ymlnamespace :specs do
task validate: :environment do
# validations
end
task sync: :environment do
# apply logic
end
end.github/workflows/deploy.yml
- Validate spec
- Run tests
- Deploy via Kamal
mkdir -p ~/.ssh
echo "${{ secrets.LIGHTSAIL_SSH_KEY }}" > ~/.ssh/lightsail_key
chmod 600 ~/.ssh/lightsail_key
ssh-keygen -R "${{ vars.LIGHTSAIL_HOST }}" || true
ssh-keyscan "${{ vars.LIGHTSAIL_HOST }}" >> ~/.ssh/known_hostsImportant:
- Do NOT use
-Hinssh-keyscan - Avoid hashed known_hosts (causes HostKeyMismatch)
git pull --rebase origin maingit add .
git commit -m "Your message"
git push origin maingit pull --rebase origin main
git push origin maingit status
# edit files
git add <file>
git rebase --continueThis section explains how to securely configure credentials required for deployment.
Go to your GitHub repository: Settings → Secrets and variables → Actions
You will see two sections:
- Secrets
- Variables
What this is
Your private SSH key (.pem) used to access the Lightsail instance.
How to get it (from your local machine)
If you already SSH like this:
ssh -i ~/Downloads/rails-test-key.pem ubuntu@13.213.250.166Then open the key:
cat ~/Downloads/rails-test-key.pemWhat to copy
Copy everything, including:
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
Add to GitHub
- Go to Secrets → New repository secret
- Name:
LIGHTSAIL_SSH_KEY - Paste the entire key
- Click Add secret
What this is
Rails uses this to decrypt credentials.
Get it locally
cat config/master.keyAdd to GitHub
- Name:
RAILS_MASTER_KEY - Value = contents of master.key
What this is
Token for pushing/pulling Docker images from GitHub Container Registry.
Create token
- Go to GitHub → Settings → Developer Settings → Personal Access Tokens
- Create new token with scopes:
write:packagesread:packages
Add to GitHub
- Name:
GHCR_TOKEN - Value = your token
What this is
Your Lightsail public IP
Example: 13.213.250.166
Add to GitHub
- Go to Variables → New repository variable
- Name:
LIGHTSAIL_HOST - Value:
13.213.250.166
Whenever editing config/specs/business.yml, you must:
- Validate
- Commit
- Push
bundle exec rake specs:validatebundle exec rails testgit commit -m "Update spec"
git push origin mainGitHub UI:
- Go to Actions
- Select Rails CI/CD Deploy
- Click Run workflow
git commit --allow-empty -m "Trigger deploy"
git push origin main- Go to repository
- Navigate to
config/specs/business.yml - Click Edit
- Modify values
- Scroll down → Commit changes
- Triggers GitHub Actions
- Runs validation + tests
- Deploys automatically
PM edits YAML (GitHub UI or local)
↓
GitHub Actions
- validate
- test
↓
Kamal deploy
↓
Lightsail server
↓
Updated Rails app
- Always
git pull --rebasebefore push - Avoid secrets in repo
- Never re-run old workflows
- Use fresh commits to trigger deploy
- Keep tests aligned with specs
- Avoid hashed SSH known_hosts
- Validate specs before pushing