Scaling Android Deployment with Bitbucket Pipelines and Fastlane


This is a guest post from Ivan Rigovsky


Traveler.today makes local travel guides for tourists and self-explorers. We create a different app for each location because each of our apps is made for a different partner/customer. 

As our business started to grow, we needed to create multiple new apps per week and it started to take too much time away from the whole team. It interrupted our development work as we would spend most of our time supporting releases. For each new app, it took several hours to configure the server, build and deploy a new application, update the database, fill metadata, etc.

The purpose of this post is to share our experience in automating deployment of dozens of Android applications based on the same code base. This process resulted in reducing our deployment time per app from several hours to several minutes.

What we did

We decided to automate the most time consuming parts of the process

  • building and deploying servers
  • refreshing the application database
  • uploading application metadata (screens, description, changelog)
  • building and deploying applications

Here's how we did it using Bitbucket, Git and Fastlane. 

Git tags and our server deploy process

We use the Git-flow methodology. We have several test and production environments for each customer. To reduce manual work, we decided to deploy applications using tags with versions. The below images show you some sample branch names.

Git branches with tags

Then we configured a bitbucket route for each tag version to its environment. For each tag, we build a server instance, copy it, send it to DigitalOcean and then notify our Slack channel about success or failure. 

For the test instance, we have a continuous integration process with fast deploy and start. For production, we build and manually deploy. We provide a commit ID, version and application name into the build script to understand what version was deployed. We use the variables BITBUCKET_TAG and BITBUCKET_COMMIT to understand which version was deployed for each instance. 

Here is some detail on the process.

First, define access variables on the account variable page:

Next, build the CI process using Bitbucket Pipelines. Here is our Pipelines code in the test environment. 

pipelines:
  tags:
	test-*:
	 - step:
	     name: Build and deploy
	     caches:
	       - maven
	     script:
	       - mvn clean install -DskipTests=true -Ptest -Dapp.name=test -Dapp.version=$BITBUCKET_TAG -Dapp.commit=$BITBUCKET_COMMIT
	       - scp -v -P $SSH_PORT city-webapp/target/test.war $SSH_LOGIN@$SSH_IP:$DEPLOY_FOLDER
	       - scp -v -P $SSH_PORT deploy/deploy_test.sh $SSH_LOGIN@$SSH_IP:$DEPLOY_FOLDER
       	       - ssh -p $SSH_PORT $SSH_LOGIN@$SSH_IP 'sh $DEPLOY_FOLDER/deploy_test.sh'

Finally, for notifications, we use Slack and Bitbucket WebHooks on our #devops channel. Here's how you do it. Go to your repo settings page and click on webhooks.

Add a new webhook for each 'Push' and 'Build status updated' events.

Here is how notifications look.

With this new process, building and deploying microservices for each application only takes a few minutes, including caching and copying files.

Using Fastlane to Build and Deploy Mobile Apps

You can read about how to set up Fastlane in this blog

In this section, I will describe our build and deploy process. We need to build and deploy several apps for the same code base, but with a different database and metadata like images, descriptions, titles, and changelogs. 

Here's the command to build the app name with the current profile.

fastlane build app:test

Then we start the process of updating the database, building and deploying the application. Step by step.

platform :android do
  desc "Build apps: 'sudo fastlane build app:altay' for instance"
  lane :build do |options|
    update_data(app: options[:app])    #1. update database
    build_android(app: options[:app])  #2. build from src
   deploy(app: options[:app])              #3. deploy app
end

Step 1: Update database

We would often forget to update the database or to synchronize API versions. Now, we just run this script to get access to the API:

sudo fastlane update_data app:altay

We have an external API on the server with fresh data in JSON format. Because all new users must have offline access to the data (travelers often don’t have internet), we will provide it for each new release. Then save the downloaded file to the mobile app directory before the build starts. The build script in the next step will fetch this data from file and save into the database.

desc "Update database"
lane :update_data do |options|
    objects = download(url: $API_URL + options[:app])
    objectFile = "../mobile/src/" + options[:app] + "/assets/json/database.json"
    File.open(objectFile, 'w') { |file| file.write(database.to_json) }
end

Step 2: Build

To start build, provide key variables into the script: $PATH_TO_KEY, $STORE_PASSWORD, $KEY_ALIAS, $KEY_PASSWORD. 

platform :android do
  desc "Build android app from src"
  lane :build_android do |options|
    gradle(
      task: "assemble",
      flavor: options[:app],
      build_type: "Release",
      print_command: false,
      properties: {
        "android.injected.signing.store.file" => $PATH_TO_KEY,
        "android.injected.signing.store.password" => $STORE_PASSWORD,
        "android.injected.signing.key.alias" => $KEY_ALIAS,
        "android.injected.signing.key.password" => $KEY_PASSWORD,
      }
  )
  end

In the result we have new apk file, and now we are ready for deploy. 

Step 3: Deploy

Manually deploying an application in the App Store and Google Play is a challenge when we want to optimize screenshots, video, or keywords.  To deploy the apk, set up the folder with metadata from the source and start the supply Fastlane task. You can also put all these variables into Bitbucket.

desc "Deploy a new version to the Google Play.
  lane :deploy do |options|
    supply(
      track: options[:track],
      package_name: "today.traveler." + options[:app],
      metadata_path: "$APP_PATH/fastlane/" + options[:app] + "_metadata",
      apk: "mobile/build/outputs/apk/" + options[:app] + "/release/guide-" + options[:app] + ".apk"
    )
  end

The next step for us is to add CI for iOS applications and build a separate server architecture for more micro-services to improve deployment flexibility. 

Conclusion

Now the entire application and server deploy process only takes half an hour with all tests and checks. Before automation, it had taken about 1 hour for each app or each server deploy. For 10 apps we've saved an entire day in time. For 50 apps, we save an entire week! 

Here's how our new process helped us: 

  • no more manually uploading data, database updates and deploys.
  • no more human errors in the workflow.
  • reduced onboarding time for new team members.
  • and now we always  know the version of the product and when it was delivered without a lot of documentation. 

We can add any number of new applications to our deployment process and it won't decrease our speed. That means that we can allocate resources to improve our core product. 

As your startup grows, it's important to think about scaling your development process. I hope this post helped you think about your processes and gave you ideas on you can scale.


Author bio: Ivan Rigovsky is a mobile developer who built a platform to scale development of  travel guides. Follow him on Twitter, or connect via email if you need development help. 


Love sharing your technical expertise? Learn more about the Bitbucket writing program.

Scaling your Bitbucket team? Upgrade your plan here