iOS Continuous Deployment with Fastlane 🚀

iOS Continuous Deployment with Fastlane - Coletiv Blog

If you want to add Continuous Deployment (CD) to your iOS applications and you are having a hard time doing so, you have come to the right place.

We were missing an article describing the complete process of using Fastlane to perform CD safely and with a working example using a Continuous Integration (CI) service (in this case Travis CI). With this in mind, this guide will provide you a step-by-step guide for the entire process.

We have now deployed multiple applications at our company and we found the process of manually delivering the applications to the App Store is a very repeated, frustrating and time consuming process. Using Fastlane we went from a few hours of testing and deployment to just a few minutes.

This is the second article from a two part series that includes:

Notes: Sample Project 📋

Notes is an iOS application that allows the user to add, remove and change notes. This project is used to illustrate how to do continuous deployment using Fastlane together with Travis CI.

The entire project is available on GitHub, for you to consult and use as you please.

Requirements 🎒

  • Create a new Apple ID without 2FA. Avoid using your own account, its safer for you and easier to deliver the project along to your client or someone else. Fastlane supports 2FA but you will run into issues when managing the session token (you should test it though!);

  • Add the account to the project development team in Apple Developer and iTunesConnect. This will be mandatory to automatically create the necessary certificates and provisioning profiles, and also to upload the applications to TestFlight or the App Store;

  • An empty GitHub private repository to store the encrypted certificates and provisioning profiles used in your project. Don’t use or change this repository for anything else, the script will manage the repository files on its own.

By the end of the article 🎓

You will have multiple commands available, all under a single file, that will allow you to:

  • Test your application, with a pretty printing at the end;

  • Manually deploy your application from your machine, with the build version incrementing by fetching the current version from TestFlight;

  • Automatically deploy your application from your CI service.

A. Xcode Project Setup 🛠

We need to setup the Xcode project for a harmoniously interaction with the Fastlane and Travis CI scripts.

At the time of writing and after trying to use Xcode with the **Automatically manage signing **selected, we concluded the only viable way to properly implement the continuous deployment was to go with a manual certificate management.

These are the steps:

  • Create the certificates;

  • Configure Xcode signing to the new certificates;

  • (Optional) Register more devices.

A.1. Certificate Management using Match (from Fastlane)

You will need the Apple account and the GitHub repository mentioned in the Requirements 🎒 section. Just use the init command and follow the steps shown (you will be prompted the repository URL).

fastlane match init

By the end of the script you will have created a Matchfile inside the fastlane folder. Open it and change the app_identifier and the username to your application bundle identifier and the Apple ID of the account created, respectively.

Now we need to run the command to create the certificates to the desired profile type. Since we will want to configure the Xcode project correctly, we are creating the development and the appstore types.

Run the development script, a passphrase will be prompted to encrypt and decrypt the certificates, save it since we will later need it.

fastlane match development

Proceed with the appstore script:

fastlane match appstore

Every certificate needed will be created, remember that this will create the certificates for all the devices listed in the developer account certificate manager, if you need to add more check step 3. Register Devices ahead.

A.2. Xcode Configuration

After you run the scripts above, every certificate will be installed in your computer. This way you can open your .xcworkspace or .xcodeproj and configure your targets signing accordingly.

A.2.1. Disable Automatically Manage Signing

Remember to associate a specific provisioning profile with a configuration, an example using the default Xcode configurations:

  • match Development for Debug

  • match AppStore for Release

Xcode (Target Signing Configuration) Xcode (Target Signing Configuration)

As you can see, we disabled Automatically manage signing and set the match created provisioning profiles to their respective configuration.

NOTE: Leave the test targets (In the picture above: NotesTests and NotesUITests) with the Automatically manage signing setting turned ON.



A.2.2. Enable Apple Generic Versioning System

This is only if you want Fastlane to manage the versioning of the application, it needs to use the Apple Generic system in order to know how it should increment the build version automatically.

To do this you can either configure the project (like the example below) or the target depending on what suits you best:

Xcode (Project Versioning Configuration) Xcode (Project Versioning Configuration)

