iOS Continuous Deployment Using Travis CI and Fastlane

Continuous Deployment (CD) is a practice of using automation to release your code in a quick and sustainable way. Here's how you set up CD for iOS apps to perform automated building, code signing and deployment to the App Store.

Written by 

In the last post, we covered how to set up a CI system to perform code validation and run tests on pull requests. This time, we'll go through step-by-step how you set up a CD system for your iOS apps. You're not required to read the first post, but do so if you want to assemble the complete CI/CD setup!

Whenever you're ready, let's launch your iOS apps to the App Store - automated using Travis CI and Fastlane!

1. Initial Travis CI setup 🔧

1.1.  Activate repo in Travis CI

So I'm just going to quote the initial setup from the Travis CI docs here:

  • Go to Travis-ci.com and Sign up with GitHub.
  • Accept the Authorization of Travis CI. You’ll be redirected to GitHub.
  • Click the green Activate button, and select the repositories you want to use with Travis CI.

1.2. GitHub access token for Travis CI

We need to provide Travis CI access to our GitHub repositories. Go to GitHub's Personal Access Token in Settings and generate a new access token. You can name it "travis" if you like. Then you need to go to the environment variables section of your repo in Travis CI. It should be under Settings. You then need to create an environment variable called GITHUB_API_TOKEN and copy/paste the access token into it.

2. Initial Fastlane setup 🔨

2.1. Install RubyGems

If you don't have Bundler installed already on your Mac, do it by running gem install bundler. If you don't have permissions, you need to have rbenv installed or sudo it if you're brave.

Navigate to the directory of your project and run bundle init. That will create a Gemfile for you. Here are the dependencies we need to install, so copy/paste this code into your newly created Gemfile:

source "https://rubygems.org"

gem "fastlane"
gem "xcode-install"

Now that we've specified the dependencies we need, install them by running bundle install.

2.2. Create an Apple ID without 2-Factor Authentication (2FA)

To deploy your apps using Fastlane, make sure you have an Apple ID without 2FA enabled. If not, you can create a new Apple ID here. This way, Travis CI won't be required to enter a 2FA code during deployment! Fastlane does provide an alternative where 2FA is supported, but it comes with some limitations. If you're interested, you can learn about it here.

Whenever you've created the Apple ID, add it as a user in your App Store Connect, and you should be ready to continue!

2.3. Fastlane init

To get started with Fastlane, navigate to your project using the terminal and run fastlane init and select "Manual setup" when prompted. That will create a fastlane folder as well as a Fastfile and an Appfile. Now, just open the Appfile and provide the parameters app_identier and apple_id:

app_identifier "com.appid.example"
apple_id "appleid@example.com"

You should use your newly created Apple ID without 2FA as apple_id.

3. Configure Fastlane match 🔏

Here's what will allow us to perform code signing from Travis CI easily! Fastlane match is a tool for generating all necessary certificates and provisioning profiles and storing them in a git repository. This way, anyone with access to the repo, such as Travis CI, will be able to fetch these credentials from the repo and perform code signing of our apps, so they can go on the App Store!

3.1. Create empty GitHub repository

This is required by Fastlane match. You can create a new GitHub repository here. Name it "ios-certificates" and make it private. That's it. Now you just let Fastlane match do the changes to it.

3.2. Fastlane match init

Open your terminal, navigate to your project and run fastlane match init. Follow the guide by providing the URL to your empty git repo. Afterward, open the newly created Matchfile in the fastlane folder. Provide the URL to the git repo you created:

git_url("https://github.com/andreaslydemann/ios-certificates.git")

This will let Fastlane match know where to fetch the certificates and profiles from next time.

3.3. Generate profiles and certificates

You can now run the following to generate new certificates and provisioning profiles:

  • First, run fastlane match development. That should generate the certificate and profile used by our debug builds.
  • Afterward, run fastlane match appstore to create the certificate and profile used by our release builds.

3.4. Select provisioning profiles in Xcode

The newly created certificates and profiles should now be possible to select inside our project. Open up Xcode and go to Signing & Capabilities. Here, select the appropriate provisioning profiles like this:

3.5. Fastlane environment variables

You need to create these two additional environment variables in Travis CI:

  • FASTLANE_PASSWORD is the password to your newly created Apple ID without 2FA.
  • MATCH_PASSWORD is the passphrase you created for your certificates/profiles during the Fastlane match setup.

