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

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

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).

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

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*****

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.

Tap the iOS logo to create a new iOS project.

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.

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.

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

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

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.

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:

default_platform(:ios) # 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:

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") end
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.

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: 1//0etIq08H_oIB5CgYIARAAGA4SNwF-L9IrgmGb2iadCGcAmqhzK_NH8F8zQBkbPrMS2qtWhMqUmpbVbYK4YzC_83-eHb8NgylEMUg

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: [](

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:

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 your Fastfile:

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) end

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.

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

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.

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

Then for the workflow set up, click 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.

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

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

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

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.

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

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
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.

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.

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.

Originally published at on January 20, 2022.




Supporting developers with insights and tutorials on delivering good software. ·

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Top 10 Upcoming WhatsApp Features in 2022

The Fifth PoD Governance Cycle Has Ended!

Why Is DeFi Expensive

How I Made My Minimum Amazing Product In A Day

Learn Dart (for Flutter) by comparing it to TypeScript

Meet Podstack: A New Podcast Recommendation Engine!

Output: Piezo Speakers

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


Supporting developers with insights and tutorials on delivering good software. ·

More from Medium

Flutter CI/CD with GitHub Actions , fastlane and Firebase App Distribution for iOS

Communicate Between Flutter and Native Android and iOS Code Using Platform Channel

Person on laptop

How to connect firebase with flutter application using Firebase CLI and node.js(windows 10) .

Flutter 2.10 Migration Guide