|
| 1 | +# Spec-Driven Rails App Development and Deployment (End-to-End Guide) |
| 2 | + |
| 3 | +## Overview |
| 4 | +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. |
| 5 | + |
| 6 | +Workflow: |
| 7 | +``` |
| 8 | +Edit business spec → Commit → GitHub Actions (validate + test) → Deploy via Kamal → App updates |
| 9 | +``` |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## 1. Project Setup |
| 14 | + |
| 15 | +[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.] |
| 16 | + |
| 17 | +### Create Rails App |
| 18 | + |
| 19 | +```bash |
| 20 | +rails new spec_driven_app --database=sqlite3 |
| 21 | +cd spec_driven_app |
| 22 | +``` |
| 23 | + |
| 24 | +### Run App Locally |
| 25 | + |
| 26 | +```bash |
| 27 | +bin/dev |
| 28 | +``` |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## 2. Version Control (Git + GitHub) |
| 33 | + |
| 34 | +### Initialize Git |
| 35 | + |
| 36 | +```bash |
| 37 | +git init |
| 38 | +git add . |
| 39 | +git commit -m "Initial commit - spec driven Rails app" |
| 40 | +``` |
| 41 | + |
| 42 | +### Connect to GitHub |
| 43 | + |
| 44 | +```bash |
| 45 | +git remote add origin git@github.com:apperph/github-actions-test.git |
| 46 | +git branch -M main |
| 47 | +git push -u origin main |
| 48 | +``` |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +## 3. Deployment Setup (Lightsail) |
| 53 | + |
| 54 | +### SSH into Server |
| 55 | + |
| 56 | +```bash |
| 57 | +ssh -i ~/Downloads/rails-test-key.pem ubuntu@<LIGHTSAIL_IP> |
| 58 | +``` |
| 59 | + |
| 60 | +### Notes |
| 61 | +- Default user: `ubuntu` |
| 62 | +- Ensure Docker is installed |
| 63 | +- Open port 80 |
| 64 | + |
| 65 | +--- |
| 66 | + |
| 67 | +## 4. Kamal Deployment Setup |
| 68 | + |
| 69 | +### Install Kamal |
| 70 | + |
| 71 | +```bash |
| 72 | +bundle add kamal |
| 73 | +``` |
| 74 | + |
| 75 | +### Deploy Config (`config/deploy.yml`) |
| 76 | + |
| 77 | +```yaml |
| 78 | +service: spec_driven_app |
| 79 | +image: ghcr.io/apperph/github-actions-test |
| 80 | +servers: |
| 81 | + web: |
| 82 | + - 13.213.250.166 |
| 83 | +proxy: |
| 84 | + ssl: false |
| 85 | + host: 13.213.250.166 |
| 86 | +registry: |
| 87 | + server: ghcr.io |
| 88 | + username: apperph |
| 89 | + password: |
| 90 | + - KAMAL_REGISTRY_PASSWORD |
| 91 | +builder: |
| 92 | + arch: amd64 |
| 93 | +env: |
| 94 | + clear: |
| 95 | + PORT: 80 |
| 96 | + RAILS_ENV: production |
| 97 | + secret: |
| 98 | + - RAILS_MASTER_KEY |
| 99 | +ssh: |
| 100 | + user: ubuntu |
| 101 | + keys_only: true |
| 102 | + config: true |
| 103 | +``` |
| 104 | +
|
| 105 | +--- |
| 106 | +
|
| 107 | +## 5. Spec-Driven Logic |
| 108 | +
|
| 109 | +### Business Spec File |
| 110 | +
|
| 111 | +```bash |
| 112 | +config/specs/business.yml |
| 113 | +``` |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +## 6. Rake Tasks |
| 118 | + |
| 119 | +### Validate + Sync |
| 120 | + |
| 121 | +```ruby |
| 122 | +namespace :specs do |
| 123 | + task validate: :environment do |
| 124 | + # validations |
| 125 | + end |
| 126 | + task sync: :environment do |
| 127 | + # apply logic |
| 128 | + end |
| 129 | +end |
| 130 | +``` |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## 7. GitHub Actions CI/CD |
| 135 | + |
| 136 | +### Workflow File |
| 137 | +`.github/workflows/deploy.yml` |
| 138 | + |
| 139 | +### Pipeline Flow |
| 140 | +1. Validate spec |
| 141 | +2. Run tests |
| 142 | +3. Deploy via Kamal |
| 143 | + |
| 144 | +### Key Commands Used in Workflow |
| 145 | + |
| 146 | +#### SSH Setup |
| 147 | + |
| 148 | +```bash |
| 149 | +mkdir -p ~/.ssh |
| 150 | +echo "${{ secrets.LIGHTSAIL_SSH_KEY }}" > ~/.ssh/lightsail_key |
| 151 | +chmod 600 ~/.ssh/lightsail_key |
| 152 | +ssh-keygen -R "${{ vars.LIGHTSAIL_HOST }}" || true |
| 153 | +ssh-keyscan "${{ vars.LIGHTSAIL_HOST }}" >> ~/.ssh/known_hosts |
| 154 | +``` |
| 155 | + |
| 156 | +**Important:** |
| 157 | +- Do NOT use `-H` in `ssh-keyscan` |
| 158 | +- Avoid hashed known_hosts (causes HostKeyMismatch) |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## 8. Git Workflow (VERY IMPORTANT) |
| 163 | + |
| 164 | +### Always Sync Before Push |
| 165 | + |
| 166 | +```bash |
| 167 | +git pull --rebase origin main |
| 168 | +``` |
| 169 | + |
| 170 | +### Push Changes |
| 171 | + |
| 172 | +```bash |
| 173 | +git add . |
| 174 | +git commit -m "Your message" |
| 175 | +git push origin main |
| 176 | +``` |
| 177 | + |
| 178 | +### If Push Rejected |
| 179 | + |
| 180 | +```bash |
| 181 | +git pull --rebase origin main |
| 182 | +git push origin main |
| 183 | +``` |
| 184 | + |
| 185 | +### Resolve Conflicts |
| 186 | + |
| 187 | +```bash |
| 188 | +git status |
| 189 | +# edit files |
| 190 | +git add <file> |
| 191 | +git rebase --continue |
| 192 | +``` |
| 193 | + |
| 194 | +--- |
| 195 | + |
| 196 | +## 9. GitHub Secrets & Variables Configuration |
| 197 | + |
| 198 | +This section explains how to securely configure credentials required for deployment. |
| 199 | + |
| 200 | +### Where to Configure |
| 201 | +Go to your GitHub repository: |
| 202 | +Settings → Secrets and variables → Actions |
| 203 | + |
| 204 | +You will see two sections: |
| 205 | +- Secrets |
| 206 | +- Variables |
| 207 | + |
| 208 | +### A. GitHub Secrets (Sensitive Data) |
| 209 | + |
| 210 | +#### 1. LIGHTSAIL_SSH_KEY |
| 211 | +**What this is** |
| 212 | +Your private SSH key (.pem) used to access the Lightsail instance. |
| 213 | + |
| 214 | +**How to get it (from your local machine)** |
| 215 | +If you already SSH like this: |
| 216 | + |
| 217 | +```bash |
| 218 | +ssh -i ~/Downloads/rails-test-key.pem ubuntu@13.213.250.166 |
| 219 | +``` |
| 220 | + |
| 221 | +Then open the key: |
| 222 | + |
| 223 | +```bash |
| 224 | +cat ~/Downloads/rails-test-key.pem |
| 225 | +``` |
| 226 | + |
| 227 | +**What to copy** |
| 228 | +Copy everything, including: |
| 229 | + |
| 230 | +``` |
| 231 | +-----BEGIN RSA PRIVATE KEY----- |
| 232 | +... |
| 233 | +-----END RSA PRIVATE KEY----- |
| 234 | +``` |
| 235 | + |
| 236 | +**Add to GitHub** |
| 237 | +1. Go to Secrets → New repository secret |
| 238 | +2. Name: `LIGHTSAIL_SSH_KEY` |
| 239 | +3. Paste the entire key |
| 240 | +4. Click Add secret |
| 241 | + |
| 242 | +#### 2. RAILS_MASTER_KEY |
| 243 | +**What this is** |
| 244 | +Rails uses this to decrypt credentials. |
| 245 | + |
| 246 | +**Get it locally** |
| 247 | + |
| 248 | +```bash |
| 249 | +cat config/master.key |
| 250 | +``` |
| 251 | + |
| 252 | +**Add to GitHub** |
| 253 | +- Name: `RAILS_MASTER_KEY` |
| 254 | +- Value = contents of master.key |
| 255 | + |
| 256 | +#### 3. GHCR_TOKEN |
| 257 | +**What this is** |
| 258 | +Token for pushing/pulling Docker images from GitHub Container Registry. |
| 259 | + |
| 260 | +**Create token** |
| 261 | +1. Go to GitHub → Settings → Developer Settings → Personal Access Tokens |
| 262 | +2. Create new token with scopes: |
| 263 | + - `write:packages` |
| 264 | + - `read:packages` |
| 265 | + |
| 266 | +**Add to GitHub** |
| 267 | +- Name: `GHCR_TOKEN` |
| 268 | +- Value = your token |
| 269 | + |
| 270 | +### B. GitHub Variables (Non-sensitive) |
| 271 | + |
| 272 | +#### 1. LIGHTSAIL_HOST |
| 273 | +**What this is** |
| 274 | +Your Lightsail public IP |
| 275 | + |
| 276 | +Example: `13.213.250.166` |
| 277 | + |
| 278 | +**Add to GitHub** |
| 279 | +1. Go to Variables → New repository variable |
| 280 | +2. Name: `LIGHTSAIL_HOST` |
| 281 | +3. Value: `13.213.250.166` |
| 282 | + |
| 283 | +--- |
| 284 | + |
| 285 | +## 10. Important Notes |
| 286 | + |
| 287 | +### Spec Updates |
| 288 | +Whenever editing `config/specs/business.yml`, you must: |
| 289 | +1. Validate |
| 290 | +2. Commit |
| 291 | +3. Push |
| 292 | + |
| 293 | +### Validate Locally |
| 294 | + |
| 295 | +```bash |
| 296 | +bundle exec rake specs:validate |
| 297 | +``` |
| 298 | + |
| 299 | +### Run Tests |
| 300 | + |
| 301 | +```bash |
| 302 | +bundle exec rails test |
| 303 | +``` |
| 304 | + |
| 305 | +### Deploy Trigger |
| 306 | + |
| 307 | +#### Automatic (preferred) |
| 308 | + |
| 309 | +```bash |
| 310 | +git commit -m "Update spec" |
| 311 | +git push origin main |
| 312 | +``` |
| 313 | + |
| 314 | +#### Manual Trigger |
| 315 | +GitHub UI: |
| 316 | +1. Go to **Actions** |
| 317 | +2. Select **Rails CI/CD Deploy** |
| 318 | +3. Click **Run workflow** |
| 319 | + |
| 320 | +#### Force Trigger |
| 321 | + |
| 322 | +```bash |
| 323 | +git commit --allow-empty -m "Trigger deploy" |
| 324 | +git push origin main |
| 325 | +``` |
| 326 | + |
| 327 | +--- |
| 328 | + |
| 329 | +## 11. Manual Updates in GitHub (No Terminal) |
| 330 | + |
| 331 | +### Editing Business Spec |
| 332 | +1. Go to repository |
| 333 | +2. Navigate to `config/specs/business.yml` |
| 334 | +3. Click **Edit** |
| 335 | +4. Modify values |
| 336 | +5. Scroll down → Commit changes |
| 337 | + |
| 338 | +### Result |
| 339 | +- Triggers GitHub Actions |
| 340 | +- Runs validation + tests |
| 341 | +- Deploys automatically |
| 342 | + |
| 343 | +--- |
| 344 | + |
| 345 | +## 12. Final Architecture |
| 346 | + |
| 347 | +``` |
| 348 | +PM edits YAML (GitHub UI or local) |
| 349 | + ↓ |
| 350 | +GitHub Actions |
| 351 | + - validate |
| 352 | + - test |
| 353 | + ↓ |
| 354 | +Kamal deploy |
| 355 | + ↓ |
| 356 | +Lightsail server |
| 357 | + ↓ |
| 358 | +Updated Rails app |
| 359 | +``` |
| 360 | + |
| 361 | +--- |
| 362 | + |
| 363 | +## 13. Best Practices / Lessons Learned |
| 364 | +- Always `git pull --rebase` before push |
| 365 | +- Avoid secrets in repo |
| 366 | +- Never re-run old workflows |
| 367 | +- Use fresh commits to trigger deploy |
| 368 | +- Keep tests aligned with specs |
| 369 | +- Avoid hashed SSH known_hosts |
| 370 | +- Validate specs before pushing |
| 371 | + |
0 commit comments