4. Prepare pipeline ⏭

4.1. Travis CI configuration

Create a file in the root of your project and name it ".travis.yml". This is where we will define all the jobs that we want to run. Copy/paste the following code into the .travis.yml file:

# environment setup
language: swift
osx_image: xcode11.1

# caching of dependencies
cache:
  bundler: true
  directories:
    - Carthage

# branch safelist
branches:
  only:
    - master
    - /^release\/.*$/

# prepare dependency managers
before_install:
  - brew update
  - brew outdated carthage || brew upgrade carthage
  - gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)"

# install dependencies
install:
  - travis_wait carthage bootstrap --platform iOS --cache-builds
  - bundle install

# jobs and stages to run
jobs:
  include:
    - stage: Deploy
      name: Deploy to App Store
      script:
        - echo "machine github.com login $GITHUB_API_TOKEN" >> ~/.netrc
        - bundle exec fastlane deploy_to_app_store
      if: branch =~ /^release\/.*$/

The deployment stage is what's important here. Here are the key takeaways from it:

  • The deployment stage is only triggered on commits to branches that are prefixed with "release/". This means that we perform a deployment to the App Store by creating a remote branch called "release/x.x.x".
  • The deployment stage contains a script that will run two commands:
    • The command echo "machine github.com login $GITHUB_API_TOKEN" >> ~/.netrc gives Travis CI access to the private GitHub repo containing the necessary certificates and profiles.
    • The command bundle exec fastlane deploy_to_app_store triggers the lane in our Fastfile that is responsible for deploying the app to the App Store.

if you're not familiar with the concepts of pipelines in Travis CI, I recommend that you read about the job lifecycle and build stages from the Travis CI docs.

4.2. Branch protection

We also want our release branches to be protected so future commits into it must be submitted as pull requests. To do this, go to your project settings in GitHub and select "Branches". Here, you create a branch protection rule with the following settings:

 

5. Prepare deployment 🚀

5.1. Fastfile lanes

In your Fastfile, you'll need a lane for performing the deployment and one for installing certificates. You can copy/paste the following code into your Fastfile, and you should be good:

default_platform(:ios)

before_all do
  xcversion(version: "11.1")
end

setup_travis

def version_number
  if ENV["TRAVIS_BRANCH"].match?(/^release\/.*$/)
    return ENV["TRAVIS_BRANCH"].split("/").last
  else
    return get_version_number
  end
end

platform :ios do
  desc "Installs the certificates locally"
  private_lane :install_certificates do
    tmp_keychain = "temporary_keychain"

    create_keychain(
      name: tmp_keychain,
      password: tmp_keychain,
      default_keychain: true,
      timeout: 3600,
      unlock: true,
    )

    match(
      type: "appstore",
      keychain_name: tmp_keychain,
      keychain_password: tmp_keychain,
      readonly: true
    )
  end

  desc 'Deploys QCards to App Store'
  lane :deploy_to_app_store do
    install_certificates

    increment_version_number(version_number: version_number)
    increment_build_number(build_number: latest_testflight_build_number + 1)

    gym(
      scheme: "QCards",
      clean: true
    )

    deliver(
      submit_for_review: true,
      reject_if_possible: true,
      skip_screenshots: true,
      skip_metadata: true,
      force: true
    )
  end
end

The lane deploy_to_app_store is called from our deployment stage in travis.yml. This lane will, in turn, call the install_certificates lane which will fetch the code signing credentials using Fastlane match and store them in a temporary keychain. Afterward, we're ready to perform the code signing, whenever we build the app as an .ipa file using gym. Lastly, deliver will send the .ipa file to App Store Connect and submit it for review.

You need to manually prepare the app version with screenshots, release notes etc. in App Store Connect before you deploy your app with Fastlane - otherwise, Fastlane will report back that the app version wasn't found and the job will fail.

5.2. Enable Apple Generic versioning system

Since Fastlane will increment the build numbers for us, it's required that we change the versioning system to "Apple Generic". Look for versioning in the build settings and change it like this:

Results ✅

Now you should be all set with the setup. If you've come this far, you should be able to create a remote branch prefixed with "release/" and see the pipeline go to work. If everything is successful, you should see a screen similar to this in Travis CI.

There we go! This setup will save you a lot of time in the future. You can find the source code for the complete setup on GitHub.

Thanks for following along!

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.