feat: Railway migration, Shorebird OTA, CI/CD overhaul & infrastructure docs#111
feat: Railway migration, Shorebird OTA, CI/CD overhaul & infrastructure docs#111teetangh wants to merge 5 commits into
Conversation
…re docs - Fix backend DotEnv loading for Docker/Railway with includePlatformEnvironment - Add Dockerfile, railway.toml, .dockerignore, and health check endpoint - Add backend-deploy.yml workflow for auto-deploy to Railway on push to prod - Add complete Railway env vars list (21 vars) to backend/.env.example - Integrate shorebird_code_push for silent OTA patches - Add shorebird-release and shorebird-patch jobs to flutter-ci.yml - Add workflow_dispatch trigger, fix deprecated flutter pub run commands - Update branches from main/develop to prod/dev - Create CLAUDE.md with infrastructure, OTA, and branch strategy docs - Rename deployment docs to numbered kebab-case convention Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces infrastructure for hosting the Dart Frog backend on Railway and implementing Over-The-Air (OTA) updates using Shorebird. Key changes include a multi-stage Dockerfile, Railway configuration, a health check endpoint, and comprehensive documentation for the new system architecture. Feedback focuses on improving the backend container's reliability by switching to a distroless base image to support SSL and glibc, ensuring app resilience by wrapping Shorebird update checks in error handling, and correcting documentation regarding environment variable precedence.
| RUN dart compile exe build/bin/server.dart -o build/bin/server | ||
|
|
||
| # ── Stage 2: Runtime ──────────────────────────────────────── | ||
| FROM scratch |
There was a problem hiding this comment.
The scratch base image is extremely minimal and lacks essential components like root SSL certificates and standard C libraries (glibc). This will cause two critical issues:
- HTTPS failures: Outgoing requests to services like Sentry, GitHub, or Resend will fail with TLS/SSL handshake errors.
- Runtime errors: The AOT-compiled Dart executable is dynamically linked to
glibc, which is absent inscratch, causing the container to fail at startup.
Using a distroless image is a best practice that solves both problems by providing a minimal, secure environment with these necessary components.
FROM gcr.io/distroless/cc-debian12
| final shorebirdCodePush = ShorebirdCodePush(); | ||
| final isUpdateAvailable = | ||
| await shorebirdCodePush.isNewPatchAvailableForDownload(); | ||
| if (isUpdateAvailable) { | ||
| await shorebirdCodePush.downloadUpdateIfAvailable(); | ||
| } |
There was a problem hiding this comment.
The Shorebird update check involves network requests and could throw an exception (e.g., due to network issues or server errors). An unhandled exception here would prevent runApp from being called, causing the app to fail on startup. To make the app more resilient, this logic should be wrapped in a try-catch block to handle potential failures gracefully.
| final shorebirdCodePush = ShorebirdCodePush(); | |
| final isUpdateAvailable = | |
| await shorebirdCodePush.isNewPatchAvailableForDownload(); | |
| if (isUpdateAvailable) { | |
| await shorebirdCodePush.downloadUpdateIfAvailable(); | |
| } | |
| try { | |
| final shorebirdCodePush = ShorebirdCodePush(); | |
| final isUpdateAvailable = | |
| await shorebirdCodePush.isNewPatchAvailableForDownload(); | |
| if (isUpdateAvailable) { | |
| await shorebirdCodePush.downloadUpdateIfAvailable(); | |
| } | |
| } catch (e, s) { | |
| // Silently log the error and continue app startup. | |
| Sentry.captureException(e, stackTrace: s); | |
| } |
| // Load environment variables. | ||
| // includePlatformEnvironment: true merges Platform.environment into the map, | ||
| // so env vars injected by Docker/Railway are accessible via env['KEY']. | ||
| // The .env file (if present) overrides platform env vars. |
There was a problem hiding this comment.
This comment incorrectly states that the .env file overrides platform environment variables. The dotenv package's load() method does not overwrite existing variables. Since includePlatformEnvironment: true loads platform variables first, they will take precedence. The comment should be corrected to reflect this behavior, which is standard and desirable for containerized environments where platform variables are the source of truth.
| // The .env file (if present) overrides platform env vars. | |
| // The .env file provides local defaults, but platform variables (from Docker/Railway) will override them. |
…nk, runtime - Switch base image from dart:stable to ghcr.io/cirruslabs/flutter:stable (prisma_flutter_connector requires Flutter SDK for dependency resolution) - Add Prisma client generation step (lib/generated/ is gitignored) - Add build_runner step for freezed/json codegen on generated models - Replace prisma/schema.prisma symlink with real file copy (Docker cannot follow symlinks outside build context) - Switch runtime from scratch to debian:bookworm-slim (Flutter image lacks /runtime/ that dart:stable provides) - Add detailed build notes in Dockerfile documenting all failure modes - Rename service references from familiarise-api to familiarise-mobile-api - Update migration checklist with completed items Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Shorebird initialized with app_id: f9b217a0-1007-48a5-bd41-d381568e23f1 - shorebird.yaml added to Flutter assets in pubspec.yaml (by shorebird init) - Track ios/Podfile.lock in git (was gitignored — locks CocoaPods versions for reproducible builds, same rationale as pubspec.lock) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Marked Railway project as created and connected to familiarise-mobile-api. - Confirmed all environment variables are set in Railway dashboard. - Verified database URLs and backend deployment status. - Updated health check and auth endpoint testing details. - Completed Shorebird initialization and committed shorebird.yaml with real app_id. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Pull request overview
This PR migrates backend hosting from Globe.dev to Railway, adds Shorebird OTA support for Flutter, and updates CI/CD + documentation to align with the new infrastructure and branch strategy ahead of Globe.dev shutdown.
Changes:
- Add Railway deployment support (Dockerfile build, Railway config,
/api/healthendpoint, backend env loading adjustments). - Integrate Shorebird OTA (dependency + startup patch check) and extend GitHub Actions workflows for backend tests/deploy and Shorebird release/patch.
- Add/refresh deployment documentation and checklists.
Reviewed changes
Copilot reviewed 20 out of 25 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
shorebird.yaml |
Adds Shorebird app configuration (app_id, auto-update notes). |
pubspec.yaml |
Adds Shorebird dependency and includes shorebird.yaml in assets. |
pubspec.lock |
Locks Shorebird dependency version. |
lib/main.dart |
Adds Shorebird patch availability check during startup. |
ios/Podfile.lock |
Introduces tracked CocoaPods lockfile for reproducible iOS builds. |
docs/deployment/03-migration-checklist.md |
Adds a migration verification checklist across phases. |
docs/deployment/02-migration-and-cicd-plan.md |
Updates plan references (service name/URLs) for Railway migration. |
docs/deployment/01-deployment-strategy.md |
Adds a deployment strategy guide (backend + mobile CI/CD options). |
docs/README.md |
Updates documentation index to new deployment docs. |
backend/routes/api/health.dart |
Implements Railway health check endpoint. |
backend/railway.toml |
Adds Railway build/deploy + healthcheck configuration. |
backend/prisma/schema.prisma |
Replaces prior schema symlink with a full schema copy for Docker/Railway builds. |
backend/main.dart |
Updates dotenv loading to support platform env vars (Docker/Railway) with optional .env. |
backend/README.md |
Rewrites backend docs (local dev, env vars, routes, Railway/Docker deployment). |
backend/Dockerfile |
Adds multi-stage Docker build for Dart Frog + Prisma + build_runner + AOT compilation. |
backend/.env.example |
Expands backend env var examples (Stream/Supabase/Upstash/Resend/etc). |
backend/.dockerignore |
Reduces Docker build context (excluding env, build outputs, etc.). |
README.md |
Updates repo overview, backend setup, and CI/CD documentation for Railway + Shorebird. |
CLAUDE.md |
Adds infrastructure/CI conventions and gotchas for contributors/agents. |
.mcp.json.example |
Adds Railway MCP server configuration. |
.gitignore |
Stops ignoring ios/Podfile.lock to track it in git. |
.github/workflows/flutter-ci.yml |
Updates triggers, replaces deprecated commands, adds backend test + Shorebird jobs. |
.github/workflows/backend-deploy.yml |
Adds Railway deploy workflow with tests + post-deploy health check. |
.env.example |
Updates API base URL guidance for local vs Railway production. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| | GET | `/api/health` | Health check (used by Railway) | | ||
| | POST | `/api/auth/sign-in` | Sign in | | ||
| | POST | `/api/auth/sign-up` | Register | | ||
| | DELETE | `/api/auth/sign-out` | Sign out | | ||
| | GET | `/api/auth/session` | Get current session | |
There was a problem hiding this comment.
The API routes table is inaccurate for auth and Stream endpoints. For example, the codebase uses POST /api/auth/email/sign-in and POST /api/auth/email/sign-up (not /api/auth/sign-in or /api/auth/sign-up), POST /api/auth/sign-out (not DELETE), and POST /api/stream/token (not GET). Please update the table to match the actual Dart Frog routes so the README doesn’t mislead deployers/clients.
| - assets/images/ | ||
| - assets/icons/ | ||
| - assets/fonts/ | ||
| - shorebird.yaml |
There was a problem hiding this comment.
shorebird.yaml is being bundled as a Flutter asset, but there’s no runtime code in the repo that reads it (it’s typically a Shorebird CLI config file). Unless the app needs to load this file at runtime, consider removing it from flutter/assets to avoid shipping config-only metadata in the app bundle.
| - shorebird.yaml |
| # with a real file copy. If the web schema changes, re-copy it: | ||
| # cp ~/Desktop/familiarise_web/prisma/schema.prisma backend/prisma/schema.prisma |
There was a problem hiding this comment.
This Dockerfile comment includes a developer-specific local path (cp ~/Desktop/...) for re-copying the Prisma schema. Since this repo will be used by multiple developers/CI, it’d be better to phrase this generically (e.g., “copy from the web repo path”) or point to an internal script that performs the sync, rather than referencing a specific machine path.
| # with a real file copy. If the web schema changes, re-copy it: | |
| # cp ~/Desktop/familiarise_web/prisma/schema.prisma backend/prisma/schema.prisma | |
| # with a real file copy. If the web schema changes, re-copy the updated | |
| # familiarise_web/prisma/schema.prisma into backend/prisma/schema.prisma. |
|
|
||
| # Copy environment file | ||
| # Generate Prisma client | ||
| dart run orm generate |
There was a problem hiding this comment.
dart run orm generate won’t work in backend/ as-is: backend/pubspec.yaml doesn’t declare a dependency named orm and there’s no local package providing that executable. The README should use the actual Prisma generation command used by this repo (e.g., dart run prisma_flutter_connector:generate ... or whatever your canonical script is) so new developers can follow it successfully.
| dart run orm generate | |
| dart run prisma_flutter_connector:generate |
| name: Shorebird Release | ||
| needs: [deploy-android, deploy-ios] | ||
| runs-on: ubuntu-latest | ||
| if: startsWith(github.ref, 'refs/tags/') |
There was a problem hiding this comment.
shorebird-release is configured to run on tag pushes (if: startsWith(github.ref, 'refs/tags/')) but it needs: [deploy-android, deploy-ios]. Those deploy jobs only run when github.event_name == 'release', so on a tag push they will be skipped and this job won’t run either. To make Shorebird releases actually happen on tags, adjust the dependencies/conditions (e.g., depend on the build jobs for tag pushes, or run Shorebird release on release events instead of tag pushes).
| if: startsWith(github.ref, 'refs/tags/') | |
| if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/') |
| inputs: | ||
| deploy_type: | ||
| description: 'Deployment type' | ||
| required: false | ||
| default: 'patch' | ||
| type: choice | ||
| options: | ||
| - patch | ||
| - release |
There was a problem hiding this comment.
workflow_dispatch defines an input deploy_type, but it isn’t referenced anywhere in job if: conditions or steps. As written, manually dispatching the workflow will always run shorebird-patch (and never shorebird-release unless also pushing a tag), regardless of the selected option. Either wire inputs.deploy_type into the job conditions, or remove the unused input to avoid confusion.
| inputs: | |
| deploy_type: | |
| description: 'Deployment type' | |
| required: false | |
| default: 'patch' | |
| type: choice | |
| options: | |
| - patch | |
| - release |
| curl --fail --retry 5 --retry-delay 10 \ | ||
| "${{ secrets.PRODUCTION_API_BASE_URL }}/api/health" || \ | ||
| echo "::warning::Health check failed - check Railway dashboard" |
There was a problem hiding this comment.
The post-deploy health check can’t fail the workflow because it’s followed by || echo "::warning::...". That means a bad deploy still reports success, which undermines the purpose of a health check gate. If you want this to block approval/deploy, let curl --fail ... exit non-zero (or explicitly exit 1 on failure) so the job fails when the service is unhealthy.
| curl --fail --retry 5 --retry-delay 10 \ | |
| "${{ secrets.PRODUCTION_API_BASE_URL }}/api/health" || \ | |
| echo "::warning::Health check failed - check Railway dashboard" | |
| if ! curl --fail --retry 5 --retry-delay 10 "${{ secrets.PRODUCTION_API_BASE_URL }}/api/health"; then | |
| echo "::warning::Health check failed - check Railway dashboard" | |
| exit 1 | |
| fi |
| // Check for Shorebird OTA patches (downloads in background, | ||
| // applies silently on next app launch) | ||
| final shorebirdCodePush = ShorebirdCodePush(); | ||
| final isUpdateAvailable = | ||
| await shorebirdCodePush.isNewPatchAvailableForDownload(); | ||
| if (isUpdateAvailable) { | ||
| await shorebirdCodePush.downloadUpdateIfAvailable(); | ||
| } |
There was a problem hiding this comment.
The Shorebird update check is awaited during app startup (before runApp). Even though the comment says “downloads in background”, isNewPatchAvailableForDownload() and downloadUpdateIfAvailable() will block initialization and can noticeably slow cold start when network is slow. Consider moving this after runApp (or running it unawaited / with a short timeout) so app launch isn’t gated on the OTA check.
|
|
||
| # Start the development server | ||
| dart_frog dev | ||
| dart run orm generate # Generate Prisma client |
There was a problem hiding this comment.
The backend setup instructions reference dart run orm generate, but backend/pubspec.yaml doesn’t include an orm package/executable. This command will fail for anyone following the README. Please update this to the repo’s actual Prisma generation command (or a script you expect contributors to run) to keep the setup steps accurate.
| dart run orm generate # Generate Prisma client | |
| npx prisma generate # Generate Prisma client |
There was a problem hiding this comment.
Code Review
This pull request migrates the backend to Railway and integrates Shorebird for over-the-air (OTA) updates. Key changes include the addition of a production Dockerfile for the Dart Frog API, the implementation of a health check endpoint, and extensive updates to the project documentation and CI/CD workflows. A redundancy was identified in the Shorebird configuration where the auto_update property should be explicitly set to false to prevent conflict with the manual update logic implemented in the Flutter application.
| # If auto_update: false, you will need to use package:shorebird_code_push to trigger updates. | ||
| # https://pub.dev/packages/shorebird_code_push | ||
| # Uncomment the following line to disable automatic updates. | ||
| # auto_update: false |
There was a problem hiding this comment.
The auto_update property is currently commented out, meaning it defaults to true. When enabled, Shorebird automatically checks for and downloads patches at launch, which makes the manual update logic you've added in lib/main.dart redundant.
Since your code in main.dart already handles checking for updates, you should uncomment this line to explicitly set auto_update: false. This will prevent redundant checks and ensure the update behavior is solely controlled by your code, as intended.
auto_update: falseThere was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 25 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
docs/deployment/02-migration-and-cicd-plan.md:1035
- In this CLAUDE.md snippet, auto-deploy is described as triggering on pushes to
main, but the branch strategy in this PR isprodfor production deploys. Update the snippet to referenceprodso the generated docs match the actual workflow triggers.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| shorebird-release: | ||
| name: Shorebird Release | ||
| needs: [deploy-android, deploy-ios] | ||
| runs-on: ubuntu-latest | ||
| if: startsWith(github.ref, 'refs/tags/') | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Flutter | ||
| uses: subosito/flutter-action@v2 | ||
| with: | ||
| flutter-version: ${{ env.FLUTTER_VERSION }} | ||
| channel: stable | ||
| cache: true | ||
|
|
||
| - name: Install dependencies | ||
| run: flutter pub get | ||
|
|
||
| - name: Generate code | ||
| run: dart run build_runner build --delete-conflicting-outputs | ||
|
|
||
| - name: Install Shorebird CLI | ||
| uses: shorebirdtech/setup-shorebird@v1 | ||
|
|
||
| - name: Create Shorebird release (Android) | ||
| run: shorebird release android --flutter-version ${{ env.FLUTTER_VERSION }} | ||
| env: | ||
| SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }} | ||
|
|
||
| - name: Create Shorebird release (iOS) | ||
| run: shorebird release ios --flutter-version ${{ env.FLUTTER_VERSION }} | ||
| env: |
There was a problem hiding this comment.
shorebird release ios requires a macOS runner (Xcode toolchain). Running the Shorebird release job on ubuntu-latest will fail for iOS; split Android/iOS into separate jobs (ubuntu for android, macos for ios) or use a matrix with per-platform runs-on.
| shorebird-patch: | ||
| name: Shorebird Hotfix Patch | ||
| runs-on: ubuntu-latest | ||
| if: | | ||
| github.event_name == 'workflow_dispatch' || | ||
| startsWith(github.ref, 'refs/heads/hotfix/') | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Flutter | ||
| uses: subosito/flutter-action@v2 | ||
| with: | ||
| flutter-version: ${{ env.FLUTTER_VERSION }} | ||
| channel: stable | ||
| cache: true | ||
|
|
||
| - name: Install dependencies | ||
| run: flutter pub get | ||
|
|
||
| - name: Generate code | ||
| run: dart run build_runner build --delete-conflicting-outputs | ||
|
|
||
| - name: Install Shorebird CLI | ||
| uses: shorebirdtech/setup-shorebird@v1 | ||
|
|
||
| - name: Apply patch (Android) | ||
| run: shorebird patch android | ||
| env: | ||
| SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }} | ||
|
|
||
| - name: Apply patch (iOS) | ||
| run: shorebird patch ios | ||
| env: |
There was a problem hiding this comment.
This job runs on ubuntu-latest but executes shorebird patch ios, which typically requires macOS/Xcode. To avoid guaranteed failures, run the iOS patch step on a macOS runner (separate job or matrix) and keep Android patching on Linux.
Summary
Infrastructure migration and CI/CD overhaul across 4 phases, preparing for Globe.dev shutdown (April 3, 2026).
21 files changed — 572 insertions, 116 deletions.
Phase 1 — Railway Migration (backend hosting)
backend/main.dart— FixedDotEnvloading for Docker/Railway usingincludePlatformEnvironment: true. The backend now works both locally (.envfile) and in containers (platform env vars). Zero route changes needed.backend/Dockerfile— Correct multi-stage build:dart_frog build→dart compile exe. Fixes the plan doc's nonexistentdart build clicommand.backend/.dockerignore— Excludes.env, build artifacts, and non-essential files from Docker context.backend/railway.toml— Railway build/deploy config with health check at/api/health.backend/routes/api/health.dart— Health check endpoint returning{"status": "ok", "timestamp": "..."}..github/workflows/backend-deploy.yml— Auto-deploys backend to Railway on push toprodbranch (withbackend/**path filter). Includes test gate and post-deploy health check.backend/.env.example— Added 8 missing env vars discovered from code analysis:STREAM_API_KEY/SECRET,SUPABASE_URL/SERVICE_ROLE_KEY,UPSTASH_REDIS_*,RESEND_API_KEY,DART_ENV,ALLOWED_ORIGINS..env.example— UpdatedAPI_BASE_URLto reference Railway production URL.Phase 2 — Shorebird OTA Integration
pubspec.yaml— Addedshorebird_code_push: ^2.0.5todependencies(NOTdev_dependencies— fixes plan doc error, this is runtime code).lib/main.dart— Added silent OTA update check at startup (downloads patch in background, applies on next launch).shorebird.yaml— Placeholder config. Requiresshorebird initlocally to generate realapp_id.Phase 3 — GitHub Actions Enhancement
.github/workflows/flutter-ci.yml—workflow_dispatchtrigger with patch/release choicemain/develop→prod/devv*.*.*)flutter pub run→dart run(2 occurrences)test-backendjob (analyze + test on every push/PR)shorebird-releasejob (after store deploys, on tags)shorebird-patchjob (onhotfix/*push or manual dispatch)Phase 4 — Documentation
CLAUDE.md— Created (was referenced in README but missing). Covers: Railway hosting, Shorebird OTA, update decision tree, branch strategy, env config approach, CI/CD workflows, known gotchas.README.md— Updated overview table (backend → Railway, added OTA row), backend setup section (Railway production info), CI/CD section (9 jobs table + hotfix workflow).backend/README.md— Full rewrite: Railway hosting, complete env vars table (21 vars), API routes with health check, Docker build instructions.docs/deployment/03-migration-checklist.md— Comprehensive verification checklist for all 4 phases + post-migration validation.docs/deployment/— Renamed files to numbered kebab-case:01-deployment-strategy.md,02-migration-and-cicd-plan.md,03-migration-checklist.md.docs/README.md— Updated links to new deployment doc filenames..mcp.json.example— Added Railway MCP server config.Critical fixes vs. the original migration plan doc
dart build cli(doesn't exist)dart compile exeString.fromEnvironment+--dart-definefor API_BASE_URLenvied+.envapproachmainbranchprod(created fromdev)DotEnv()..load(['.env'])throws in DockerDotEnv(includePlatformEnvironment: true)+ conditional.envloadflutter-ci.ymlexists — kept it, added jobsshorebird_code_pushindev_dependenciesdependencies(runtime code).envgeneration.envcreation patternNext steps (manual, post-merge)
Immediate (before April 3)
prodbranch fromdevafter merging this PR@railway/mcp-server) or dashboardbackend/.env.exampleRAILWAY_TOKEN— Railway → Account Settings → TokensPRODUCTION_API_BASE_URL— Railway assigned URLSHOREBIRD_TOKEN—shorebird loginlocallySUPABASE_URL,SUPABASE_ANON_KEY,STREAM_API_KEY,RAZORPAY_KEY_ID,STRIPE_PUBLISHABLE_KEY,API_BASE_URL,CODECOV_TOKEN, Android signing keys, iOS signing keys, App Store Connect keysshorebird initlocally to generate realapp_idinshorebird.yamlprod→ verify Railway auto-deploy + health checkValidation checklist
See
docs/deployment/03-migration-checklist.mdfor the full verification checklist.Test plan
dart analyzepasses with zero errors (confirmed: only 2 pre-existing infos)flutter pub getresolves successfully (confirmed: shorebird_code_push installed)cd backend && docker build -t familiarise-api .docker run -p 8080:8080 --env-file .env familiarise-apicurl http://localhost:8080/api/healthreturns{"status":"ok"}prodtriggersbackend-deploy.ymlhotfix/*triggersshorebird-patchjob🤖 Generated with Claude Code