We have a few apps now, but one of them isn't really used by anyone other than developers. This is our React Native host app. We built our React Native components as a library to be consumed by our other apps. Our development environment for these components is a unique app that acts as a host for the React Native components. It's effectively a long tableview.
This app is often updated for developers, but never deployed to beta users inside Artsy. So I automated it. Using Travis CI and fastlane. This post covers how I got that set up.

As the JavaScript is continuously deployed, the native side of the app rarely gets a deploy. In order to ensure an up-to-date version of the app, I used the scheduler now available in Travis CI, and Circle CI. This is a perfect use-case for one-off tasks like uploading an app to Apple's Testflight on a weekly basis.
I wanted this to exist outside of our current CI environment for two reasons:
- Our CI is already using AppHub to deploy the JavaScript parts of our React Native on a per-commit basis. It's complicated enough as it is, without adding a lot more process.
- Our CI is currently running on Linux boxes, and so everything is fast and stable. Deploying using the main repo would force us to use macOS which would slow down our processes.
The downside of this choice is that the process of uploading is not inside the main repo, and can go out of sync with the main app.
Setup
I created a new repo, and added the usual LICENSE and README, then started working on a PR that added the initial support for CI to run. Here are the general steps I needed to make work:
- Downloading and setting up the application.
- Ensuring signing will work.
- Creating the build and shipping it to Testflight.
- Notifications that it passed or succeeded.
Finally I needed to document the process, which is what you're reading.
Downloading and setting up the application
My initial thoughts were to use a submodule, but that option provides little advantage over cloning the repo itself so it's done inline. Our dependencies for the app live in Rubygems (fastlane/CocoaPods), NPM (React Native) and CocoaPods (Artsy Mobile code), so I use the before_install
and before_script
section of the .travis.yml
to set up our dependencies:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Note the - bundle update
. As fastlane works against unofficial iTunes connect which is always changing, it's safer to always use the most recent release.
Ensuring signing will work.
This one is a bit tricker, luckily I've already set up one of our apps to use fastlane match and I can re-use that infrastructure. As it is a private repo, Travis did not have access to clone the repo. I fixed this by creating an access token for a user with read-only access to our match-codesigning repo, then exposed this as a private environment variable in CI which the Matchfile uses. E.g.
1 2 3 4 |
|
This is one of the highlights on fastlane's choice in building a DSL that which sits above a real programming language, you give users a lot of flexibility.
Next up, I added a fastlane lane for code signing, and keychain setup. This just calls two setup functions.
1 2 3 4 5 |
|
Creating the build and shipping it to Testflight
This is handled by fastlane gym at the start of the main lane.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
It uses a scheme for deploys, which prioritises using AppHub over a local React Native server. Gym handles a lot of CLI ugliness for us, and works well.
Sending the app to Testflight involves a a few lines:
1 2 3 4 5 6 |
|
This lets the deploy process figure out what the latest release version is, and how many builds have shipped for that version. Then those can be used to set the build version and create a tag associated with it.
fastlane pilot is used to send off the compiled build to Testflight.
Keeping track of deploys
I don't know when we'll need it today, but it's always good to be able to go back and see what code lines up to every release. To do this I have a few lines of Ruby that creates a tag inside the original Emission repo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Notifications that it passed or succeeded.
This was easy, I created a new slack inbound web-hook and added that as an environment variable. Then when a build passes we post a notification that there is a new version for everyone in Slack, if the lane fails then it will also post to slack. To ensure we keep on top of it, during development this was commented out.
1 2 3 4 5 6 |
|
That wraps up setting up the CI. Once you've confirmed everything has worked, you can add the scheduler inside Travis and expect to see a slack notification in a week.
By the end of the process, our Fastfile
looked like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
|
Automatically deploying is a good pattern for encouraging more deploys of an app which has only been deployed once. It's a pattern we could also move to in some of our other apps too, if it feels good. If you're interested in if something has changed since this post was authored, the repo is here: https://github.com/artsy/emission-nebula so you can read out the Fastfile and we'll answer questions you have inside GitHub issues on it.
The most annoying part about building deployment changes are that an iteration takes ~20 minutes, so make sure you also have another (easily interrupted) task to do at the same time.
The second most annoying is that it took months to eventually get this right - so I owe Felix Krause a big thanks for sitting down and pairing with me, we figuring out that xcodebuild
can create empty archive issues when you run projects that have the xcproject/xcworkspace a few levels deep.