We at Sixt wanted to share our approach to continuous integration and delivery for our iOS applications. Over the past year, it became crucial for our development process to change the way we do CI and CD.
First, let’s begin with a bit of history of our app and team and how that affected our decision for a CI provider. The first version of the Sixt app was released in 2008 and our team consisted of only one iOS developer. At that point, CI was not necessary and everything was done by hand. After growing the team to 3 members we decided to start with CI and use Jenkins. Back then it was the logical decision to make. Jenkins is free, we could host it on our own hardware and one build running at a time was fine. In 2016 we decided to move to TeamCity and use two build agents for our CI. Nowadays our iOS team consists of 15 developers building three public apps Sixt, DriveNow, and MyDriver as well as several internal apps. With this rapid growth in the last years, the need for a new CI provider arose. In this blog post, we want to share our current CI/CD pipeline.
It was clear from the beginning that we wanted to move over to a cloud-based CI provider, as the hosting and maintenance of our own hardware took too much time our developers could spend otherwise. The two most important metrics for us were startup time and concurrency. After comparing a couple of providers we decided to go with CircleCI as they seemed to fulfill our needs pretty closely and seemed mature enough. Follow this link to get started with CircleCI.
Now to the interesting part of how our current pipeline is actually looking. We are using fastlane for running all of our scripts and CircleCI is just executing different lanes depending on the branch it is triggered from. Here is a general overview of how the branches and lanes are connected:
Additionally to running the above build plan we are also running our whole test suite in a nightly build. This includes our unit as well as our UI tests.
In the next paragraphs, we will go through the initial setup of the project and code signing, creating the individual lanes and running everything on CirleCI.
Apple Developer Portal
First, we need to create our App identifier in the portal. Go to this page and fill out the necessary information. We need an app identifier for every build configuration. For this post our base bundle identifier is com.name.app so we have to create these app identifiers:
The dev and alpha app identifiers are created in our enterprise account. To allow deployment on any device.
As mentioned above we are going to use fastlane for running our scripts. A good practice is to use a Gemfile to lock dependencies. Create a Gemfile in the root of your project and add fastlane as a dependency.
Afterwards to install fastlane and initialize it you can run this
bundler exec fastlane init
It will ask for a couple of things first your Apple Developer Portal and iTunesConnect login and the team you want to use. We recommend creating a separate Apple Developer account for your CI usage if you are part of a team. Afterwards, an Appfile with your information should be created.
The next step is to configure our Appfile with the previously created bundle identifiers. It should look like this afterward
One of the biggest pain points in the past was code signing. We decided to go with fastlane match to manage our certificates and our provisioning profiles to reduce further overhead. A general overview of how match is working and why you should use it can be found here. The goal is that every developer is using the same certificates and profiles and the CI is regenerating them if necessary. The setup for fastlane match is quite easy if you have an empty developer portal. If you have already existing certificates, which can’t be revoked, a couple of manual steps are involved. Let’s go through the basic setup steps.
First, create a match file in the fastlane directory which specifies the repository and username.
Then we create a lane in our Fastfile to install the certificates from the repository. We will add an option to refresh the certificates for our CI here. By default, this lane is read-only and will only try to install them. The temporary keychain part is to make it run in CircleCI because otherwise manual interaction with the CLI would be necessary. Make sure to replace the git_branch, app_identifier and team_id parameters with your own.
You can run this lane and let fastlane create the certificates and profiles with this command:
bundle exec fastlane certificates refresh_certificates:true
It will ask you for a passphrase to encrypt the contents of the repository. It is important to write down that passphrase somewhere. In the future, this passphrase is the only thing other developers need to setup their certificates and profiles on their machine. Afterwards, you can see in the repository that match created the corresponding branches and pushed everything. Locally the certificates are added to the keychain and the provisioning profiles are installed. Now you need to change the project settings to use the corresponding bundle identifier and profiles. First, let’s create a new configuration for our alpha and name it Alpha.
Following you will need to change the bundle identifier depending on the configuration. Go to your App Target->Build Settings and search for “bundle identifier”. It should look similar to this:
The last step for configuring code signing is changing the signing settings. Go to the general tab, disable “Automatically manage singing” and choose the right profile for each configuration. You should see one eligible profile for each in the dropdown. Our end result looks like this:
The next step is creating lanes for running our different test suites. We have a lane for unit tests, UI tests and one that runs all of them. Remember to replace your workspace and scheme.
Build and Delivery
Last part of our Fastfile is the actual building and delivery of the application. We choose Crashlytics Beta to distribute our internals releases. We need two lanes one for the alpha and one for the production release which goes to Testflight. Again remember to replace the workspace and scheme parameters.
The complete Fastfile for our setup can be found here.
Now finally to running the whole thing on CircleCI. Create the “.cricleci” folder and inside it create a config.yml. All the jobs are pretty similar. Each one is checking out the code, restoring the dependency cache, installing the gems and saving the cache. The only difference is which lane is executed. Important here is to pass the options to use the temporary keychain and refresh the certificates. For example, the job for the alpha looks like this:
The only thing left is defining our workflows. They decide which branch runs which jobs, the context for our environment variables and schedule our nightly.
In CircleCI you have to setup the project. This will add a deploy key to the repository and Circle will start building on commits. Inside the setup project site just look for the “Start Building” button and hit it. The operating system and platform are configured by our config.yml.
Afterwards, it is important to go into the project settings and add a user deploy key to the project. This allows fastlane match to clone our certificates repository. Here again, we advise you to use a bot github account to add user ssh keys. The option to add a user key can be found here:
The last step is to add our environment variables and we are ready to start building. Because we are using the new context feature of CircleCI the environment variables are available to all our projects. This makes it so much easier to configure further projects down the line. For our setup the following variables are necessary:
- MATCH_ PASSWORD
MATCH_PASSWORD is the passphrase you used to encrypt the certificates repository and FASTLANE_PASSWORD is your login password for the Apple Developer Portal. The other two are pretty self-explanatory.
That’s it! Everything should be running now according to our build plan. Happy Building🎉🎉🎉
What to look out for
Due to the usage of a temporary keychain, you will see the following output every time you run the certificates lane. It is not an error and if your build fails, the cause is probably something different.