The AtmosphereConf talks your skyline missed
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(spec): add staging environment to railway deploy spec

- §4: two Railway environments — staging (from staging branch) and production
(from main). Feature branches → staging → validate → merge to main.
- §4.3: separate env vars per environment (different APP_URL values).
- §4.5: staging branch creation instructions.
- §7: verification plan split into staging-first then production.
- §11: acceptance criteria split into code, infrastructure, staging, production.

+83 -29
+83 -29
docs/superpowers/specs/2026-04-10-railway-deploy.md
··· 205 205 206 206 ## 4. Railway Infrastructure 207 207 208 - ### 4.1 Create project and service 208 + ### 4.1 Environments: staging + production 209 + 210 + The project uses two Railway environments with a promotion workflow: 211 + 212 + | Environment | Branch | Domain | Purpose | 213 + |---|---|---|---| 214 + | **Staging** | `staging` | Auto-generated `*.up.railway.app` | Validate changes before production. Push here first. | 215 + | **Production** | `main` | `understory.watch` (custom domain) | Public-facing. Code arrives via `staging` → `main` merge. | 216 + 217 + **Workflow:** Push to `staging` branch → Railway auto-deploys staging → validate on the Railway domain → merge `staging` → `main` → Railway auto-deploys production. 218 + 219 + This means you can make tweaks, test them on the staging URL, and only promote to production when confident. The two environments run the same code at different points in time, differentiated only by their `APP_URL` and domain. 220 + 221 + ### 4.2 Create project and services 209 222 210 223 Using Railway MCP tools or CLI: 211 224 212 225 1. Create a new Railway project named "understory" 213 - 2. Create a service linked to the GitHub repo `musicjunkieg/understory` on the `main` branch 214 - 3. Railway auto-detects Next.js and uses its Node.js builder (Nixpacks) 215 - 4. Auto-deploy on push to `main` is enabled by default 226 + 2. Link to the GitHub repo `musicjunkieg/understory` 227 + 3. Create two environments: 228 + - **Production** environment: deploys from `main` branch 229 + - **Staging** environment: deploys from `staging` branch 230 + 4. Railway auto-detects Next.js and uses its Node.js builder (Nixpacks) for both 231 + 5. Each environment gets its own service instance, env vars, and domain 216 232 217 - ### 4.2 Environment variables 233 + ### 4.3 Environment variables 218 234 219 - Set in Railway's service environment: 235 + **Staging environment:** 220 236 221 237 | Variable | Value | Notes | 222 238 |---|---|---| 223 - | `APP_URL` | `https://understory.watch` | Required. Initially use the Railway-generated `*.up.railway.app` URL for smoke testing, then switch to the custom domain. | 224 - | `HOSTNAME` | `0.0.0.0` | Required. The standalone server defaults to `localhost`, which won't accept connections on Railway. `0.0.0.0` binds on all interfaces. | 239 + | `APP_URL` | `https://<staging>.up.railway.app` | The auto-generated Railway domain for staging. Set after Railway creates it. | 240 + | `HOSTNAME` | `0.0.0.0` | Required. Standalone server defaults to `localhost`; `0.0.0.0` binds on all interfaces. | 225 241 226 - `NODE_ENV=production` is set automatically by Railway. `PORT` is set automatically by Railway (the standalone server reads it). `ASSEMBLYAI_API_KEY` is not needed at runtime (only for offline build scripts). 242 + **Production environment:** 227 243 228 - ### 4.3 Domain configuration 244 + | Variable | Value | Notes | 245 + |---|---|---| 246 + | `APP_URL` | `https://understory.watch` | The custom domain. | 247 + | `HOSTNAME` | `0.0.0.0` | Same as staging. | 229 248 230 - **Phase 1 — Railway domain (smoke test):** 231 - Railway auto-generates a `*.up.railway.app` domain. Use this for initial verification. Temporarily set `APP_URL` to this domain. 249 + `NODE_ENV=production` is set automatically by Railway in both environments. `PORT` is set automatically. `ASSEMBLYAI_API_KEY` is not needed at runtime (only for offline build scripts). 232 250 233 - **Phase 2 — Custom domain:** 234 - 1. Add `understory.watch` as a custom domain in Railway 251 + ### 4.4 Domain configuration 252 + 253 + **Staging:** Uses the auto-generated `*.up.railway.app` domain. No custom domain needed — it's for validation only. 254 + 255 + **Production:** 256 + 1. Add `understory.watch` as a custom domain in the production environment 235 257 2. Railway provides a CNAME target (e.g., `<hash>.railway.app`) 236 258 3. At your registrar, create a CNAME record: `understory.watch` → `<railway-cname-target>` 237 259 4. Railway auto-provisions an SSL certificate via Let's Encrypt 238 - 5. Update `APP_URL` to `https://understory.watch` 239 - 6. DNS propagation typically takes 5–30 minutes 260 + 5. DNS propagation typically takes 5–30 minutes 261 + 262 + ### 4.5 Branch setup 263 + 264 + Create the `staging` branch from `main` and push it: 265 + 266 + ```bash 267 + git checkout main 268 + git checkout -b staging 269 + git push -u origin staging 270 + ``` 271 + 272 + Going forward: feature branches merge into `staging` for testing, then `staging` merges into `main` for production release. 240 273 241 274 --- 242 275 ··· 285 318 286 319 ### 7.1 Pre-deploy (local) 287 320 288 - Before pushing: 321 + Before pushing to `staging`: 289 322 - [ ] `npm run build` succeeds with `output: "standalone"` — the `.next/standalone/` directory is created 323 + - [ ] The `postbuild` script ran: `.next/standalone/data/`, `.next/standalone/.next/static/`, and `.next/standalone/public/` all exist 290 324 - [ ] `npm test` — all 40 scoring tests pass (unrelated to deploy, but confirms nothing broke) 291 325 - [ ] `npx tsc --noEmit` — clean 292 326 - [ ] `npx eslint src/` — clean 293 327 - [ ] `GET /oauth/client-metadata.json` on `localhost:3000` returns valid metadata JSON with `client_id` matching `http://127.0.0.1:3000/oauth/client-metadata.json` 294 328 - [ ] OAuth login flow works locally with the self-hosted metadata (no cimd-service) 295 329 296 - ### 7.2 Post-deploy (Railway domain) 330 + ### 7.2 Post-deploy — staging 297 331 298 - Using the auto-generated `*.up.railway.app` URL: 299 - - [ ] Landing page loads (confirms build + serve work) 332 + Push code to `staging` branch → Railway auto-deploys the staging environment. Using the auto-generated `*.up.railway.app` URL: 333 + 334 + - [ ] Landing page loads with correct styles (confirms standalone build + static assets served) 300 335 - [ ] `/talks` shows 115+ talk grid (confirms `data/talks.json` was bundled) 301 336 - [ ] `/talk/<any-rkey>` shows transcript + HLS video player (confirms transcript JSON files were bundled) 302 - - [ ] `/oauth/client-metadata.json` returns valid metadata with correct `client_id` URL 337 + - [ ] `/oauth/client-metadata.json` returns valid metadata with `client_id` matching the staging Railway URL 303 338 - [ ] OAuth login flow completes — avatar appears in Nav 304 339 - [ ] `/api/crawl` returns `TalkMentions` data (authenticated) 305 340 - [ ] Second `/api/crawl` call returns `cached: true` (confirms in-memory cache works) 306 341 - [ ] Build logs show no errors or unexpected warnings 307 342 308 - ### 7.3 Post-DNS (custom domain) 343 + ### 7.3 Promote to production 344 + 345 + After staging passes, merge `staging` → `main`. Railway auto-deploys the production environment. 309 346 310 - After pointing `understory.watch` to Railway: 347 + ### 7.4 Post-DNS — production (custom domain) 348 + 349 + After pointing `understory.watch` to Railway's production CNAME and DNS propagates: 311 350 - [ ] `https://understory.watch` loads with valid SSL certificate 351 + - [ ] Page loads with correct styles (CSS/JS bundles served) 352 + - [ ] `/talks` and `/talk/<any-rkey>` work (data bundled) 312 353 - [ ] `/oauth/client-metadata.json` shows `client_id` = `https://understory.watch/oauth/client-metadata.json` 313 354 - [ ] Full OAuth flow works on the production domain 314 355 - [ ] `/api/crawl` works on the production domain ··· 353 394 354 395 ## 11. Acceptance Criteria 355 396 356 - - [ ] Railway project "understory" exists with a service linked to `musicjunkieg/understory` 357 - - [ ] `APP_URL` and `HOSTNAME=0.0.0.0` environment variables set in Railway 397 + ### Code 358 398 - [ ] `next.config.ts` has `output: "standalone"` and `outputFileTracingIncludes` for `data/` 359 399 - [ ] `package.json` has `postbuild` script copying `.next/static`, `public`, `data` into standalone directory 360 400 - [ ] `package.json` `start` script is `node .next/standalone/server.js` ··· 362 402 - [ ] `/oauth/client-metadata.json` route exists and returns valid metadata 363 403 - [ ] `src/lib/auth/client.ts` uses `buildClientMetadata(appUrl)` (no `OAUTH_CLIENT_ID` env var) 364 404 - [ ] `npm run build` produces a working standalone output with `data/`, `.next/static/`, and `public/` present in `.next/standalone/` 365 - - [ ] Railway deploy succeeds automatically on push; build logs are clean 366 - - [ ] Landing page, `/talks`, `/talk/[rkey]`, and OAuth flow all work on the Railway domain 367 - - [ ] `/api/crawl` returns valid crawl data when authenticated 405 + - [ ] `npx tsc --noEmit` clean, `npx eslint src/` clean, `npm test` passes 406 + 407 + ### Railway infrastructure 408 + - [ ] Railway project "understory" exists with a service linked to `musicjunkieg/understory` 409 + - [ ] Two environments configured: staging (deploys from `staging` branch) and production (deploys from `main` branch) 410 + - [ ] `APP_URL` and `HOSTNAME=0.0.0.0` set in both environments (different `APP_URL` values) 411 + - [ ] `staging` branch exists and is pushed to origin 412 + 413 + ### Staging validation 414 + - [ ] Staging auto-deploys on push to `staging`; build logs clean 415 + - [ ] Landing page, `/talks`, `/talk/[rkey]` all work on the staging Railway domain 416 + - [ ] OAuth flow completes on staging 417 + - [ ] `/api/crawl` returns valid crawl data when authenticated on staging 418 + 419 + ### Production validation 420 + - [ ] Production auto-deploys on merge to `main`; build logs clean 368 421 - [ ] Custom domain `understory.watch` configured with valid SSL 422 + - [ ] Landing page, `/talks`, `/talk/[rkey]` all work on `https://understory.watch` 369 423 - [ ] Full OAuth flow works on `https://understory.watch` 370 - - [ ] `npx tsc --noEmit` clean, `npx eslint src/` clean, `npm test` passes 424 + - [ ] `/api/crawl` returns valid crawl data when authenticated on production