DEVOPS
SEP 25, 2025
Godfrey Cheng
14 min read

FLUTTER CI/CD PIPELINE SETUP

Complete guide to setting up CI/CD pipelines for Flutter apps. Learn GitHub Actions, automated testing, code quality checks, and deployment to app stores.

Flutter CI/CD Pipeline Setup

Modern app development requires automated testing and deployment pipelines. Manual testing and deployment are time-consuming, error-prone, and don't scale with growing teams.

This guide will show you how to set up robust CI/CD pipelines for Flutter apps using GitHub Actions. You'll learn to automate testing, code quality checks, builds, and deployments to both Google Play Store and Apple App Store.

GITHUB ACTIONS FOUNDATION

GitHub Actions provides free CI/CD for public repositories and generous limits for private ones. It integrates seamlessly with your Flutter development workflow.

# .github/workflows/flutter_ci.yml
name: Flutter CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Verify formatting
      run: dart format --output=none --set-exit-if-changed .
      
    - name: Analyze project source
      run: flutter analyze --fatal-infos
      
    - name: Run tests
      run: flutter test --coverage
      
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        files: ./coverage/lcov.info
        fail_ci_if_error: true

  build_android:
    needs: test
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build Android APK
      run: flutter build apk --release
      
    - name: Build Android App Bundle
      run: flutter build appbundle --release
      
    - name: Upload APK artifact
      uses: actions/upload-artifact@v3
      with:
        name: android-apk
        path: build/app/outputs/flutter-apk/app-release.apk
        
    - name: Upload AAB artifact
      uses: actions/upload-artifact@v3
      with:
        name: android-aab
        path: build/app/outputs/bundle/release/app-release.aab

  build_ios:
    needs: test
    runs-on: macos-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build iOS (no signing)
      run: flutter build ios --release --no-codesign
      
    - name: Upload iOS artifact
      uses: actions/upload-artifact@v3
      with:
        name: ios-build
        path: build/ios/iphoneos/Runner.app

✅ WORKFLOW BENEFITS

  • • Runs on every push and PR
  • • Parallel Android and iOS builds
  • • Automated code quality checks
  • • Coverage reporting

🎯 KEY FEATURES

  • • Flutter version caching
  • • Dependency caching
  • • Artifact uploads
  • • Multi-platform support

COMPREHENSIVE TESTING PIPELINE

A robust testing pipeline includes unit tests, widget tests, integration tests, and code quality checks. This ensures your app is reliable before deployment.

# .github/workflows/comprehensive_testing.yml
name: Comprehensive Testing

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  quality_checks:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    # Code formatting check
    - name: Check code formatting
      run: dart format --output=none --set-exit-if-changed .
      
    # Static analysis
    - name: Run static analysis
      run: flutter analyze --fatal-infos --fatal-warnings
      
    # Import sorting check
    - name: Check import sorting
      run: |
        flutter pub global activate import_sorter
        import_sorter --check-only
        
    # Unused dependencies check
    - name: Check for unused dependencies
      run: |
        flutter pub global activate dependency_validator
        dependency_validator
        
    # Security audit
    - name: Security audit
      run: |
        flutter pub deps --json > deps.json
        # Add security scanning tool here
        
  unit_tests:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Run unit tests
      run: |
        flutter test \
          --coverage \
          --test-randomize-ordering-seed random \
          --reporter expanded
          
    - name: Generate coverage report
      run: |
        sudo apt-get update
        sudo apt-get install lcov
        genhtml coverage/lcov.info -o coverage/html
        
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        files: ./coverage/lcov.info
        fail_ci_if_error: true
        verbose: true

  widget_tests:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Run widget tests
      run: flutter test test/widget_test/
      
  integration_tests:
    runs-on: macos-latest
    strategy:
      matrix:
        device:
          - "iPhone 14 Pro Simulator (16.0)"
          - "iPad Pro (11-inch) (4th generation) Simulator (16.0)"
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Start iOS Simulator
      run: |
        xcrun simctl list devices
        xcrun simctl boot "${{ matrix.device }}" || true
        
    - name: Run integration tests
      run: |
        flutter test integration_test/ \
          --device-id="${{ matrix.device }}"
          
    - name: Upload test results
      uses: actions/upload-artifact@v3
      if: failure()
      with:
        name: integration-test-results
        path: test_results/