A.3. Register Devices 📱

This step isn’t mandatory but if you are testing with multiple devices, you will want to register them and create the appropriate provisioning profiles to be able to run the application on those devices.

In order to register devices you will need a name and a UUID. If a device is connected to your computer, you can use Instruments to list all the connected devices and find these two parameters using the command:

instruments -s devices

After retrieving the name and UUID of the devices you can do two things:

  • Register a single device via command line using the tool register_device: fastlane run register_device name:"iPhone 8" udid:"d629fef002af1..."

  • Register multiple devices by using the Fastlane tool register_devices. Unfortunately there is no command line support at the time of writing and I can’t provide an example, if this changes please let me know.

NOTE: You will need to repeat the step 1. Certificate Management using Match with Forcing to update the Development provisioning profiles with the new devices.

fastlane match development --force

The AppStore provisioning profiles are not specific to the list of devices you have registered therefore they don’t need to be updated.

B. Fastlane Setup 🚀

After configuring Xcode, we will need to write the Fastlane scripts to perform the manual and automatic deployment.

NOTE: If you don’t have any builds uploaded to Testflight or the App Store, you need to perform a manual deployment before you can automate the process.

We need to update the Appfile, located inside the fastlane folder, to contain the app_identifier and the apple_id these two are mandatory.

The itc_team_id and the team_id are only needed if your Apple ID is integrated into more than one team on the Apple Developers Portal and iTunesConnect.

NOTE: If your Apple ID is in multiple teams, remove the settings and run the B.1. Manual Deployment script, extract the iTunesConnect ID and the Team ID from the script log (you will be prompted to select a team) and set them in the Appfile.

# Application Configuration
app_identifier "com.coletiv.medium-notes" # Bundle Identifier

# Apple Configuration
apple_id "medium-notes-ci@coletiv.com" # Apple ID
itc_team_id "119013826" # iTunes Connect Team ID
team_id "RGK82JLN9X" # Developer Portal Team ID

# For more information about the Appfile, see:
#     https://docs.fastlane.tools/advanced/#appfile

B.1. Manual Deployment

We now need to create the Fastlane lane responsible for the manual deployment, we called it manual_testflight, check the Fastfile below:

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do

  desc "Push Notes to TestFlight Manually"
  lane :manual_testflight do

    # Fetch the necessary certificates and
    # provisioning profiles into default keychain.
    match(
      readonly: true
    )

    # Increment the build number using the
    # latest Testflight build number.
    increment_build_number(
      build_number: latest_testflight_build_number() + 1,
      xcodeproj: "./Notes.xcodeproj"
    )

    # Build the application using the
    # specified scheme.
    build_app(scheme: "Notes")

    # Upload the application to Testflight
    upload_to_testflight(
      skip_waiting_for_build_processing: true
    )
  end

end

The script will fetch all the necessary certificates, increment the build number by checking the latest build uploaded to iTunesConnect, create an .ipa file and upload it to TestFlight.

Before running the script we will need to set some environmental variables: the Apple ID password and the passphrase I said you would need later during step A.1. Certificate Management using Match.

export FASTLANE_PASSWORD=”YOUR_APPLE_ID_PASSWORD”
export MATCH_PASSWORD=”YOUR_CERTIFICATES_PASSPHRASE”

If this is the first build you are uploading, you might get a version prompted, just press Enter and it will properly get the initial version (1.0).

NOTE: If you didn’t follow step A.1. Certificates Management using Match, you will not have the necessary certificates on your computer to perform the manual deployment.

Now we only need to run the manual_testflight lane:

fastlane manual_testflight

B.2. Automatic Deployment

