CI/CD Pipeline
GitHub Actions + Fastlane for building, testing, and distributing ReCursor to iOS and Android.
Pipeline Overview
Section titled “Pipeline Overview”PR opened/updated: [flutter analyze] -> [flutter test] -> (pass/fail)
Push to main: [flutter analyze] -> [flutter test] -> [build Android APK/AAB] -> [build iOS IPA] | | Play Store TestFlight (internal track)GitHub Actions Workflow Structure
Section titled “GitHub Actions Workflow Structure”Three jobs:
test— runs onubuntu-latest: analyze, unit tests, widget tests, golden testsbuild-android— runs onubuntu-latest: build AAB, upload to Play Store internal trackbuild-ios— runs onmacos-latest(required for Xcode): build IPA, upload to TestFlight
Workflow Configuration
Section titled “Workflow Configuration”name: Test
on: pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable'
- name: Install dependencies run: flutter pub get working-directory: apps/mobile
- name: Analyze run: flutter analyze working-directory: apps/mobile
- name: Run tests run: flutter test working-directory: apps/mobilename: Deploy
on: push: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: # Same as test.yml
build-android: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0'
- name: Setup Fastlane uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' bundler-cache: true
- name: Decode keystore run: | echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks working-directory: apps/mobile
- name: Build AAB run: fastlane android deploy working-directory: apps/mobile env: KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
build-ios: needs: test runs-on: macos-latest steps: - uses: actions/checkout@v4
- uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0'
- name: Setup Fastlane uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' bundler-cache: true
- name: Build IPA run: fastlane ios deploy working-directory: apps/mobile env: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}iOS Code Signing (Fastlane Match)
Section titled “iOS Code Signing (Fastlane Match)”- Store encrypted certificates and provisioning profiles in a private Git repo.
- Required GitHub Secrets:
MATCH_GIT_BASIC_AUTHORIZATION— base64-encodedusername:PATMATCH_PASSWORD— encryption passphrase- App Store Connect API key (preferred over
FASTLANE_PASSWORDto avoid 2FA issues)
- Use
setup_ciin the Fastlane lane to create a temporary keychain on the CI runner.
Fastfile (iOS)
Section titled “Fastfile (iOS)”# fastlane/Fastfileplatform :ios do desc "Deploy iOS app to TestFlight" lane :deploy do setup_ci
match( type: "appstore", readonly: is_ci, )
build_app( scheme: "Runner", workspace: "ios/Runner.xcworkspace", )
upload_to_testflight( skip_waiting_for_build_processing: true, ) endendAndroid Code Signing
Section titled “Android Code Signing”- Store the keystore as a base64-encoded GitHub Secret (
KEYSTORE_BASE64). - Decode in workflow:
echo $KEYSTORE_BASE64 | base64 --decode > android/app/keystore.jks - Reference key alias and passwords from secrets in
key.properties. - Upload to Play Store internal track via Fastlane’s
supplyaction. - Note: First release must be uploaded manually via Play Console.
Fastfile (Android)
Section titled “Fastfile (Android)”# fastlane/Fastfileplatform :android do desc "Deploy Android app to Play Store" lane :deploy do build_android_app( task: "bundle", build_type: "release", )
upload_to_play_store( track: "internal", release_status: "draft", ) endendKey Principles
Section titled “Key Principles”- Never echo secret values in logs.
- PR builds run tests only, never deploy.
- Use branch-based triggers: PRs -> test;
mainpush -> test + deploy. - Pin Flutter version in CI to match local development (
subosito/flutter-action). - Cache pub dependencies and build artifacts between runs.
Alternative: Codemagic
Section titled “Alternative: Codemagic”- Purpose-built for Flutter with macOS build machines included.
- Built-in code signing management (no Fastlane config needed).
- Costs money but saves significant setup time, especially for iOS.
- Consider if GitHub Actions macOS runner costs or complexity become prohibitive.
Related Documentation
Section titled “Related Documentation”- Testing Strategy — Testing approach
- Architecture Overview — System architecture
Last updated: 2026-03-17