Our CI/CD Learning Tool is out. Have a look! Upvote now on Product Hunt -->

    10 Mar 2022 · Greatest Hits

    Deploy Flutter (iOS) Apps to Testers using Firebase App Distribution with Semaphore

    11 min read

    Deploying iOS apps to early testers helps you get feedback for improvements or detect issues that came out from early on during the development. Firebase App Distribution, or App Distribution, is a service that helps you deploy your Flutter apps to testers.

    This article focuses on automating deployments for Flutter (iOS) apps using ad-hoc releases in App Distribution. If you’re interested in implementing this for Android, you can read this.


    • An existing Flutter project; you can use our starter app or create one by running flutter create my_app
    • A Firebase account; you can create one here
    • An Apple Developer Account ($99 / year) for the developer certificates and provisioning profiles

    Firebase App Distribution

    Deploying iOS apps is more complex than in Android. Unlike in Android, most of the time, you can create an android package (APK), and users can directly install and use them on their devices. But for iOS, it’s a different story. You can only install apps from the Apple Apps Store or signed ad-hoc releases from tools like App Distribution.

    App Distribution and Apple Apps Store

    App Distribution is an alternative service to deploy iOS apps to a limited number of testers to get early feedback or detect issues, and it’s not a complete replacement for the Apple Apps Store if you are planning to release your apps to the public.

    Creating a Firebase Project

    In the console, create a new project by tapping Add project.

    Assign a project name and unique ID (optional).

    Semaphore Firebase

    You can also enable Google Analytics (Optional). For now, we’ll disable this setting.

    Google analytics firebase

    Authentication with Firebase CLI

    Firebase CLI contains tools that allow you to manage, view, and deploy to Firebase. We will use Firebase CLI to authenticate CI later when deploying builds to Firebase projects.

    Setting up Firebase CLI

    There are at least two ways to set up Firebase CLI on your machine. Either by downloading the standalone binary or via the npm package manager.

    If you already have npm, you can go ahead and install Firebase CLI using the package manager, as shown below:

    npm install -g firebase-tools

    Testing and logging in with Firebase CLI

    Authenticate Firebase CLI using the email associated with your Firebase account:

    firebase login
    ✔  Success! Logged in as joshua*****@gmail.com
    Firebase CLI

    For more information, see Firebase CLI reference.

    Setting up App Distribution

    App Distribution in Firebase is where deployed builds show up and where you can manage your app testers.

    Setting up an iOS project

    If you have an existing Flutter project that uses Firebase, please skip this step. Otherwise, do the following if you are using the starter app or have created a new project using flutter create.

    In the Firebase console’s sidebar, navigate to and open App Distribution.

    App distribution

    Tap the iOS logo to create a new iOS project.

    ios firebase choose a product

    Then, enter the details of your iOS project. Optionally, you can download the GoogleService-Info.plist file and put them to your root iOS project directory.

    add firebase to your apple app

    If you use the starter app, replace com.example.semaphoreciFlutterDemo with your desired bundle identifier.

    Setting up group testers

    App Distribution allows you to configure multiple test groups depending on the build you deploy, for example, for internal or private beta builds.

    Navigate to the App Distribution page and select the Testers & Groups tab.

    ios firebase testers & groups

    Add a group named “testers”, then click Save.

    Last, to deploy your build to the users, add their emails to the group.

    ios firebase groups


    fastlane is an open-source platform with a wide range of tools and APIs for simplifying deployment for Android and iOS projects. We will use fastlane to automate our workflow, communicate with Firebase, and ultimately deploy the project.

    Setting up Ruby

    Ruby is a must-have to use fastlane in your project. Here’s an exhaustive list of ways to install Ruby on your machine.

    Setting up Bundler

    Bundler and Gemfile are for defining dependencies in fastlane. Run the following command to install bundler:

    gem install bundler

    Setting up fastlane

    If you’re on macOS and have Homebrew installed, run the following:

    brew install fastlane

    If you have gems installed on macOS/Windows/Linux, run the following:

    sudo gem install fastlane

    Setting up fastlane with your Flutter iOS project

    Set up fastlane in the ios directory of your Flutter project.

    To initialize fastlane, run the following:

    cd ios && fastlane init && cd -
    1. Select manual setup:
    What would you like to use fastlane for?
    1. 📸  Automate screenshots
    2. 👩‍✈️  Automate beta distribution to TestFlight
    3. 🚀  Automate App Store distribution
    4. 🛠  Manual setup - manually setup your project to automate your tasks
    >>> 4

    Then, wait for all the packages and dependencies to be installed properly.

    ios firebase fastlane

    You should now have a fastlane directory containing the Appfile and Fastfile.

    Adding the Firebase App Distribution Plugin to Fastlane

    We will need the plugin to upload the build artifacts to Firebase.

    Install the fastlane plugin with the following command:

    fastlane add_plugin firebase_app_distribution

    Then, when asked to modify the Gemfile for the current path (ios), type y.

    Depending on your Ruby setup, you might be asked to enter your password and provide temporary root privileges when installing the dependency:

    sudo fastlane add_plugin firebase_app_distribution

    Setting up Fastfile with Firebase


    Fastfile stores the configuration for automation using fastlane.

    First, navigate to your Fastfile directory and replace the default content with the following:

    # Credentials (for later)
    git_authorization = "<GIT_AUTHORIZATION>"
    firebase_app_id = "<YOUR_FIREBASE_APP_ID>"
    firebase_cli_token = "<YOUR_CLI_TOKEN>"
    # Team
    team_id = "<YOUR_TEAM_ID>"
    platform :ios do
     desc "Deploy iOS to Firebase"
     lane :deploy do
     # add actions here: https://docs.fastlane.tools/actions

    deploy here is the name of the fastlane workflow. To run the command in your terminal, run fastlane deploy.

    Next, below team_id variable, add the following:

    # Keychains
    keychain_user = "temp"
    keychain_pass = "temp"
    def delete_temp_keychain(name)
     name: name
     ) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
    def create_temp_keychain(name, password)
     name: name,
     password: password,
     unlock: false,
     timeout: 0
    def ensure_temp_keychain(name, password)
     create_temp_keychain(name, password)

    These are for creating temporary keychains and downloading developer certificates, and provisioning profiles on the CI.

    Then, inside the deploy lane, add the following:

    platform :ios do
      lane :deploy do
        ensure_temp_keychain(keychain_user, keychain_pass)
          scheme: "Runner",
          archive_path: "./build/Runner.xcarchive",
          export_method: "ad-hoc",
          output_directory: "./build/Runner",
          app: firebase_app_id,
          firebase_cli_token: firebase_cli_token,
          release_notes: "Bug fixes and performance improvements",
          groups: "internal",

    App ID

    To get your App ID, click Project Overview, then Project Settings.

    Next, select the current app’s Project Settings and copy your App ID.

    firebase ios setup

    Replace firebase_app_id in your Fastfile.

    Generating Firebase CLI token

    Earlier you authenticated Firebase CLI with your Firebase account; mainly for testing tooling installation. Now, we will authenticate and generate a token for CI.

    To generate a token for CI run:

    firebase login:ci

    This should give a hashed string like this:

    ✔  Success! Use this token to login on a CI server:

    Replace firebase_cli_token in your Fastfile.

    Setting up fastlane match

    fastlane match, or match, simplifies the complex code signing for your iOS apps, and Semaphore has an in-depth documentation on how to deal with iOS code signing using fastlane match. I will explain the setup here succinctly.

    Configuring match

    Initialize match by running

    fastlane match init

    Next, select git to store the developer certificates and provisioning profiles:

    fastlane match supports multiple storage modes, please select the one you want to use:
    1. git
    2. google_cloud
    3. s3
    >>> 1

    Then, enter the URL of your git repository, for example: [https://github.com/joshuadeguzman/ios-certificates](https://github.com/joshuadeguzman/ios-certificates)

    Finally, replace development to adhoc in your Matchfile


    Generating certificates and profiles

    Create ad-hoc certificates and profiles by running. When prompted, enter your Apple Developer Credentials.

    fastlane match adhoc -a <YOUR_BUNDLE_IDENTIFIER>
    All required keys, certificates and provisioning profiles are installed 🙌

    Use the match provisioning profile in your project settings using Xcode:

    firebase ios Xcode

    Downloading certificates and profiles in fastlane

    In most cases, you should be able to clone your match’s git private repository on your local machine. But in CI, we have to provide appropriate authorization to allow access.

    Create your Github personal access token by following this.

    Use the personal access token generate and replace the git_authorizationin yourFastfile:

    git_authorization = "joshuadeguzman:<YOUR_ACCESS_TOKEN>"

    Last, add the match command

    platform :ios do
      lane :deploy do
        ensure_temp_keychain(keychain_user, keychain_pass)
        # Add this
          type: "adhoc",
          git_basic_authorization: git_authorization,
        ... (truncated)

    Deploying to Firebase using fastlane locally

    That’s it for Firebase and fastlane for now. Let’s test the Firebase deployment by running fastlane locally.

    Ensure the project is in clean state:

    flutter packages & clean

    Then, build the Flutter iOS app:

    flutter build ios —-no-codesign

    To deploy your app, run the following:

    cd ios && fastlane deploy && cd -

    This should give you a success message for deploying your app to Firebase.

    firebase ios deploy

    Great! Your latest iOS build should be displayed on App Distribution’s releases dashboard.

    Deploying to Firebase using Semaphore

    Before proceeding, I recommend reading core concepts that make Semaphore intuitive and powerful. I also explained the Visual Builder for iOS continuous integration in this article.

    Setting up a Semaphore project for your app

    Log in with your Semaphore account, then create a new project.

    firebase ios create

    Choose the repository for your project, then click Continue to workflow set up.

    Then for the workflow set up, click Customize.

    firebase ios customize

    Setting up blocks to integrate and build your apps

    Configuring the workflow pipeline

    We’ll be using a Mac Based Virtual Machine environment with a macos-xcode13 image.

    firebase ios semaphore

    Setting up your blocks for Continuous Integration (CI)

    Create a block named Install Dependencies, then add a job Install and cache Flutter:

    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get
    cache store flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages /root/.pub-cache


    Then, add a new block named Lint, then add jobs Format and Analyze:


    flutter format --set-exit-if-changed .


    flutter analyze . 


    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get
    firebase ios semaphore flutter

    Finally, add a block to run your Flutter tests.

    Run unit and widget tests

    flutter test test


    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get
    firebase ios semaphore

    Optionally, you can test your workflow by clicking Run the workflow.

    Configuring promotions to deploy your apps

    Setting up a new promotion

    Promotions help us create conditions around our workflows and whether or not we have to run another workflow or set of workflows. Also, they can be triggered manually or automatically.

    After configuring the visual builder, we can merge the set-up-semaphore branch to the default branch for continuous deployment to Firebase using automatic promotions. In our case, the Deploy to Firebase promotion gets triggered once the condition "branch = 'master' AND result = 'passed' is satisfied by the workflow run.

    Configuring the deployment pipeline

    Similar to the Main pipeline, we will use a Mac Based Virtual Machine environment with a macos-xcode13 image.

    firebase ios semaphore

    Next, in this pipeline, you will also need a Install Dependencies block set up with a job Install and cache Flutter:

    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get
    cache store flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages /root/.pub-cache
    firebase ios semaphore

    Last, create a block named Deploy to Firebase with a job Run Fastlane:

    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    sh bump_version.sh
    flutter build ios --no-codesign
    cd ios
    bundle install
    cache store
    bundle exec fastlane deploy

    This block is responsible for executing your deploy lane in fastlane.

    firebase ios semaphore

    Click Run the workflow and commit the changes.

    Awesome, your workflow should be ready for automatic deployments.

    Testing continuous deployment

    To check automatic promotions and deployments, we should merge it to the working branch to master:

    git fetch --all
    git merge origin/set-up-semaphore
    git push origin master

    Now, push the changes to trigger the build.

    firebase ios semaphore


    Setting up a continuous deployment pipeline for iOS takes more time than Android, but it’s worth the investment. Creating new releases every time a developer merges a pull request using this approach saves you half the time if you’re doing all the steps mentioned above manually, especially dealing code-signing for your teams.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Writen by:
    I'm a software engineer focusing on delivering products that help improve people's lives. I help build a platform that enables everyone to invest in people and ideas. I collaborate with the tech community regarding developer experiences and tools like Flutter and Dart. I previously helped launch products used by millions globally.