Build the React Native Deployment Pipeline of Your Dreams in 1 Hour with Fastlane, Circleci, Codepush and Appcenter.
Félix Mézière15 min read
How is this tutorial different?
It’s a modus operandi.
This tutorial will make your React Native deployment pipeline completely operational in 1 hour for iOS and Android.
Other tutorials do a general presentation that lets you figure out the details or a zoom-in on a particular part of the pipeline or a deep-dive on every component which would take too much time to read and require you to join the bits together to make the deployment pipeline.
You can find such articles with this Google search.
In this tutorial, you won’t have to open Xcode nor Android Studio, not even once.
It is based on a generator
In this tutorial, you will make heavy use of the awesome generator-rn-toolbox
. Thanks @Theodo and @BAM ;)
This will maximise setup speed, reduce bug surface and ensure up-to-date standards when you create your deployment pipeline.
It is designed to be run on existing project (whether it’s a 3 years old project or a fresh react-native init awesomeProject
doesn’t matter)
The resulting deployment pipeline is super customisable
There isn’t too much magic. After this, you will have a skeleton with the time-consuming essentials setup but which is very easy to:
-
tweak to match your workflow: all the tools are in place, you can now re-order/tweak them (for example: CodePush to all versions higher than a constant, instead of CodePushing to currently deployed version only).
-
extend with extra features (like adding crash reporting, automating further what gets uploaded to the stores, end-to-end testing, hooks etc.).
What if you don’t understand the moving parts of this deployment pipeline (e.g. the infamous Apple code signing) or its day-to-day workflow?
Get more insights with our in-house detailed docs on how this works here. This is quite opinionated to fit our needs though.
If you still don’t understand something, Google that part specifically until you feel confident, then come back here :-)
This React Native deployment pipeline is meant for you to customise: it is a way for you to learn how all the tools can work together. I expect you to get your hands dirty after setting it up to tweak it to your exact needs.
The stack
- Environment, build and deployment track management: Fastlane 💖
- Continuous Integration: CircleCI 💪
- Staging app deployment: AppCenter Releases 🚀
- Prod app deployment: AppStore Connect and Google Play Console 📱
- Prod and Staging apps hot code push: AppCenter CodePush 🔫
Why a CI server instead of VS AppCenter for builds?
Among other problems, Appcenter Build is slow, not very customisable and doesn’t handle state-of-the-art shared code signing with match
.
One of its biggest issues is also that it’s hard to debug: the build tools and scripts used are different than the ones used to build locally and it’s not possible to ssh
into the distant build machine.
We love Appcenter Releases and CodePush though, that’s what we’ll use here for deployment hosting!
At Theodo, we chose CircleCI because it’s performant, widely used accross web and mobile (which makes sense since React Native devs often come from the ranks of Web devs) and is very customisable. Lots of CI tools are compatible with Fastlane so feel free to swap this one.
Workflow that you will get once completing this
- Everytime you push a branch, the
node
job that runs jest will be triggered. Use this as a hook for branches in Github. - If the branch is
staging
orproduction
, oncenode
deploys with CodePush and passes, twoios
andandroid
jobs will be triggered in parallel to do hard deployments (Hard deployment: do a full build of the app, native code included, and deploy it to Appcenter/the Stores. Soft deployment: use CodePush to update the Javascript code of the app live.) - When you want to change the version of the hard-deployed app, change the values in
fastlane/.env
- At the moment, CodePush is setup to target the app version defined in
fastlane/.env
- The best workflow with CodePush is obtained by doing pushes to all versions greater than a specified version, fixed in
.env
. This workflow is not implemented in the generator but feel free to implement it yourself as an exercise to test your understanding of the workflow :-)
Prerequisites
- Your React Native project running locally
- Admin rights on your project’s git repo
- An empty git repository for
match
or an existingmatch
repository linked to your Apple Dev Portal account (match
is used to make Apple code signing a breeze). - Admin rights on your Organization/account on Appcenter
- Admin rights on your Apple Developer Portal
- (iOS prod only) Admin rights on your Appstore Connect account
- (Android prod only) Admin rights on your Google Play Console account
- A CircleCI macOS plan
Steps
⚠️ FRIENDLY ADVICE ️
Remember to commit after each step
1. Create the staging
apps in Appcenter
- Get your project name (as in
react-native init <projectname>
) - In Appcenter, making sure you use your “Organization” if necessary (instead of your own account):
- Create staging apps
<projectname>-ios-staging
and<projectname>-android-staging
(configured for React Native and iOS/Android). - Create production apps
<projectname>-ios-production
and<projectname>-android-production
. - Make sure there is a single
Staging
CodePush deployment in the 2 staging apps found at Release -> CodePush -> Create standard deployments -> manage. Copy the two deployment keys somewhere. - Make sure there is a single
Production
CodePush deployment in the 2 production apps. Copy the 2 deployment keys somewhere. - (Only if you want to use Appcenter analytics and crash reporting) For each of the 4 apps, copy the Appcenter App Secret somewhere.
(We create different apps for staging and prod in appcenter mainly because appcenter analytics + crash reports are per-app).
2. Setup Fastlane
npm install --global yo
gem install bundler
yarn global add appcenter-cli generator-rn-toolbox
appcenter login
yo rn-toolbox:fastlane-setup
# First commit
Answers
- Please confirm the project name:
<Press Enter>
- Commit keystore files?:
n
- Would you like to use an encrypted archive to store secret files and keys?:
Y
- Would you like to use a deployment script?:
Y
- Overwrite :
<Press Enter>
3. Create staging
environment
yo rn-toolbox:fastlane-env
# Second Commit
Answers
-
Please confirm the project name:
<Press Enter>
-
The name for this new environment (lowercase, no space):
staging
-
The name of your repository Git branch for the environment just set:
staging
-
The name of the company which will be
publishing this application:
<your company>
-
Which platform will you use for React Native deployment?:
AppCenter
-
The app name for this environment:
<Your App Staging>
-
The App Id for this environment:
<your>.<company>.<projectname>.staging
-
The type of certificate you will be using:
Adhoc
-
Your git repo for match:
git@github.com:<TeamOrg>/<match-repo>.git
(<- this is your match repo mentioned earlier) -
The branch you want to use for match:
<Press Enter unless using a different branch than master for match>
-
The developer.apple.com team id for the certificates:
XXXXXXXX
(<- find this in the url when on “Account” section of your Apple Dev Portal) -
Your apple id (should be admin on the Apple Developer Portal):
you@example.com>
-
Your keystore password:
<Press Enter>
-
Will you deploy with Appcenter CodePush on this environment? (y/n):
Y
-
A valid App Center Username:
<the-client's-Organization>
(<- find in the url when on the app in the AppCenter interface. It’s the username for a person or the name of the Organization for an Organization) -
A valid App Center API token:
XXXXXXXXXXXXXXXXXXXXXXXXXX
(<- make sure the token only has access to your Org’s apps. Google how to get it if needed). -
The iOS project id on AppCenter for this environment, should be different than Android and not contain spaces:
<iOS staging Appcenter app name created in step 1.>
-
The Android project id on AppCenter, should be different than iOS and not contain spaces:
<Android staging Appcenter app name created in step 1.>
-
Your iOS CodePush deployment key:
<iOS Staging CodePush deployment key>
-
Your Android CodePush deployment key:
<Android Staging CodePush deployment key>
-
Your iOS CodePush deployment name:
Staging
-
Your Android CodePush deployment name:
Staging
-
Will you be using Appcenter Analytics and Crash reporting on this environment?:
<Up to you :). You are probably better off using Firebase and Sentry for this stuff, but it will require more work.>
-
(If yes above) Your AppCenter app secret for the iOS App:
<staging iOS app secret>
-
(If yes above) Your AppCenter app secret for the Android App:
<staging Android app secret>
-
(After some npm installation…) Should fastlane modify the Gemfile at path ‘xxx’ for you? (y/n):
Y
4. Create iOS staging deployment certificates and profiles
This creates all the certificates and profiles required for your app in the Apple Dev Portal and installs them in your local environment.
bundle exec fastlane ios setup --env=staging
You will be asked twice for a match
passphrase for the repo. If you are using an existing match
repository, ask for the passhphrase, otherwise define it here and note it somewhere: this is the password to share with teammates so that they can get the iOS code signing identities (you shouldn’t need this if you setup automatic React Native deployment with CircleCI, in which case you will enter the match
passphrase once in the CI server).
5. (If needed) Open src/environment/index.staging.js and update it with all required js
environment variables for the staging
environment. Commit.
6. Wrap your app with CodePush
In order for CodePush to work, it needs to wrap your app as explained here.
import codePush from "react-native-code-push";
import { AppRegistry } from "react-native";
import { ENV } from "./src/environment";
import AppContainer from "./App";
// Below is an example of how CodePush behaviour can be made different in production: in this case, update will be installed to prod devices only after user has stopped using it for 2 minutes.
const codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.ON_NEXT_SUSPEND,
minimumBackgroundDuration: ENV === "production" ? 120 : 0
};
const CodePushedApp = codePush(codePushOptions)(AppContainer);
AppRegistry.registerComponent("yourApp", () => CodePushedApp);
// Third Commit
7. Pack the Staging secrets in an encrypted archive
yarn pack-secrets -e staging
# Fourth Commit
⚠️ Make sure that you commit the resulting archive and note down the passphrase somewhere
This archive contains all the staging env secret passwords and files. If you lose it, you won’t be able to deploy the app anymore. This archive is useful to share the secrets between developers without having to send through email/usb etc.
⚠️ In the future, whenever adding more secret files (e.g. Firebase keys), make sure that you run the following steps:
- run
yarn unpack-secrets -e staging -p <your passphrase>
to get the latest version of all secrets locally - add the secret file to
SECRETS_TO_PACK
inpack-secrets.sh
- run
yarn pack-secrets.sh -e staging
and commit the resulting archive: other devs and the CI server will now have access to this secret file.
It would be cool to replace this encrypted archive with the use of transcrypt, PRs welcome! :)
8. Hard-Deploy Staging
yarn deploy -t hard -e staging
=> You can now download the staging
app on your phone at https://install.appcenter.ms
9. Soft-Deploy Staging (CodePush)
yarn deploy -e staging
=> You should receive the update you CodePushed directly on your phone without having to manually update your app from Appcenter.
10. Create production
environment
yo rn-toolbox:fastlane-env
# Sixth Commit
Answers
-
Please confirm the project name:
<Press Enter>
-
The name for this new environment (lowercase, no space):
production
-
The name of your repository Git branch for the environment just set:
production
-
The name of the company which will be
publishing this application:
<your company>
-
Which platform will you use for React Native deployment?:
AppStore
-
The App Id for this environment:
<your>.<company>.<projectname>.production
-
The type of certificate you will be using:
App Store
-
Your git repo for match:
git@github.com:<TeamOrg>/<match-repo>.git
(<- same as for Staging) -
The branch you want to use for match:
<Press Enter>
-
The developer.apple.com team id for the certificates:
XXXXXXXX
(<- find this in the url when on “Account” section of your Apple Dev Portal) -
The appstoreconnect.apple.com team name:
<AppStore Connect team name>
-
An AppstoreConnect Apple Id (good practice: ID should have “developer” access - only allowed to upload builds):
<someBotDevAccount@you.com>
(<- possibly not the same apple id as used for the apple dev portal, since this one should only have dev access to Appstoreconnect and will be used by CI: you don’t want it to have admin access to the apple dev portal) -
Your apple id (should be admin on the Apple Developer Portal):
you@example.com
-
Your keystore password:
<Press Enter>
-
A Google Play JSON Key relative path:
<relative/path/to/dsadsadasdad.json>
(<- follow tutorial here to get this with a service account. /!\ Make sure this file is in the repo and is gitignored) -
Will you deploy with Appcenter CodePush on this environment? (y/n):
Y
-
A valid App Center Username:
<your-Organization>
(<- find in the url when on the app in the AppCenter interface. It’s the username for a person or the name of the Organization for an Organization) -
A valid App Center API token:
XXXXXXXXXXXXXXXXXXXXXXXXXX
(<- make sure the token only has access to your Org’s apps. Google how to get it if needed). -
The iOS project id on AppCenter for this environment, should be different than Android and not contain spaces:
<iOS production Appcenter app name created in step 1.>
-
The Android project id on AppCenter, should be different than iOS and not contain spaces:
<Android production Appcenter app name created in step 1.>
-
Your iOS CodePush deployment key:
<iOS Production CodePush deployment key>
-
Your Android CodePush deployment key:
<Android Production CodePush deployment key>
-
Your iOS CodePush deployment name:
Production
-
Your Android CodePush deployment name:
Production
-
Will you be using Appcenter Analytics and Crash reporting on this environment?:
<Up to you :). You are probably better of using Firebase and Sentry for this stuff, but it will require more work.>
-
(If yes above) Your AppCenter app secret for the iOS App:
<production iOS app secret>
-
(If yes above) Your AppCenter app secret for the Android App:
<production Android app secret>
-
(After some npm installation…) Should fastlane modify the Gemfile at path ‘xxx’ for you? (y/n):
Y
11. Setup iOS production deployment certificates
bundle exec fastlane ios setup --env=production
12. Pack the Production secrets in a encrypted archive
- Make sure the Google API JSON key file added in step 9. is in
.gitignore
- Add this file to the production secrets by appending its relative path to the
SECRETS_TO_PACK
string inpack-secrets.sh
and running:
yarn pack-secrets -e production
# Sixth Commit
⚠️ Make sure that you commit the resulting archive and note down the passphrase somewhere. Passphrase should be different than for staging
to allow different access rights.
This archive contains all the production env secret passwords and files. If you lose it, you won’t be able to deploy the app anymore.
⚠️ In the future, whenever adding more secret files (e.g. Firebase keys), make sure that you run the same steps as for staging above (replace staging
with production)
⚠️ If you lose the keystore file present in this archive, you will not be able to deploy the Android app ever again and will have to create a new app in the Google Play Store.
13. (If needed) Open src/environment/index.production.js and update it with all required js
environment variables for the production
environment. Commit.
14. Create the production apps in the portals
-
In Appstore Connect, create your app with the correct bundle id
<your>.<client>.<projectname>.production
-
In Google Play Console:
-
create your app
-
locally, do an Android hard deploy and see it fail:
yarn deploy -t hard -e production -o android
- get the apk located at
android/app/build/outputs/apk/release/app-release.apk
and upload it to the Internal track. Accept Google Code Signing prompt if asked. Click Save and don’t specify anything else: your app is now linked to the correct bundle id and keystores.
15. Make sure you have an app icon
Otherwise AppStore Connect will not accept your app. You need a 192 x 192 png.
Thanks generator-rn-toolbox
again! :)
yo rn-toolbox:assets --icon relative/path/to/your/icon.png
# Seventh Commit
16. Hard-Deploy Production
yarn deploy -t hard -e production
=> You should now be able to see your app’s binaries in both Google Play Store Internal track (“add from library”) and AppStore Connect’s TestFlight.
17. Soft-Deploy Production (CodePush)
yarn deploy -e production
=> You should receive the update you CodePushed directly on your phone without having to update their app.
18. Now setup automated React Native deployment with CircleCI so that no-one has to use these commands anymore :)
- Enter CircleCI and select your team/account on the top-left corner
- Click Add Projects -> iOS -> Setup project-> Start building (on your project)
- Locally, run
yo rn-toolbox:circleci
: - Please confirm the react-native project name (as in react-native-init ):
<Press enter>
- Path to the React Native project relative to the root of the repository (no trailing slash):
<Press enter unless weird config/monorepo in which case put the path to React Native folder>
- Go to CircleCI’s Environment Variables settings. Add the following variables as specified by the generator:
FL_APPCENTER_API_TOKEN
: same appcenter API Token as infastlane/.env.***.secret
filesMATCH_PASSWORD
: the password used for the match repo when doing the first deploymentFASTLANE_PASSWORD
: the password for the AppStoreConnect Apple IDPRODUCTION_SECRETS_PASSPHRASE
: the production secrets archive passwordSTAGING_SECRETS_PASSPHRASE
: the staging secrets archive password- …. any other secrets passphrases if you added other environments
- Go to Checkout SSH keys section and add a github account that has access to the match repo (ideally read access and only to this repo)
- Push your code.
staging
andproduction
branches should run additional deployment steps! - Setup CircleCI hooks on Github to only allow branch merge on Circle builds succeeding.
Appendix: Some nice tweaks to do, good exercises for you to understand the moving parts of the stack
- Change the parameters of
pilot
inFastfile
andinfo.plist
so that the upload to test flight is directly distributed to external testers. - By changing
Fastfile
, Make the staging app be deployed to a newTesters
distribution group in Appcenter instead ofCollaborators
- Create a new variable in
.env
(called e.g.MINIMUM_CODEPUSH_VERSION
) and changeFastfile
accordingly so that CodePush targets all versions greater than or equal to the value of this variable instead of targetting onlyANDROID_VERSION_NAME
andIOS_VERSION
.