← Back to posts Cover image for Mastering CI/CD for Flutter: A Practical Guide to Fastlane and GitHub Actions

Mastering CI/CD for Flutter: A Practical Guide to Fastlane and GitHub Actions

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

Mastering CI/CD for Flutter: A Practical Guide to Fastlane and GitHub Actions

Automating your Flutter app’s build, test, and release process is a game-changer. It saves hours of manual work, reduces human error, and lets you ship updates with confidence. Yet, setting up a robust CI/CD pipeline for both Android and iOS often feels daunting. You need to handle code signing, environment secrets, platform-specific builds, and store submissions—all while keeping your workflow maintainable.

This guide walks you through a practical setup using Fastlane to automate builds and GitHub Actions to orchestrate your pipeline. By the end, you’ll have a working foundation you can adapt for your own projects.

Why Fastlane + GitHub Actions?

Fastlane is a powerful automation tool that abstracts away the platform-specific complexities of building and deploying mobile apps. With it, you can define simple commands like flutter build ipa or upload_to_testflight in a reproducible way.

GitHub Actions provides a flexible, integrated CI/CD environment. It’s free for public repositories and offers generous minutes for private ones. Combining these tools gives you a pipeline that runs on every commit, ensuring your app is always in a shippable state.

Step 1: Setting Up Fastlane

First, add Fastlane to your project. Ensure you have Ruby installed, then from your project’s root directory:

# Install the Fastlane gem
gem install fastlane

# Navigate to your Android directory and initialize Fastlane
cd android
fastlane init

# Do the same for iOS
cd ../ios
fastlane init

During initialization, Fastlane will guide you through setting up App Store Connect and Google Play credentials. For now, you can skip the setup and manually create the configuration files.

The core of Fastlane is the Fastfile. Here’s a simple example for iOS that builds and uploads to TestFlight:

# ios/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Build the iOS app and upload to TestFlight"
  lane :beta do
    # Increment the build number
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )

    # Build the app with Flutter
    sh("cd ../ && flutter build ipa --release --no-codesign")

    # Upload to TestFlight
    upload_to_testflight(
      ipa: "../build/ios/ipa/YourApp.ipa",
      skip_waiting_for_build_processing: true
    )
  end
end

For Android, a similar lane can build an App Bundle and promote it to an internal track:

# android/fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "Build and deploy to Google Play Internal Track"
  lane :internal do
    # Build the app bundle with Flutter
    sh("cd ../ && flutter build appbundle --release")

    # Upload to Google Play
    upload_to_play_store(
      track: 'internal',
      aab: '../build/app/outputs/bundle/release/app-release.aab'
    )
  end
end

Step 2: Handling Secrets and Signing

A common stumbling block is managing signing credentials and API keys. Never commit these to your repository. Use environment variables or encrypted secrets.

With Fastlane, you can use the dotenv gem to load environment-specific variables. Create a .env.default file (checked into version control) with placeholder values and a .env file (ignored by Git) with real secrets.

For GitHub Actions, use the repository secrets settings. You can add keys like MATCH_PASSWORD, APP_STORE_CONNECT_API_KEY, and GOOGLE_PLAY_JSON_KEY_DATA.

Step 3: Creating the GitHub Actions Workflow

Now, let’s tie it all together with a GitHub Actions workflow. This pipeline will run your tests, build the app, and deploy it based on the branch.

Create a file at .github/workflows/cicd.yml:

name: CI/CD Pipeline

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          channel: 'stable'
      - run: flutter pub get
      - run: flutter test

  build-and-deploy:
    needs: test
    runs-on: macos-latest
    if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
    steps:
      - uses: actions/checkout@v4

      - name: Set up Flutter
        uses: subosito/flutter-action@v2

      - name: Install Fastlane
        run: gem install fastlane

      - name: Build and Deploy Android
        if: github.ref == 'refs/heads/develop'
        env:
          GOOGLE_PLAY_JSON_KEY_DATA: ${{ secrets.GOOGLE_PLAY_JSON_KEY_DATA }}
        run: |
          cd android
          bundle exec fastlane internal

      - name: Build and Deploy iOS
        if: github.ref == 'refs/heads/develop'
        env:
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
        run: |
          cd ios
          bundle exec fastlane beta

This workflow runs tests on every push and pull request. When a push is made to the develop branch, it triggers a deployment to the testing tracks (Google Play Internal and TestFlight). You can extend this to deploy to production from the main branch by adding another condition.

Common Pitfalls and Best Practices

  1. Code Signing for iOS: Use Fastlane’s match to manage your certificates and provisioning profiles. It syncs them across your team and CI system, eliminating “code signing” headaches.
  2. Build Caching: GitHub Actions can be slow if you install dependencies every time. Cache your Flutter dependencies and Ruby gems to speed up runs.
  3. Conditional Deployments: As shown in the workflow, use branch conditions to control where your builds go. This prevents accidental production releases.
  4. Keep Fastfiles DRY: If you have shared steps between Android and iOS, consider writing a custom Fastlane action in Ruby or calling a shared shell script.

Wrapping Up

Implementing CI/CD with Fastlane and GitHub Actions might require some initial setup, but the payoff is immense. You gain consistent builds, automated testing, and one-command deployments. Start with the basic pipeline above, then iterate based on your team’s needs. Automate the tedious parts, and focus on what matters—building a great Flutter app.

Remember, the goal is not just automation, but reliable and repeatable automation. Once your pipeline is in place, you’ll wonder how you ever shipped apps without it.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Flutter's Hidden Gem: Building Powerful Linux Desktop Apps with Ease

Flutter's Hidden Gem: Building Powerful Linux Desktop Apps with Ease

Flutter is gaining traction as a robust framework for Linux desktop development, offering a smoother experience compared to traditional toolkits like GTK and Qt. This post will explore why Flutter excels for Linux apps, highlight its advantages, and provide practical insights for developers looking to leverage Flutter for their desktop projects.

Cover image for Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows

Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows

Deploying Flutter desktop apps on Windows can be challenging, especially regarding code signing and preventing Windows from blocking installations. This post will guide developers through the process of signing their Flutter desktop applications, exploring alternatives to the Microsoft Store like creating executable installers, and covering the necessary steps to ensure a smooth user experience.

Cover image for Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows

Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows

Developers frequently voice frustrations about Flutter SDK version management and installation processes, particularly on Windows. This post will explore best practices for managing multiple Flutter versions using tools like FVM, discuss integrating Flutter with package managers like Winget for a smoother Windows setup, and offer strategies to ensure a consistent development environment across projects and teams.