🧪 Testing Strategy:

  • • Unit tests for business logic (70% of tests)
  • • Widget tests for UI components (20% of tests)
  • • Integration tests for user flows (10% of tests)
  • • Code coverage threshold enforcement

AUTOMATED DEPLOYMENT PIPELINE

Deploy your Flutter app to Google Play Store and Apple App Store automatically when tests pass. This includes app signing, version management, and release notes.

# .github/workflows/deploy.yml
name: Deploy to App Stores

on:
  push:
    tags:
      - 'v*'  # Deploy when version tags are pushed

jobs:
  deploy_android:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    # Setup Android signing
    - name: Setup Android signing
      run: |
        echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks
        echo "storeFile=keystore.jks" >> android/key.properties
        echo "keyAlias=$ANDROID_KEY_ALIAS" >> android/key.properties
        echo "storePassword=$ANDROID_STORE_PASSWORD" >> android/key.properties
        echo "keyPassword=$ANDROID_KEY_PASSWORD" >> android/key.properties
      env:
        ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
        ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
        ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
        ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
        
    - name: Build signed App Bundle
      run: flutter build appbundle --release
      
    - name: Deploy to Google Play Console
      uses: r0adkll/upload-google-play@v1
      with:
        serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
        packageName: com.onon.yourapp
        releaseFiles: build/app/outputs/bundle/release/app-release.aab
        track: production
        status: completed
        inAppUpdatePriority: 2
        userFraction: 0.33
        whatsNewDirectory: distribution/whatsnew
        mappingFile: build/app/outputs/mapping/release/mapping.txt

  deploy_ios:
    runs-on: macos-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    # Setup iOS certificates and provisioning profiles
    - name: Install Apple Certificate
      uses: apple-actions/import-codesign-certs@v1
      with:
        p12-file-base64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
        p12-password: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
        
    - name: Install Provisioning Profile
      uses: apple-actions/download-provisioning-profiles@v1
      with:
        bundle-id: com.onon.yourapp
        issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
        api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
        api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
        
    - name: Build iOS app
      run: |
        flutter build ios --release --no-codesign
        cd ios
        xcodebuild -workspace Runner.xcworkspace \
          -scheme Runner \
          -configuration Release \
          -destination generic/platform=iOS \
          -archivePath $PWD/build/Runner.xcarchive \
          archive
        xcodebuild -exportArchive \
          -archivePath $PWD/build/Runner.xcarchive \
          -exportOptionsPlist ExportOptions.plist \
          -exportPath $PWD/build
          
    - name: Deploy to TestFlight
      uses: apple-actions/upload-testflight-build@v1
      with:
        app-path: ios/build/Runner.ipa
        issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
        api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
        api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

  create_release:
    needs: [deploy_android, deploy_ios]
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Create GitHub Release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: Release ${{ github.ref }}
        body: |
          🚀 New release deployed to app stores!
          
          ## What's New
          - Check CHANGELOG.md for detailed changes
          
          ## Deployment Status
          - ✅ Android: Google Play Store
          - ✅ iOS: TestFlight & App Store
        draft: false
        prerelease: false

🚀 Deployment Features:

  • • Automated app signing for both platforms
  • • Staged rollout for risk mitigation
  • • Automatic release notes from changelogs
  • • Version management and tagging

ENVIRONMENT & SECRET MANAGEMENT

Proper secret management and environment configuration ensure secure deployments while supporting different stages like development, staging, and production.

# .github/workflows/multi_environment.yml
name: Multi-Environment Deployment

on:
  push:
    branches:
      - develop      # Deploy to staging
      - main         # Deploy to production
  pull_request:
    branches:
      - main         # Test on PR

