In Part 1 of this series, we discussed how Continuous Integration (CI) at AnySoftKeyboard ensures that our main branch is always in a healthy, shippable state. Through rigorous testing, static analysis, and code style checks, we gain the confidence that our code is robust and ready for prime time.
But what good is a perfectly integrated codebase if it never reaches our users? This is where Continuous Deployment (CD) comes into play. In this second part, we’ll dive into how AnySoftKeyboard automates the process of getting our application from our main and release branches directly into the hands of our users.
Why Continuous Deployment? The Need for Speed and Feedback
The primary goal of Continuous Deployment is to make the release process routine, reliable, and, most importantly, fast. Instead of infrequent, high-stress “release days,” CD allows us to deliver value to users continuously.
For AnySoftKeyboard, this means:
- Faster Feedback from Alpha and Beta Users: By deploying frequently to our Alpha and Beta channels, we get new features and bug fixes into the hands of our most engaged users almost immediately. This rapid feedback loop is invaluable for validating new functionality, identifying regressions, and ensuring a high-quality user experience before a wider release.
- Quick Delivery of Features and Fixes: When a critical bug is found or a highly anticipated feature is ready, CD enables us to push it out to users with minimal delay, maximizing impact and user satisfaction.
- Reduced Risk: Each deployment is a small, incremental change, making it easier to identify and roll back issues if they arise, compared to large, infrequent releases.
Our Deployment Triggers: Different Cadences for Different Audiences
AnySoftKeyboard employs a multi-channel deployment strategy, each with its own trigger and audience, allowing us to balance rapid iteration with stability.
Alpha Channel (The Cutting Edge)
- Metric: Deployment to Alpha takes approximately 15 minutes from merge to Play Store.
- Pointer: The Alpha deployment is kicked off by the
checks.ymlworkflow, which runs on every push to themainbranch. After all checks pass, it triggers a deployment request. - Snippet: This process is defined in
.github/workflows/checks.yml. The workflow triggers on apushtomain, and if all checks pass, the finaldeploy-requestjob is executed to initiate the Alpha deployment.
Beta Channel (The Weekly Digest)
- Pointer: The Beta promotion is handled by a scheduled workflow that runs daily, but the promotion itself is gated to only happen on Wednesdays.
- Snippet: The schedule is defined in
.github/workflows/deployment_promote.yml:
# In .github/workflows/deployment_promote.yml
on:
schedule:
- cron: '04 04 * * *' # Runs daily at 04:04 UTC
This workflow triggers our custom deployment tool, which contains the gating logic in js/github_deployments/deployment_config.ts. The promoteOnWednesday function ensures the promotion to the Beta channel only proceeds when the job runs on a Wednesday.
Stable Channel (The Official Release)
- Metric: Our stable rollout completes over 5 days: 10% → 20% → 50% → 100%.
- Pointer: The Stable release process is triggered not by a Git tag, but by a push to a specifically named branch, like
release-branch-ime-v1.12-r1. This kicks off a time-based, gradual rollout. - Snippet: The
checks.ymlworkflow is also configured to run on pushes to release branches, as seen in the same file:
# In .github/workflows/checks.yml
on:
push:
branches:
- 'release-branch-*-v*.*-r*'
Our deployment script then uses a regular expression (/^release-branch-ime-.*$/) in js/github_deployments/deployment_config.ts to identify this as a Production release, which begins the automated, multi-day staged rollout.
The Brains of the Operation: Our github_deployments Tool
While GitHub Actions define the “when” of our deployments, the “what” and “how” are orchestrated by a custom, in-house TypeScript tool located at js/github_deployments. This command-line tool, executed via Bazel, acts as the central brain for our entire CD process.
Its primary responsibilities are:
- Interpreting Triggers: It consumes the branch name (e.g.,
main,release-branch-...) and determines which deployment configuration to use (Alpha, Beta, or Production). - Calculating Rollout Steps: For our time-based rollouts, it calculates the current day of the release and selects the correct rollout percentage from a predefined array.
- Interacting with the GitHub API: It creates the official
deploymentevents using the GitHub API, which in turn trigger thedeploy.ymlworkflow. It’s also used to report the status back to GitHub.
For example, when the checks.yml workflow runs on a main branch push, it executes the following command:
# This command is wrapped in our .github/actions/deploy-request action
bazel run //js/github_deployments -- deploy \
--refname="main" \
--deployMode="force_new" \
# ... other parameters
This command tells our tool to create a new “Alpha” deployment. This layer of abstraction allows us to keep our deployment logic in well-tested TypeScript code, rather than in complex and hard-to-maintain YAML files.
Executing the Release: The Magic of Automation
Regardless of the trigger, the actual process of building, signing, and publishing the application is entirely automated. Once a deployment is triggered, a dedicated GitHub Actions workflow takes over.
This workflow executes a series of scripts that perform all the necessary steps:
- Building the Application: The latest code is compiled and packaged into an Android App Bundle (
.aab). - Secure Signing: Signing keys and API credentials, stored as encrypted GitHub Secrets, are injected at build time. The App Bundle is then cryptographically signed using these credentials to ensure its authenticity and integrity.
- Publishing to Google Play: The signed App Bundle is uploaded to the Google Play Store. This entire process is handled via programmatic API calls to the Google Play publishing APIs, powered by the
gradle-play-publisherplugin. This eliminates manual uploads, reduces human error, and ensures a consistent, reliable deployment every time.
The key here is that no human intervention is required for these steps. The automation handles everything, from compiling the code to interacting with the Play Store’s backend.
Closing the Loop: Visibility and Supporting Tools
An automated deployment process is only effective if it provides clear visibility into its status. For AnySoftKeyboard, various tools and mechanisms are in place to provide this feedback directly within our development workflow.
- Deployment Status Updates: We use our
github_deploymentstool to communicate with the GitHub Deployments API. The same tool that initiates a deployment is also called at various stages within the maindeploy.ymlworkflow to report back the status. For example, as soon as a deployment begins, we run the following command to set the status to “in progress”:
# In .github/workflows/deploy.yml
bazel run //js/github_deployments -- status \
--deployment-id="$" \
--state=in_progress
This immediately updates the deployment status in the GitHub UI, providing real-time feedback.
- Automated GitHub Releases: After a successful deployment to the Google Play Store, a dedicated
post_deploymentjob in ourdeploy.ymlworkflow handles the creation of a public-facing GitHub Release. Interestingly, this is a two-step process. When the stable rollout first begins (at 10%), we use the popularsoftprops/action-gh-releaseaction to create a Pre-release.
# In .github/workflows/deploy.yml
- name: Create GitHub Pre-Release
if: endsWith(needs.deploy.outputs.deployment_environment, '_010')
uses: softprops/[email protected]
with:
prerelease: true
# ... other parameters
Once the rollout successfully completes and reaches 100% of users, a later step in the same job updates the release, removing the “pre-release” flag. This ensures the official release notes are only marked as “Latest” when the deployment is fully complete.
# In .github/workflows/deploy.yml
- name: Update GitHub Release to Stable
if: endsWith(needs.deploy.outputs.deployment_environment, '_100')
run: gh release edit "$" --prerelease=false
These mechanisms ensure that while the deployment itself is automated, the development team remains fully informed and can quickly react to any issues.
To be continued…
In the next and final part of this series, we’ll explore the “Complimenting Workflows” that support our core CI/CD pipeline. These include other automation tasks that enhance developer experience, maintain code quality, and ensure the overall health of the AnySoftKeyboard project. Stay tuned!