For the automatic deployment we need to add some changes to the manual lane and we created the lane travis_testflight, you can check in the Fastfile below:

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do

  desc "Push Notes to TestFlight with Travis CI"
  lane :travis_testflight do

    # Fetch the keychain env variables
    # securely stored in the travis.yml.
    keychain_name = ENV["MATCH_KEYCHAIN_NAME"]
    keychain_password = ENV["MATCH_KEYCHAIN_PASSWORD"]

    # Create a temporary keychain to
    # store the certificates.
    create_keychain(
      name: keychain_name,
      password: keychain_password,
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      add_to_search_list: true
    )

    # Fetch the necessary certificates and
    # provisioning profiles.
    match(
      keychain_name: keychain_name,
      keychain_password: keychain_password,
      readonly: true
    )

    # Increment the build number using the
    # latest Testflight build number.
    increment_build_number(
      build_number: latest_testflight_build_number() + 1,
      xcodeproj: "./Notes.xcodeproj"
    )

    # Build the application using the
    # specified scheme.
    build_app(scheme: "Notes")

    # Upload the application to Testflight
    upload_to_testflight(
      skip_waiting_for_build_processing: true
    )

    # Remove the temporary keychain leaving
    # no trace.
    delete_keychain(
      name: keychain_name
    )
  end

end

This script will do the same thing the manual does, but will take into account an extra measure of security. It will create a locked keychain and save the certificates there and, by the end of the script, it will delete the keychain along with all the downloaded certificates.

NOTE: Do not run this lane on your personal computer, it will mess with your keychain and you might end up losing your keychain data. Use it only on the CI machines.

C. Travis CI Setup

As you were able to see in section B.2. Automatic Deployment, the script contains environmental variables. We will need to encrypt those into the Travis CI script and some other variables.

For this step you will need five environmental variables, some are the same as before:

  • MATCH_PASSWORD = The certificates passphrase;

  • MATCH_KEYCHAIN_NAME = A keychain name (choose);

  • MATCH_KEYCHAIN_PASSWORD = A keychain password (choose);

  • FASTLANE_PASSWORD = Apple account password;

  • CI_USER_TOKEN = GitHub Personal Access Token with repo permissions, you can get it here (it will be necessary to access the private repository with the certificates).

After you get all those, you need to encrypt them onto your .travis.yml file, to do so just insert the following five commands, one by one:

travis encrypt 'MATCH_PASSWORD=YOUR_CERTIFICATES_PASSPHRASE' --add env.global

travis encrypt 'MATCH_KEYCHAIN_NAME=KEYCHAIN_NAME' --add env.global

travis encrypt 'MATCH_KEYCHAIN_PASSWORD=KEYCHAIN_PASSWORD' --add env.global

travis encrypt 'FASTLANE_PASSWORD=YOUR_APPLE_ID_PASSWORD' --add env.global

travis encrypt 'CI_USER_TOKEN=YOUR_PERSONAL_ACCESS_TOKEN' --add env.global

This will add the environmental variables directly to your travis script. Now we just need to add a command to provide GitHub access to the Travis machine during before_install and execute the Fastlane lanes during script phase, you can check the final script below:

# Configurations
language: objective-c
osx_image: xcode9.2
xcode_sdk: iphonesimulator11.0
# Before Install
before_install:
  # This command will provide travis machines with github access
  - echo -e "machine github.com\n  login $CI_USER_TOKEN" >> ~/.netrc
# Script (Fastlane)
script:
  - fastlane scan
  - fastlane travis_testflight