jobs:
  determine_environment:
    runs-on: ubuntu-latest
    outputs:
      environment: ${{ steps.env.outputs.environment }}
      api_url: ${{ steps.env.outputs.api_url }}
    steps:
    - name: Determine environment
      id: env
      run: |
        if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
          echo "environment=production" >> $GITHUB_OUTPUT
          echo "api_url=https://api.yourapp.com" >> $GITHUB_OUTPUT
        elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then
          echo "environment=staging" >> $GITHUB_OUTPUT
          echo "api_url=https://staging-api.yourapp.com" >> $GITHUB_OUTPUT
        else
          echo "environment=test" >> $GITHUB_OUTPUT
          echo "api_url=https://test-api.yourapp.com" >> $GITHUB_OUTPUT
        fi

  build_and_test:
    needs: determine_environment
    runs-on: ubuntu-latest
    environment: ${{ needs.determine_environment.outputs.environment }}
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Create environment config
      run: |
        cat > lib/config/environment.dart << EOF
        class Environment {
          static const String apiUrl = '${{ needs.determine_environment.outputs.api_url }}';
          static const String environment = '${{ needs.determine_environment.outputs.environment }}';
          static const String apiKey = '${{ secrets.API_KEY }}';
          static const bool isProduction = ${{ needs.determine_environment.outputs.environment == 'production' }};
        }
        EOF
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Run tests
      run: flutter test
      
    - name: Build for environment
      run: |
        if [[ "${{ needs.determine_environment.outputs.environment }}" == "production" ]]; then
          flutter build apk --release --flavor production -t lib/main_production.dart
        elif [[ "${{ needs.determine_environment.outputs.environment }}" == "staging" ]]; then
          flutter build apk --release --flavor staging -t lib/main_staging.dart
        else
          flutter build apk --debug --flavor development -t lib/main_development.dart
        fi

# GitHub Repository Secrets Setup:
# ANDROID_KEYSTORE_BASE64          - Base64 encoded Android keystore
# ANDROID_KEY_ALIAS                - Android key alias
# ANDROID_STORE_PASSWORD           - Android keystore password
# ANDROID_KEY_PASSWORD             - Android key password
# IOS_CERTIFICATE_BASE64           - Base64 encoded iOS certificate
# IOS_CERTIFICATE_PASSWORD         - iOS certificate password
# APPSTORE_ISSUER_ID               - App Store Connect issuer ID
# APPSTORE_KEY_ID                  - App Store Connect key ID
# APPSTORE_PRIVATE_KEY             - App Store Connect private key
# GOOGLE_PLAY_SERVICE_ACCOUNT_JSON - Google Play Console service account
# API_KEY                          - Your app's API key
# FIREBASE_CONFIG_ANDROID          - Firebase config for Android
# FIREBASE_CONFIG_IOS              - Firebase config for iOS

# Environment-specific secrets:
# For each environment (production, staging, test), create:
# - API_URL
# - DATABASE_URL
# - ANALYTICS_KEY
# - Feature flags configuration

🔐 Security Best Practices:

  • • Store all sensitive data in GitHub Secrets
  • • Use environment-specific configurations
  • • Rotate secrets regularly
  • • Limit secret access to necessary workflows only

CI/CD BEST PRACTICES

1

Fast Feedback Loop: Keep build times under 10 minutes. Use caching, parallel jobs, and incremental builds to speed up the pipeline.

2

Fail Fast: Run static analysis and unit tests first. Don't waste time on expensive integration tests if basic quality checks fail.

3

Staged Deployments: Deploy to staging first, then production. Use feature flags and gradual rollouts to minimize risk.

4

Monitor Everything: Track build success rates, test coverage, deployment frequency, and lead time for changes.

5

Rollback Strategy: Always have a plan to quickly rollback deployments. Keep previous versions available and test rollback procedures.

READY TO AUTOMATE YOUR FLUTTER DEPLOYMENT?

Our team can help you set up robust CI/CD pipelines that save time, reduce errors, and improve your app quality. Let's automate your development workflow!

SHARE THIS GUIDE

MORE
FLUTTER GUIDES

Flutter App Architecture Best Practices

FLUTTER APP ARCHITECTURE BEST PRACTICES

Master Flutter app architecture with clean patterns, separation of concerns, and scalable code structures.

Flutter Testing Strategies

FLUTTER TESTING STRATEGIES

Comprehensive guide to unit testing, widget testing, and integration testing in Flutter.

Flutter State Management Guide

FLUTTER STATE MANAGEMENT GUIDE

Complete guide to choosing the right state management solution for your Flutter app.