We hope that this step-by-step guide will be your one-stop destination for everything CI/CD on react native. So let us get started.
Android
Pre-requisite
Setup fastlane
Note: We're not deviating from the official docs. Everything mentioned below can be found there, the only difference being the one below is stripped down to bare essentials.
Go into Android folder and then run the following
Setting up fastlane
You will be taken to a guided interface. You'll be asked the following questions
- Package name for your application
- Path to your json secret file [Press enter here, we can set it up later]
- If you plan on uploading info to Google Play via fastlane [Press 'n']
On completing this, fastlane will create the following files in android
folder
- Gemfile
- Gemfile.lock
- fastlane/Appfile (which defines configuration information that is global to your app)
- fastlane/Fastfile (which defines the "lanes" that drive the behavior of fastlane)
Now remember, we simply pressed enter for step 2, we need to get back to it now. Fastlane leverages google's service account to automate the deployment process. Consider this as a permanent developer account for this particular project. This account will be granted access to push build to the stores created by fastlane to the store/ internal test. Now to setup this service account you can follow this link, scroll down to the supply section. Do know that you need account owner access to do the above… so you need request client to do it for you if the account wasn’t created by you.
On completing those steps you'll have .json file at the end of it. Move it to android/app
folder and mention it in the Appfile. You can do this by adding the these two lines in Appfile
Appfile
Now you have the basic setup complete. Before we head onto setting up lanes and we need to sort out flavors. Flavors!? What are they?
Well, flavors allows you to build variants of an app seamlessly. A simple example is staging and production environment. Wouldn't it be nice if we had a dedicated app for each environment with custom names and such. Flavors does just that.
It’s a minor configuration change in build.gradle so strap in!
Flavors
Prerequisite:
- Create another app with a different bundle id under the same account. Lets say our app is name cicdrn with bundle id
com.cicdrn
. Now I'll be creating another app named cicdrn-staging with bundle idcom.cicdrn.staging
.
For this setup, we're gonna have only two environments
- production -> com.cicdrn
- staging -> com.cicdrn.staging
Paste the following lines in android/app/build.gradle
under android property
That's it!! If you want different names based on the flavors that can also be done. For staging create a file named "strings.xml" in the path android/app/src/staging/res/values
Notice the "src/staging" → staging
here is the flavor name declared under product flavors in the above configuration. So if you decide to name it differently make sure make the changes to the folder name as well.
strings.xml
Similarly do the same for production, following the path
android/app/src/production/res/values/strings.xml
Once you have everything setup, you'll no longer be able to build an apk or bundle without specifying the flavors, so here are some handy scripts you can save in your package.json.
Now the whole point to this flavors is to isolate staging and production environment. So you would want to attach relevant envs to each variant. This can be done using react-native-config.
Setting up React-Native-Config and ENV setup for flavors
Its quite straight forward, you can follow the official doc here. Just make sure you don't skip the extra step required for android, which is to add the following script in android/app/build.gradle
Once you have done add the following snippet at the very top of android/app/build.gradle
As you can see we are maintaining the env in two separate files. We don't use a single .env, we don't do that here. Now all the env are setup and configured with flavors, you can give it test run by running the following commands. This should install two apps on your device/emulator running the relevant envs.
Production:
Staging:
You can save them as scripts in your package.json like this
You should be able to see two of the same app with the custom names provided in relevant strings.xml running relevant envs. If you do see them, Great!! You have made it half-way!! else do go through it once more and see if you have missed anything.
Now lets continue our journey to the most anticipated section! Setting up the much awaited "lanes".
Setting up lanes
So we're going to setup two lanes called staging and production. Both the lanes are identical to each other with the only difference being the package name and env. The explanations are given in the form of comments wherever necessary. But before we go over the lane configures here are some handy scripts you can save in your package.json to make your life easier.
You can also pass arguments to these lanes by appending the args to the scripts above like this
Now lets dive into lane configuration
We have the lanes setup, it would be incomplete without github actions. Combination of these two will give you a seamless CICD experience.
Configuring github actions
I believe the github worflow here is quite straightforward. We have made the workflow a manual dispatch since we'll be pushing the changes back to branch after updating version and build number during fastlane execution. If we had set it for push trigger then yes this will go into an infinite loop. Hence we decided to stick with manual dispatch and this also comes with the additional flexibility of the choosing bump type via the dropdown.
Reference
Package.json
Github secrets variables
ANDROID_KEYSTORE
ANDROID_SERVICE_KEY
IOS_KEYSTORE
PRODUCTION_SECRET_KEY
STAGING_SECRET_KEY
IOS
Note: We're not deviating from the official docs. Everything mentioned below can be found there, the only difference being the one below is stripped down to bare essentials.
Switch to the IOS folder
Setup
You will be taken to a guided interface. You'll be asked the following questions
- 📸 Automate screenshots
- 👩✈️ Automate beta distribution to TestFlight
- 🚀 Automate App Store distribution
- 🛠 Manual setup - manually setup your project to automate your tasks
Choose step 4 manual setup and follow the instructions mentioned after. This will create the following files in your IOS folder
- Gemfile
- Gemfile.lock
- fastlane/Appfile (which defines configuration information that is global to your app)
- fastlane/Fastfile (which defines the "lanes" that drive the behavior of fastlane)
In your Appfile you need to mention the bundle id and team id. You can paste the script below and modify it according to your project
Just like in Android, we'll create two flavors on ios as well to isolate environments. Next section we'll go into depth on how to make this happen.
Before we start with flavors, just like how Android had service accounts to handle the upload, IOS also follows a similar mechanism. An App Connect Api Key is required, to handle the upload to Testflight, so do following this doc to set it up. Make sure to keep note of the keychain name and keychain password as this will be saved in the .env file. Also, you'll be able to download the .p8 file only once, so do keep that mind. In case, if you mess up this step, no problem, all you have to do is delete the key and create it once again.
Now lets move on to creating flavors or schemes as Apple calls it.
Flavors/Schemes
Prerequisite:
Create another app with a different bundle id under the same account. Lets say our app is name cicdrn with bundle id com.cicdrn
. Now I'll be creating another app named cicdrn-staging with bundle id com.cicdrn.staging
.
For this setup, we're gonna have only two environments
production -> com.cicdrn staging -> com.cicdrn.staging
Step 1
Open the project workspace. Select the project in the left pane (navigation area). Now to the right, the details of the app shows up. Under project, select the app name. Under info -> configuration, you'll have two options debug and release. Duplicate debug and name the config as stagingDebug similarly for release as stagingRelease.
Step 2
Now select the app under target and duplicate. In the dialog -> select duplicate only.
Step 3
Rename this new target to something appropriate. Now in the navigation area, expand the project. You'll find some new info.plist created. Rename that info to something appropriate. Also, update the display name as well. Also, update the bundle identifier for this new target (in my case com.cicdrn.staging
.). If you are not able to edit bundle identifier input instead it says
"multiple values". Then you can go to signing and capabilities and update the bundle identifier for both debug and release.
Note: Let's call this new target as STG target moving forward for simplicity. As for the target that already existed, we'll call it the PRD target.
Step 4
Make sure the new info is linked to the STG target. You can go to build settings tab, search for pack. Scroll down a little, till you find package -> info file and verify that stagingDebug and stagingRelease point to staging info plist. Now that's all wired up lets fix up scheme as well
Step 5
Now click the scheme/flavor menu, you'll notice that new scheme would have been created for STG target. We're not gonna use that, so go to manage scheme, find the STG Target scheme and press "-" button at the bottom to delete this. The reason for deleting this is that the name doesn't update when you update the target and info-plist name in step 3, so we'll create a new one but this time the names will be synced up properly. Scroll up till you find PRD Target scheme, right click and duplicate it and ideally you should be able to see a dialog prompt with names synced up properly. Click ok and now the scheme will be created.
Step 6
Now the schemes are created, remember we created two configuration called stagingDebug and stagingRelease in the beginning, we need to connect them to this STG target scheme. So select the staging scheme > edit scheme. This will open up a dialog, to the left you'll see options
- Build
- Run
- Test
- Profile
- Analyze
- Archive
Each having debug or release as configuration indicated under the name itself. Visit all the options except Build and select the appropriate conf under Build configuration dropdown. Debug should be replaced with stagingDebug and release should be replaced by stagingRelease.
Step 7
Now the final step, updating the pod file so that it can pick up the newly created target. You can do this by adding the following line as shown in the image below. You would have noticed that the tests target is commented out because I deleted it already from xcode in the beginning since we don’t need it anymore.
Do run the app from the xcode and verify if the schemes are working as expected. It should create the apps one for each scheme, make sure you select the right scheme from scheme menu before installing it.
Setting up envs with the newly created scheme
Now the schemes are good to go, we need to hook up envs to the schemes as well, and its actually quite simple. Select one of the schemes (in this case prod) from the scheme menu and select edit scheme. Expand "Build" on the left pane -> select prebuild. Here you need to add two scripts. Select the "+" button once. A scripting area would appear, do add the following script there.
Make sure the dropdown above the script area points to the production scheme. And make sure it points to the right env. In this case, it was .env.production
Now click the "+" button again, so we can add the second script. Again make sure the right option is selected above the script area
When you build the application, this will create a file named tmp.xcconfig with all the envs. Do not commit this file to git.
Now, Repeat this for staging and make sure the right env is specified meaning replace “.env.production” with “.env.staging”. Also, make sure the staging scheme is selected before selecting the edit scheme option.
That's it!! Now the envs are all hooked up properly we can proceed with setting up lanes.
Setting up lanes
Before going forward to set up lanes, there is one minor thing we need to do, to handle signing certificates and things. Fastlane provides something called match which makes it super easy to set up code signing. Do set up match by running
Choose git > and mention a repo to store all the certification. It's recommended you create a separate repo for each project to store the certificates to. This will create a match file in the ios/fastlane folder. We will remove this file later because we'll be manually mentioning the config details via "match" action in the lane setup. The next step would be to run
You'll be asked to set a passphrase. Keep a note of this since we'll be needing it later in lane setup. On finishing this step, you'll notice the repo that was mentioned in the setup will be populated with the certificates. Now when running the lanes, the lanes would require read access to github to use the certificates for the signing step, so do create personal token on github and save it in github secrets. Now we have everything set for handling certificate signing. Lets go onto lanes setup.
We'll be setting up two lanes called staging and production. Both the lanes are identical to each other with the only difference being the package name and env. The explanations are given in the form of comments wherever necessary.
Now we have the lanes set up, it would be incomplete without Github actions. A combination of these two will give you a seamless CICD experience
Configuring GitHub actions
I believe the GitHub workflow here is quite straightforward. We have made workflow a manual dispatch since we'll be pushing the changes back to the branch after updating the version and build number during Fastlane execution. If we had set it for push trigger then yes this will go into an infinite loop. Hence we decided to stick with manual dispatch and this also comes with the additional flexibility of the choosing bump type.
Reference
Package.json
Github secrets variables
ANDROID_KEYSTORE
ANDROID_SERVICE_KEY
IOS_KEYSTORE
PRODUCTION_SECRET_KEY
STAGING_SECRET_KEY
GIT_AUTHORIZATION
How do we deploy all of this
Since we use workflow
dispatch triggers, this can be manually triggered under the Actions tab in Github.
As mentioned before, in our case, the builds are released as far as test flight and internal test automatically. The actual push to production happens manually after QA and the client’s approval.