Github Actions: Build and deploy react native iOS apps

GitHub Actions iOS Deployment

As a software development company, P&M is moving in line with developing technologies. In this regard, we have faced the task of automatic deployment of on iOS app (based on react native). As soon as our CI/CD is almost fully based on GitHub Actions we are going to present to you how we have tackled this issue in GitHub Actions.

Main issues with iOS deployment

For now, let’s discuss the main issues concerning the iOS deployment:

  1. The OS: You must use MacOS to deploy iOS (apple in general) apps. The Xcode is not available in any other OS, thus you have to use MacOS to be able to publish your app to the AppStore.
  2. Dynamic versioning: You will probably want to forget about versioning and wish that it is automatically handled and increased (both build number and version).
  3. Using keystore: As you probably know MacOS keeps passwords (secrets, certificates, etc) in its keystore. However you can’t have a permanent keystore in GitHub Actions. You will have to somehow import it every time and make sure it does not ask for a password in a pop-up window. Because that is impossible in console mode.

How we solved the issues with iOS deployment

Fortunately, GitHub Actions supports MacOS, unlike many other cloud based CI/CD providers. So this issue is automatically solved if you choose GitHub Actions.

There are several possible solutions for dynamic versioning:

  1. You could have kept the current versions in an external DB. Thus, reading and incrementing (in case of success) from the DB which is persistent. The external DB can be hosted somewhere in the cloud (e.g Azure SQL) as PAAS (~5 $ per month).
  2. Another option is to keep the version in GitHub Tag and execute your action when a new tag is created.
  3. There is a possibility to save it in a file in a GitHub repository. But that is not the best solution as you will have to commit a file with new version to the git. You might end up with an infinite deployment loop.
LESEN SIE AUCH:  Virtuell besser zusammenarbeiten - dank einer Collaboration Strategy

Let’s go more practical from this point, here is the workflow file we use for our configuration:

name: CI

    - 'ios*'

    runs-on: macOS-latest
    - uses: actions/checkout@v2      
    - name: Update Node
      run: |
        sudo npm install n -g
        sudo n stable
    - name: Yarn
      run: |
        yarn install
    - name: Install react native
      run: |
        sudo npm install -g react-native-cli
    - name: Install fastlane
      run: |
        sudo gem install fastlane
    - name: Build
        FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
        MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
        GIT_URL: ${{ secrets.GIT_URL }}
        APPLE_ID: ${{ secrets.APPLE_ID }}
        OUR_KEYCHAIN: "mychain"
        SUPERPASS: "mypass"
      run: |
        TAG=$(echo ${GITHUB_REF}| cut -d'-' -f 3)
        export BUILD_NUM=$(echo $TAG| cut -d'b' -f 2)
        export VERSION_NUM=$(echo $TAG| cut -d'b' -f 3)
        pod install --project-directory=ios --repo-update
        react-native bundle --entry-file index.js --platform ios --dev false --bundle-output ios/main.jsbundle --assets-dest ios
        cd ios
        pod install
        fastlane buildnumber
        fastlane internal --verbose

First of all, we organize our CI/CD with fastlane. That is why you have to provide FASTLANE_USER , FASTLANE_PASSWORD and MATCH_PASSWORD. Obviously it makes sense to declare these values in GitHub secrets (as well as most of the other ENV variables).

The variable GIT_URL contains the git URL of the signing certificates. To avoid password prompting, we save that in pair with a github token (e.g. https://{TOKEN}{REPO}.git) .

The variables called OUR_KEYCHAIN and SUPERPASS are for MacOS keychain, but we will come back to that later.

The following lines retrieve the version and build number from the Tag (separated by “-”) and set it to corresponding environmental variables:

TAG=$(echo ${GITHUB_REF}| cut -d'-' -f 3)
export BUILD_NUM=$(echo $TAG| cut -d'b' -f 2)
export VERSION_NUM=$(echo $TAG| cut -d'b' -f 3)

Example of tag: ios-1593081088-b12b1.1 {platforName}-{timestamp}-b{buildNum}b{version})


Now, let’s have a look at fastfile:


before_each do

platform :ios do
desc 'set buildnumber'
lane :buildnumber do

bnum = sh("echo " + "$BUILD_NUM")
build_number: ENV["BUILD_NUM"].to_i,
xcodeproj: 'APP.xcodeproj'

platform :ios do
desc 'set versionnumber'
lane :versionnumber do
version_number: ENV["VERSION_NUM"],
xcodeproj: "APP.xcodeproj"

platform :ios do
desc 'Push a new build to the Internal-Testers'
lane :internal do

name: "login.keychain"

keychain_name = ENV["OUR_KEYCHAIN"]
keychain_password = ENV["SUPERPASS"]

name: keychain_name,
password: keychain_password,
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: false,
add_to_search_list: true

keychain_name: keychain_name,
keychain_password: keychain_password,
readonly: true,
type: "appstore"

scheme: 'APP',
workspace: 'APP.xcworkspace'

apple_id: ENV["APPLE_ID"],
skip_waiting_for_build_processing: true

lane :prod do
scheme: 'APP',
workspace: 'APP.xcworkspace'
reject_build_waiting_for_review: true,
skip_waiting_for_build_processing: true,
groups: 'Intranet-Prerelease',
changelog: 'new version for internal test',
distribute_external: true

First of all, we get the version and build number from environmental variables (previously set from workflow) and assign it. Then, during the match we delete the existing default keychain, create a new keychain and set it as default. Now all our passwords are saved there.

LESEN SIE AUCH:  Ein Portal entwickeln lassen: So geht`s!

Here we use dummy variables (OUR_KEYCHAIN and SUPERPASS). If we skip this step, we will get an error because MacOS will ask for a password for its default keychain which we will not be able to provide in non-interactive mode.

The later operations just build and publish the app to test flight.

IMPORTANT NOTE: if you run this fastfile locally with this configuration it will delete your local keychain. So re-think before doing so!

Über den Autor

Dr. Aram Kocharyan

Dr. Aram Kocharyan ist bei P&M für den Bereich DevOps zuständig. Aram stammt aus dem wunderschönen Armenien, lebt nun aber in Hamburg. Für den P&M Blog schreibt er auf Englisch.

Scroll to Top