# Environmental Variables
env:
  global:
    - secure: VrIEyJrkZo+IkOXHJEIir+sYIt9YsuYDcCCdtGtY1ILT5MbENSeC3rjhsi8jmg4L9mUna2mayWvZsHfo8WqLg5Iyq9TshvewhnsMI/7eEqis7afnw9kY8qJA23lsMjj57SPn0Y/C8HU2/jzlfrg0qFdCgRzOZW1BaxBq8hM1pWv8+jisbTG2M+2DgFjKUj2mstFRIkIRHsI6+NnHvHdymIVEZbneBFL0iJ/8gWqi2uq1iNobDql4jQUY8z8n2LbAwtxPIRvp/6PAKBhRzaa/jDFbayBH/W6ecLc/i1YzNJZ3N5YuWynMO30DUVQuCjTzmpSM9jmqh7czLaPpco40W0SnEFCxFJ9tlnmSyHoD6/TDalfpML30JKnA5ReuYHdy0Xa4knrV7YT28Yv4IV1cAU2byJlyaunMxt5biz1xO3nSciYqrXVKOBCHQlLBNsChNkRFn+ueKrNfGZX4gvOyfaJIOY1gAcWyZ7dJtDtgrK30W3iol6On4lVhb+sCoCRej8K5DmYepAFnjzvvq6iNaBosp7WpBzOTk3AuaKEU3yLDBJva+Ebh7NEvvKPhBzpdcX0uQAE3yRodnxfOhiyvXXraHdEYEcYbAIMQXISJOx+pWAC/irWlf6Co2i3T6Hw3oCZ1TYSp1h8ZbsZdw9KJ8QpTXasjv3lXYPKIndGTdlk=
    - secure: HWVcjB5df5Cmd/oz+NDoV4nijS6zRD+rEMfiFzWlP8a7jRWe2kJeOeCdVU737qW5bNDcc0Nt4gi9LQQFzUqKQPV3ZMLn2USeUl1DvaKZ4Pl+QErJsVXLzY5HTijA6611OQLUfg7qfscx0fnMW2uiCRoZ2dwqoYQ3fI8BCGUEEgOfKJ96B1en3SwSN0H/u/Yx9tW+dtPljcNQKi96M5AFw9Af/yU7S7eAcumi4Tii2YkAA8l7aB5V8VcPy8MQIqcnrJ0SkVgIsNaE+r9tVN6++G37f2RakyW47m4fURWTSb89LLg6ByNpq8+CIt/8qb9ZJVJrwMlii9zp4L9H0PZ8Y36wSrX2sdwu/YEZGYyjrRhIU1HB0olgKR+/j/gJfRJ11P9yeYscwMgvlQO2vuJbW/ZwrkUW4XY+qlc21MfyF4Cm5hDcqWfCqgImNOP84VoAouART6Xv03VNwpTCW3EHg2+YUUGMzIyEiX76PpmEQFxLyLWLioKgOm6hTCNMCAG5qQlw6bQlWq9LYpkRD8Pq8tU9JkDjEmOrclPByzcTVFs40C82WbsXEpwDsV37L3dmR83Zish7GA/iTo7T12e1QkrsY0KGbBQ9jr7DyaWIGwnrw+w2wZ7r9ie51PP/pGS5eO5Q2K22atVQ0TO8qY7nl9lRWr73AzsBmnYsjIHdcQA=
    - secure: n27Wz2chS8jAqOGbfiCkFj3mF1uf4E0HjVi/isG+5hMFzehGylHHwq9pCNxPDPx/VV1qHtBjW8k81Kx3iC1iHcE2jUI+XEFHgN9LaOVS1SELUkugRm8oWBDSRAqOG5O0IpmcGZKnNb4j5O1xEeKySvhYO/+AFvmN9URb02xBtoMGGia6SmhDJK38FQfw9/F1O1YJ7iByefI3y8yDvw5FsZGGeDsw8tKtAW9xOkWjzCOne71G7TzTkdKECfDgbUDAPIH34DugvV2MQeC5WVWmLX/R9I2LuTFLdr5jH+txUdc58OFu0kw9s+P1kKHgnvtWtrBCqOtKujPb7FYvPIZt0DeMgkBbF6NP5gKOYiPsATyuKvpAKlLJhWlwSjJiRCdwojHK5VOxGKZD6oQeNJVeXus7/wbygE1j6APhwQsWnu19+uVJqXqxGo0wF8j0skt8kysI13qJoulQ8UgrDmg5UY1tvA8D9HHQLFZTYmNOodXg8sOAXUZz4sBuDrDHoVIaFDwg6WOJ5buRttqvGmsvBI5ShfW7Zk6WwY3BvROSWXviEMCLbMMHcwWTzf5lBKP8pq7ePZgyDTMH+7A5bTlHEmnSFIgzXmvEanjUWEW70LgApd2gmyW2Hmd4C6W0fVmLHMcnJoA6v61XI67LPTq1NLFEy2fPDWsJzGraRNj+z2M=
    - secure: RnfgsbNlOpN/G1oS0JYapxsxCiarZiDWx/ODRteFc+nnONLDvKTSuzypocB2gLqScgw2KiDRQQUVpctdBI/rk3vQAkJ31NXJ1mLXUo0Zyt8NiTEUn2dCCFc/y07Kf1dDM7bGzvU61+kDn2HKgmFGyfHolh2t3DzVuS2Y3F1xMb+CueBhi+XwrSj8OHjKHSqo9bg0BrMWMTEcNhzpAdrnbJWxbkgyK9bpq44jC1Bq6OQkkbg6Ti4qrqnvURzUMvX386QW94T1PYgRNcYxfMeoB2QyYlTaoMFJO2ALemF+VeZ4rT0vEE8mqaAlAmxkUS5Ge3HNhjFoDNlHrJI5/lnjuulv5gcLGLhGtPVqfc1TUIe327TipaNXYUphQdX7S4VGphF9TRNLhydFIUNei+FZUklIoQiAVUohsYAdkstljDYUckzn2DoJzpgHNvw6kOy+SFYBdlTrzBhNta5P3dSKs3f18SwPVCmqE/bq2TKWhhbjjCt4NLQ77Wi+qfFRvXKxniqiLkaKZUiew2SX/9cEKN8fDvWXAX2sgTTJSiFWY/sawkwoolbrN+dfuSdwNPzebRSDNyovtpbr5RexDJfrJDjpA6tP++7jGyyIELzCzVfYiXsEaOnasw5jWM9yE8So0wGVGEngoViQ3/5b6DlZRBAW08h98Yttsc+LvEW11fc=
    - secure: pMOnc9ZtNeeKwv30q6XL2vn2iW489ikDxmTff76VW2KIiLAg2gtcvUi/t3yeYP/5rimgJP9foQZRrUZgS89LDEJs7rjQEZLt8fEhIW0dO4R8vkOytqspoyZeeUpPBJYMYNFrzXU3tkvq2L9tcIOohOlRJ64h7QUSvQQ6DG7btW3rW4GAyRwN6Nzxl2fjANH0zMUjIouYi2mXj0sC/5HSD6GE6vCLZvLeW496bDF2CIj3O7c6FLfY6JnHCPWMlsH114533UzG02l9iANs3BNcTFRmlkI6ft0ElB4hqoAMMTI9N4bSnrxn1vydr+gZy3UGeumKG2XONirCzwfuFxI31zeiOy+vDy1H1Dyh/w+8h6gleRuiWCiCTcvI0Xb9zVKttd5Cc4vS40umHycMnChLWfOTtvGEPTF6PfnwcnjTEKJgISshsg6391Qyam20Xhgbby7zS12ZKCxnZlCzlDFQGYWzsEul8E3S8fM6rp/MgO9Ri6xnqgR9K4BpA/mzpwv9+298Q4nPsilmgN51ZMhi/JKMKXRUUGhvu1c0t1OFqADAtnoBPDbnuWGV7nP8trbVMe3AYC7yoRw1CYHMNx2WOhMNc2+EI/X9WTvP3+rm4PL0k1kb64d1TTx/mJvmIu+onJEP0DPWqnpKCWyEqA52/std9tlt9+fAwhuqIavHqrw=

Time Saving Tip 🕐

If you are tired of getting the Missing Compliance warning on iTunesConnect, you can add a key to your Info.plist file that automatically complies with the export policies.

Set the key ITSAppUsesNonExemptEncryption boolean to NO, remember you should only use this if it applies to your project (more info here).

Thank you for reading! 😊

I hope this guide helps you and your team saving some time with the deployments and remember to continuously check Fastlane advancements and updates. More and more tools will come that may expedite this enduring process of deployment!

Thank you so much for reading, it means a lot to us! Also don’t forget to follow Coletiv on Twitter and LinkedIn as we keep posting more and more interesting articles on multiple technologies.

In case you don’t know, Coletiv is a software development studio from Porto specialised in Elixir, iOS, and Android app development. But we do all kinds of stuff. We take care of UX/UI design, web development, and even security for you.

So, let’s craft something together?