How to Build and Manually Sign an iOS App with Fastlane
Tags: fastlane, ios, codesign, manual
Published: 2020-05-11 04:48:34

---
title: How to Build and Manually Sign an iOS App with Fastlane
published: true
description: A real world scenario of building an iOS app with Fastlane
tags: fastlane, iOS, codesign, manual
---

This post will cover how to build and manually code sign an iOS app with Fastlane. Why would you need to do this? If you want/need to distribute an app to different Apple App Store accounts. For example if you have an Enterprise App Store account and want/need to sign the QA build of your app with the enterprise account to distribute internally and then sign the Release build with a normal/publicly available account to distribute to the public.

**TL;DR**
- How to build an iOS app using [Fastlane](https://fastlane.tools)
- How to manually code sign your iOS app using Fastlane 



### Prerequisites:
- Knowledge of iOS build process
- Knowledge of iOS code signing process
- Basic understanding of Fastlane
- Install Fastlane following their [recommended setup](https://docs.fastlane.tools/getting-started/ios/setup/)

### Assumptions:
- You are building your app on a macOS image that doesn't have your signing certificates or provisioning profiles installed.
- You have password encrypted signing certificate downloaded to directory on your build machine
- You have a mobile provisioning profile downloaded to a directory on your build machine  
- Automatic code signing is disabled in your Xcode project


For the record, I would recommend if you have a simple iOS project that you use [Fastlane match](https://docs.fastlane.tools/actions/match/) and Xcode automatic code signing. But sometimes in the real world there are restrictions and external reasons to have to do manual code signing, so we will go through the process of building and signing manually. Here is the process.


# Lets get started

Create a new Fastfile, this can be done by using Fastlane's cli tool
`fastlane init`

This creates a Fastlane directory with a Fastfile.

Your fastfile will look something like this:


```
default_platform(:ios)

platform :ios do
  desc "Description of what the lane does"
  lane :custom_lane do
    # add actions here: https://docs.fastlane.tools/actions
  end
end
```

I am going to rename the default lane that was created to "build" and I will add the command line options array `|options|` so we can have access to that later on.


```
platform :ios do
  desc "Build app"
  lane :build do |options|
    
  end
end
```

Now that the setup is done we can continue on. First we need to create a new keychain, second install our provisioning profiles, and lastly import the certificates.

Since we may have different profiles or certificates for different builds of our app, let's create a function called `setupCodeSigning`. The function will take 4 parameters- keychainPassword, certificatePassword, profilePath, and certificatePath.

**keychainPassword**: Made up password to set for your keychain you are creating on the Mac you are building your iOS app.

**certificatePassword**: The certificate password you used to encrypt your signing certificate

**profilePath**: Path to the provisioning profile (ie: ./dependencies/profile-release.mobileprovision) on your build machine

**certificatePath**: Path to the certificate (ie: ./dependencies/certificate.p12)

```
def setupCodeSigning(keychainPassword, certificatePassword, profilePath, certificatePath)
 
end

```

Now let's create the keychain. We can use the [create_keychain](https://docs.fastlane.tools/actions/create_keychain/) plugin.

Make sure you set a name for the keychain because we will reference it when importing the certificates. If your use case needs other parameters, refer to the create_keychain plugin documentation for more details.

```
def setupCodeSigning(keychainPassword, certificatePassword, profilePath, certificatePath)
  create_keychain(
    name: "CI",
    password: keychainPassword,
    default_keychain: true,
    unlock: true,
    timeout: 3600,
    lock_when_sleeps: false
  )
end
``` 

Now that we have the keychain created we need to install the provisioning profile(s). We can use the [install_provisioning_profile](http://docs.fastlane.tools/actions/install_provisioning_profile/#install_provisioning_profile) plugin. Be sure to pass the *profilePath* parameter as the *path* variable to the plugin.

```
def setupCodeSigning(keychainPassword, certificatePassword, profilePath, certificatePath)
  create_keychain(
    name: "CI",
    password: keychainPassword,
    default_keychain: true,
    unlock: true,
    timeout: 3600,
    lock_when_sleeps: false
  )
  install_provisioning_profile(path: profilePath)
end

```

Last, import the certificate(s) to the keychain by passing the certificate path, keychain name that we called our keychain, and the password we made up for the keychain.

The *setupCodeSigning* function should look like this:
```
def setupCodeSigning(keychainPassword, certificatePassword, profilePath, certificatePath)
  create_keychain(
    name: "CI",
    password: keychainPassword,
    default_keychain: true,
    unlock: true,
    timeout: 3600,
    lock_when_sleeps: false
  )
  install_provisioning_profile(path: profilePath)
  import_certificate(
    certificate_path: certificatePath,
    certificate_password: certificatePassword,
    keychain_name: "CI",
    keychain_password: keychainPassword
  )
end

```

Now that the hard part is over we can call the *setupCodeSigning* function in the build lane before we build the app.  The build lane will now look like this:


```
lane :build do |options|
    begin
      setupCodeSigning(ENV["MATCH_PASSWORD"], ENV["CERTIFICATE_PASSWORD"], './path-to-your-profile/your-profile.mobileprovision', './path-to-your-certificate/certificate.p12')
      
      cocoapods(clean_install: true, use_bundle_exec: false, error_callback: true)
      
      build_app(
        scheme: "your-scheme", 
        configuration: 'Release'
      )
      
      upload_to_testflight(
        username: options[:appStoreEmail],
        skip_waiting_for_build_processing: true,
        skip_submission: true)
        
    rescue => exception
      on_error(options[:slackUrl], "Build Failed", "#slack-channel", exception)
    end
  end
```

Optional: I like to add a "begin, rescue" block around the build to catch any errors and send them to a slack channel if anything fails for convenience. [slack plugin](http://docs.fastlane.tools/actions/slack/#slack)



Our complete fast file should look like this:

```
platform :ios do
  desc "Build app"
  lane :build do |options|
    begin
      setupCodeSigning(ENV["KEYCHAIN_PASSWORD"], ENV["CERTIFICATE_PASSWORD"], './path-to-your-profile/your-profile.mobileprovision', './path-to-your-certificate/certificate.p12')
      
      cocoapods(clean_install: true, use_bundle_exec: false, error_callback: true)
      
      build_app(
        scheme: "your-scheme", 
        configuration: 'Release'
      )
      
      upload_to_testflight(
        username: options[:appStoreEmail],
        skip_waiting_for_build_processing: true,
        skip_submission: true)

    rescue => exception
      on_error(options[:slackUrl], "Build Failed", "#slack-channel", exception)
    end
  end
end


def setupCodeSigning(keychainPassword, certificatePassword, profilePath, certificatePath)
  create_keychain(
    name: "CI",
    password: keychainPassword,
    default_keychain: true,
    unlock: true,
    timeout: 3600,
    lock_when_sleeps: false
  )
  install_provisioning_profile(path: profilePath)
  import_certificate(
    certificate_path: certificatePath,
    certificate_password: certificatePassword,
    keychain_name: "CI",
    keychain_password: keychainPassword
  )
end

def on_error(slackUrl, message, channel, exception)
  slack(
    slack_url: slackUrl,
    channel: channel,
    message: "iOS App :appleinc: " + message,
    success: false,
    payload: {},
    default_payloads: [],
    attachment_properties: { # Optional, lets you specify any other properties available for attachments in the slack API (see https://api.slack.com/docs/attachments).
      color: "#FC100D",
      fields: [
        {
          title: "Error",
          value: exception.to_s,
        },
      ]
    }
  )
   
  raise exception
end

```