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.
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.