DevOps · Android · CI/CD · AI agents
The Android DevOps Toolbox: CI/CD, Distribution, Install Guides and the Setup Behind AI Agents (MCP)
Every Android team eventually discovers that the hardest part of shipping is not the code. It is the machinery around it: the server that builds the app the same way every time, the key that signs it, the pipe that carries it to testers and then to Google Play, and now the AI agents that want access to all of it. I have set this machinery up and kept it alive for years. This is the toolbox I would hand a new engineer: what each tool is, when to choose it, how to install it, and where the official docs live.
- Android engineers are software engineers, and software includes how it ships. If you cannot read a CI log, rotate a signing secret or explain how your build reaches Play, you depend on someone else for your own release.
- The pipeline has six stations: source → build and test → quality → distribute → store → observe. Every tool fits one of them.
- CI servers run your build: GitHub Actions, CircleCI, Bitrise (hosted), Jenkins (self-hosted). Build tools (Gradle, fastlane) do the work inside them. Distribution (Firebase App Distribution, DeployGate, Play tracks) delivers builds to humans.
- Pick the CI that is closest to where your code already lives, and keep the real logic in Gradle or fastlane so you can move CI later.
- MCP (Model Context Protocol) is the plug that lets AI tools use your GitHub, Firebase and tickets. Treat it as production access: least privilege, read-only first, secrets in environment variables, a human approves writes.
Contents
- Why Android engineers need DevOps
- The map: where each tool sits
- CI servers: GitHub Actions, CircleCI, Bitrise, Jenkins
- Which CI should you pick?
- Build tooling: Gradle, fastlane, Gradle Play Publisher
- Distribution: Firebase App Distribution and DeployGate
- Google Play, the API and signing
- Quality gates, observability, Docker and secrets
- The AI era, part 1: MCP setup
- The AI era, part 2: agents in your pipeline
- Day-1 checklist for a new Android repo
- Pitfalls I have paid for
- Takeaways and glossary
1. Why Android engineers need DevOps
I hear “that is the DevOps team’s job” less often than I used to, and I am glad. Here is why I push every Android engineer to learn the basics:
- Faster feedback. A pull request that fails lint, tests or a size budget in six minutes is cheaper than a bug found by a tester on Friday.
- Reproducible builds. “Works on my machine” ends when a clean machine builds the app from a commit. If it cannot, you have hidden state (a local file, a cached key, a different JDK).
- You own the release. The person who knows why a build failed is usually the person who wrote the code. Waiting for another team to read a Gradle error costs hours.
- Signing and secrets are your responsibility. A leaked keystore or Play service account is a security incident, not a build problem.
- Release day involves you. Someone watches crash rates and the staged rollout. See the release validation strategy for what to watch, and the release lifecycle story for the human side.
- Your career. Engineers who can design the pipeline, not just use it, become the people who lead release quality. It is also the same skill set you need to supervise AI agents, which touch the same systems.
Analogy: a restaurant cook who never looks at the kitchen layout, the fridge temperature or how plates reach the table can cook well and still make people ill. DevOps is the kitchen. You do not have to run it alone, but you should be able to walk through it.
2. The map: where each tool sits
3. CI servers: GitHub Actions, CircleCI, Bitrise, Jenkins
A CI server watches your repository, starts a clean machine for each change, runs your commands and reports the result. All four tools below do that. They differ in where they run, how you pay, and how much you maintain.
3.1 GitHub Actions
What: CI built into GitHub. Workflows are YAML files in .github/workflows/. Pick it when your code is already on GitHub and you want the shortest path. Install: nothing to install for hosted runners. Commit a workflow file, then add secrets in Settings → Secrets and variables → Actions. For self-hosted runners, follow the “Add runner” page in the repository settings, which gives you a download and a registration token.
name: android-ci
on:
pull_request:
push:
branches: [main, "release/**"]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17 # match what your Android Gradle Plugin requires
- uses: gradle/actions/setup-gradle@v4 # caches Gradle for you
- name: Lint and unit tests
run: ./gradlew lintDebug testDebugUnitTest
- name: Build release bundle
env:
KEYSTORE_B64: ${{ secrets.KEYSTORE_B64 }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
echo "$KEYSTORE_B64" | base64 -d > "$RUNNER_TEMP/release.jks"
./gradlew bundleRelease -Pandroid.injected.signing.store.file="$RUNNER_TEMP/release.jks" \
-Pandroid.injected.signing.store.password="$KEYSTORE_PASSWORD" \
-Pandroid.injected.signing.key.alias="$KEY_ALIAS" \
-Pandroid.injected.signing.key.password="$KEY_PASSWORD"
- uses: actions/upload-artifact@v4
with:
name: release-aab
path: app/build/outputs/bundle/release/*.aab
Check the Marketplace for the current major version of each action before you copy this. Useful habits: concurrency cancels stale runs, an environment with required reviewers protects the job that publishes, and permissions: at the top of the file keeps the default token read-only.
Official docs: docs.github.com/actions
3.2 CircleCI
What: a hosted CI service (self-hosted runners also exist) configured in .circleci/config.yml. Pick it when you want strong caching, parallelism and test splitting, or already use it elsewhere in the company. Install: sign in with GitHub or Bitbucket, choose Projects → Set Up Project, and CircleCI adds a deploy key and a webhook. Put shared secrets in an organization context so only chosen jobs can read them.
version: 2.1
orbs:
android: circleci/android@<version> # check the orb registry for current version and names
jobs:
build:
executor:
name: android/android_docker
tag: <image-tag> # a cimg/android tag
steps:
- checkout
- android/restore_gradle_cache
- run: ./gradlew lintDebug testDebugUnitTest bundleRelease
- android/save_gradle_cache
- store_artifacts:
path: app/build/outputs/bundle/release
workflows:
ci:
jobs:
- build:
context: android-release # secrets live here, not in the file
The convenience images live under cimg/android on Docker Hub. The orb’s command and executor names change over time, so copy them from the registry page rather than from memory (including mine). Official docs: circleci.com/docs
3.3 Bitrise
What: a hosted CI/CD service built for mobile. You compose a workflow from steps in a web editor or in bitrise.yml. Pick it when the team wants mobile-aware defaults and does not want to write much YAML: Android SDK images, code signing handling and a large step library are built in. Install: sign in, Add new app, connect the repository, choose the stack (a Linux Android image), and let Bitrise scan the project. It proposes a starting workflow. Store secrets under Secrets in the workflow editor, and upload the keystore to Code signing.
format_version: "<version>"
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
workflows:
primary:
steps:
- git-clone@<version>: {}
- android-unit-test@<version>:
inputs:
- variant: debug
- android-build@<version>:
inputs:
- module: app
- variant: release
- build_type: aab
- deploy-to-bitrise-io@<version>: {}
Bitrise also publishes steps for Firebase App Distribution, Google Play and Slack; search the Step Library for the current step ids and inputs. Official docs: devcenter.bitrise.io
3.4 Jenkins
What: the long-standing open-source automation server you run yourself. Pick it when you need full control, builds must stay inside your network, or a large plugin ecosystem and existing company infrastructure matter more than convenience. The cost is operations: upgrades, plugin security, agents and disk are yours.
Install (pick one; follow the official page for your OS):
# Docker, the quickest way to try it (LTS image)
docker run -d --name jenkins -p 8080:8080 -p 50000:50000 \
-v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk17
# macOS with Homebrew
brew install jenkins-lts && brew services start jenkins-lts
# then open http://localhost:8080 and unlock with the initial admin password
# (printed in the container log, or in the file shown on the unlock page)
Run builds on agents, not on the controller. An Android agent needs a JDK, the Android command-line tools (sdkmanager to install platforms and build-tools), ANDROID_HOME set, and accepted SDK licenses. Baking all of that into a Docker image makes agents disposable.
// Jenkinsfile (declarative)
pipeline {
agent { label 'android' }
environment { ANDROID_HOME = '/opt/android-sdk' }
stages {
stage('Test') { steps { sh './gradlew lintDebug testDebugUnitTest' } }
stage('Build') {
steps {
withCredentials([file(credentialsId: 'release-keystore', variable: 'KS')]) {
sh './gradlew bundleRelease -Pandroid.injected.signing.store.file=$KS'
}
}
}
}
post { always { archiveArtifacts artifacts: 'app/build/outputs/**/*.aab', allowEmptyArchive: true } }
}
Official docs: jenkins.io/doc
4. Which CI should you pick?
| Tool | Hosting | Cost model | Strengths | Android fit |
|---|---|---|---|---|
| GitHub Actions | Hosted, self-hosted runners optional | Free minutes then per minute; larger runners cost more | Lives next to the code, huge action ecosystem, easy PR checks | Good. Linux runners suit most builds; emulator tests need care |
| CircleCI | Hosted, self-hosted runners optional | Credits by machine size and time | Fast caching, test splitting, flexible executors | Good. Android images and orb are first class |
| Bitrise | Hosted (self-hosted possible on some plans) | Plans by build credits and concurrency | Mobile-focused, step library, signing handling, UI editor | Very good. Least YAML to start |
| Jenkins | Self-hosted | Free software; you pay for servers and people | Total control, plugins, runs inside your network | Good if you maintain agents. More upkeep |
Prices and free tiers change, so check each vendor before you decide. My rules of thumb:
- Small team, code on GitHub: GitHub Actions. Start there unless you have a reason not to.
- Mobile-only team without a DevOps person: Bitrise. You trade some price for less setup.
- Large builds that need heavy parallelism: CircleCI or larger GitHub runners. Measure queue time, not just run time.
- Regulated company, builds must stay on-prem: Jenkins (or self-hosted runners for one of the others).
- Whatever you choose: keep the real logic in Gradle tasks and fastlane lanes. A CI file should be thin glue. Then moving CI later is a day of work, not a quarter.
5. Build tooling: Gradle, fastlane, Gradle Play Publisher
Gradle
Gradle is the build tool Android already uses, so make CI call it directly. Always run ./gradlew (the wrapper), which pins the Gradle version in the repository, instead of a Gradle installed on the machine. Turn on the build cache and configuration cache where your plugins support them, and use the CI vendor’s Gradle caching so repeated runs skip work. Docs: developer.android.com/studio/build
fastlane
fastlane is a Ruby toolkit that scripts mobile release tasks as lanes: build, sign, upload to Play, upload to Firebase, post to Slack. It is CI-agnostic, which is exactly why I like it.
# install (either one)
brew install fastlane
# or, pinned per project (recommended): add `gem "fastlane"` to a Gemfile, then
bundle install && bundle exec fastlane init # choose manual setup for Android
# fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Build the release bundle and push it to Play internal testing"
lane :internal do
gradle(task: "bundle", build_type: "Release")
upload_to_play_store(
track: "internal",
aab: lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH],
json_key: ENV["PLAY_JSON_KEY_PATH"]
)
end
desc "Send an APK to testers through Firebase App Distribution"
lane :beta do
gradle(task: "assemble", build_type: "Release")
firebase_app_distribution(
app: ENV["FIREBASE_APP_ID"],
groups: "qa",
release_notes: "Build from CI"
) # needs: fastlane add_plugin firebase_app_distribution
end
end
The Play upload action is called upload_to_play_store (also known as supply). Docs: docs.fastlane.tools
Gradle Play Publisher
If you would rather avoid Ruby, the Gradle Play Publisher plugin adds Play upload tasks to Gradle itself.
plugins {
id("com.github.triplet.play") version "<version>"
}
play {
serviceAccountCredentials.set(file(System.getenv("PLAY_JSON_KEY_PATH") ?: "play.json"))
track.set("internal")
}
// then: ./gradlew publishReleaseBundle
fastlane or Gradle Play Publisher? I choose fastlane when the team also needs Firebase, Slack and screenshots in one place, and the plugin when the project is Gradle-only and I want zero extra runtimes on CI.
6. Distribution: Firebase App Distribution and DeployGate
Distribution tools put a build in front of testers before the store. They are faster than Play review and need no store listing.
Firebase App Distribution
Part of Firebase, free to use, with groups, release notes and tester feedback. Choose it when you already use Firebase, or want one place for builds and crashes. For concepts see the App Distribution overview. Three ways to use it from CI: the Gradle plugin, the Firebase CLI, or fastlane.
# install the Firebase CLI (needs Node.js)
npm install -g firebase-tools
# or the standalone binary from the official install page
# in CI, authenticate with a service account instead of your own login
export GOOGLE_APPLICATION_CREDENTIALS="$RUNNER_TEMP/firebase-sa.json"
firebase appdistribution:distribute app/build/outputs/apk/release/app-release.apk \
--app "$FIREBASE_APP_ID" \
--groups "qa" \
--release-notes "Build $GITHUB_RUN_NUMBER from $GITHUB_REF_NAME"
Give the service account only the App Distribution role it needs. Docs: firebase.google.com/docs/app-distribution. Prefer to trigger uploads from the IDE? See the Android Studio plugin article.
DeployGate
DeployGate is a distribution service popular in Japan and across Asia. Testers install through its app or a link, and it shows per-build install status and device information. Choose it when your organization or clients already use it, or you need its team management features. It has a web API, a command-line tool, a Gradle plugin and a fastlane plugin; pick the one that matches your pipeline. The API route is the most universal:
# upload an APK with the DeployGate API (token comes from your account settings, kept as a CI secret)
curl -F "token=$DEPLOYGATE_API_TOKEN" \
-F "file=@app/build/outputs/apk/release/app-release.apk" \
-F "message=Build $BUILD_NUMBER" \
"https://deploygate.com/api/users/<owner>/apps"
Check the docs for current parameters (distribution pages, release notes, groups). Docs: docs.deploygate.com
| Channel | Best for | Review wait | Notes |
|---|---|---|---|
| Firebase App Distribution | Daily QA, dogfooding, UAT builds | None | Integrates with Crashlytics and Gradle |
| DeployGate | Teams and clients that use it already | None | Own app and install tracking |
| Play internal testing | The exact bundle that will ship | Minutes | Same signing and install path as production |
| Play closed and open testing | Wider beta | Can need review | Real Play Store experience |
7. Google Play, the API and signing
Play Console and the Publisher API
The Play Console is the web UI. For automation you use the Google Play Developer API, which fastlane and Gradle Play Publisher call for you. Setup, in order:
- In Google Cloud, create or pick a project, enable the Google Play Android Developer API, and create a service account with a JSON key.
- In the Play Console, open Users and permissions, invite the service account’s email, and grant only the app permissions it needs (for example, release to testing tracks, not to production).
- Store the JSON key as a CI secret, write it to a temporary file during the job, and delete it after.
- Do a first upload by hand in the console, since apps usually must exist and be set up before the API can release them.
Console menus move around. If a step in my list does not match what you see, follow the current Play Console help pages rather than my wording.
Signing
With Play App Signing, Google holds the key that signs what users install, and you sign uploads with a separate upload key. That is the setup I recommend: if the upload key leaks, you can ask Google to reset it. Without it, a lost keystore can end the app.
# create an upload keystore (store it in a secrets manager, not in git)
keytool -genkeypair -v -keystore upload.jks -alias upload \
-keyalg RSA -keysize 2048 -validity 10000
# base64 it so it can live in a CI secret
base64 -i upload.jks | pbcopy # macOS; use `base64 -w0 upload.jks` on Linux
Docs: developer.android.com/studio/publish/app-signing
8. Quality gates, observability, Docker and secrets
Quality gates
| Tool | What it checks | How you run it |
|---|---|---|
| Android Lint | Android-specific problems: API misuse, unused resources, accessibility, security hints | Built in: ./gradlew lintDebug. Docs |
| ktlint | Kotlin code style | CLI or a Gradle plugin. Docs |
| detekt | Kotlin code smells and complexity | Gradle plugin with a baseline file for legacy code. Docs |
| SonarQube | Bugs, vulnerabilities, coverage trends, quality gate on pull requests | Server (self-hosted or cloud) plus a Gradle scanner step. Docs |
| Size and startup budgets | APK or AAB size, cold start | A CI step that fails when a number crosses its limit |
Introduce these with a baseline: freeze today’s warnings, then fail only on new ones. Otherwise the first pipeline run is red forever and people stop caring.
Observability after the release
Crashlytics and Android vitals in Play Console tell you crash and ANR rates per version. Wire a Slack or chat notification into the pipeline: build result, version name, a link to the release, and a link to the crash dashboard. See the Crashlytics overview.
Docker for build images
If builds differ between machines, put the toolchain in an image: JDK, Android command-line tools, SDK platforms and build-tools, Node for the Firebase CLI. Start from the CI vendor’s image (cimg/android for CircleCI) or a maintained community image, and pin the tag. Build your own only when you need something unusual. Install Docker from docs.docker.com.
Secrets
| Where | Use it for | Watch out |
|---|---|---|
| CI secrets (GitHub secrets and environments, CircleCI contexts, Bitrise secrets, Jenkins credentials) | Keystore, passwords, Play and Firebase service accounts, API tokens | Scope them per environment. Secrets are masked in logs only if printed exactly as stored |
| A secrets manager (Vault, a cloud secret manager, a password-manager CLI) | Many teams, rotation, audit | Adds a dependency; use short-lived credentials where possible |
| The repository | Never. Not even “private” repositories | Add secret scanning and a pre-commit hook |
9. The AI era, part 1: MCP setup
The Model Context Protocol is an open standard for connecting AI applications to tools and data. Instead of every AI tool writing its own GitHub or Firebase integration, a tool exposes an MCP server once and any MCP-capable app can use it. For a DevOps engineer, that makes it the new “API access” question: who is allowed to do what, through which agent?
What you need on the machine
| Need | Why | Install |
|---|---|---|
| Node.js | Many servers start with npx | From nodejs.org or a version manager |
| Python with uv | Python servers start with uvx | docs.astral.sh/uv |
| Docker | Some servers ship as images, which also isolates them | docs.docker.com |
| Tokens and keys | A GitHub token, a Firebase login or service account, a Jira token | Create them with the smallest scope that works |
| An MCP host | Claude Code, an IDE, or another MCP-capable client | See the Claude Code setup |
Adding servers to Claude Code
# local server over stdio (everything after -- is the command that starts it)
claude mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/allowed/dir
# remote server over HTTP
claude mcp add --transport http github https://api.githubcopilot.com/mcp/ \
--header "Authorization: Bearer $GITHUB_PAT"
# see what is configured, and check status inside Claude Code with /mcp
claude mcp list
Scopes decide who gets the server: local to you, shared with the team in a project file, or for all your projects. The shared project file is .mcp.json at the repository root, so the whole team starts with the same servers. Keep tokens out of it by referencing environment variables:
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ${GITHUB_PAT}" }
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./docs"]
}
}
}
Other MCP hosts use a very similar shape: a name, a command with arguments (stdio) or a URL (HTTP), and environment variables. Options and flags change between releases, so confirm them with claude mcp --help and the Claude Code MCP documentation.
Servers that earn their place in an Android and DevOps team
These are examples to evaluate, not an endorsement list. Check each project’s own page for the current install command and permissions.
| Example | Use | Where to look |
|---|---|---|
| GitHub MCP server | Read issues and PRs, review diffs, check workflow runs | github/github-mcp-server |
| Firebase MCP server | Inspect projects, apps, Crashlytics and Remote Config from the agent. It is started through the Firebase CLI | Firebase docs |
| Filesystem server | Read and write inside folders you list, nowhere else | modelcontextprotocol/servers |
| Issue tracker server (Jira and Confluence via Atlassian’s offering, or your tracker’s) | Turn tickets into context for a fix | The vendor’s documentation |
The full catalogue and the specification are at modelcontextprotocol.io.
Security, because an MCP server is an access path
- A personal token with full repository rights pasted into a shared config file.
- A server installed from an unknown package that runs on your laptop with your permissions.
- Prompt injection: a ticket, web page or code comment contains instructions, and the agent follows them with the tools you gave it.
- An agent allowed to write to production systems with no human in the loop.
- Least privilege. Fine-grained, short-lived tokens limited to the repositories and actions needed. Read-only first; add writes one at a time.
- Secrets in the environment. Reference
${VARIABLES}in config; never commit values. - Trusted servers only. Official or reviewed code, pinned versions, and run untrusted ones in a container.
- Approvals. Keep the host’s permission prompts on for anything that writes, deletes, publishes or spends money.
- Treat all fetched text as untrusted input, especially content from tickets, web pages and third-party code.
- Audit. Know who connected what, and keep logs of tool calls where the host supports it.
10. The AI era, part 2: agents in your pipeline
Once an agent can read a ticket, edit code and run tests, the natural next step is to run it from CI: a label on a ticket or a crash threshold starts a job that opens a pull request. The thinking is in the Jira to pull request agent article and the team-level setup in automation and teams. The DevOps view is about the box the agent runs in.
What you need to set up:
- A headless mode. Claude Code can run non-interactively with
claude -p "<task>", and there is an official GitHub Action for running it on pull requests and issues. Check the docs for current options such as allowed tools and turn limits. - Secrets for the model API (for example
ANTHROPIC_API_KEY) stored as CI secrets, available only to the job that needs them. Do not run it on pull requests from forks. - A tight tool allowlist. Give the job the commands it needs (read files, run Gradle tests, open a PR), not a general shell with network and deploy rights.
- Branch protection. Required reviews and required status checks on
main, so the agent’s PR goes through the same gates as a person’s. - Limits. A job timeout, a maximum number of turns, and a spend alert on the API account. Agents loop; budgets stop them.
- A project memory file. A
CLAUDE.mdthat tells the agent how to build, test and what never to touch. See the memory article. - Observability. Save the transcript as a job artifact so you can read why it did what it did.
# sketch: a GitHub Actions job that runs an agent on a labelled issue
on:
issues:
types: [labeled]
permissions:
contents: write
pull-requests: write
jobs:
agent:
if: github.event.label.name == 'agent-fix'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: 17 }
- name: Run the agent
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
npm install -g @anthropic-ai/claude-code
claude -p "Fix issue #${{ github.event.issue.number }}. Run ./gradlew testDebugUnitTest. Open a pull request, do not push to main." \
--max-turns 20
Treat that as a shape, not a recipe: flags, package names and the official Action evolve, so read the current documentation before you copy anything into a repository that matters.
11. Day-1 checklist for a new Android repo
| # | Do this | Why |
|---|---|---|
| 1 | Commit the Gradle wrapper and pin the JDK version | Same build everywhere |
| 2 | Protect main: required PR review and required checks | Nothing unreviewed or red gets in |
| 3 | Add a CI workflow: lint, unit tests, assemble | Feedback on every PR |
| 4 | Turn on Gradle caching in CI | Builds in minutes, not tens of minutes |
| 5 | Create the upload keystore; enable Play App Signing; store secrets in CI | Safe, recoverable signing |
| 6 | Add a lane or job that sends every merge to main to App Distribution or DeployGate | Testers always have the latest |
| 7 | Add a manual, approved job that uploads to the Play internal track | Release without a laptop |
| 8 | Set version code from a CI counter or tag, never by hand | No “version code already used” surprises |
| 9 | Add ktlint, detekt and a baseline | Style debates end |
| 10 | Notify chat with result and links; connect Crashlytics | The team sees the release |
| 11 | Write RELEASING.md: who, how, rollback, contacts | The bus factor is not one |
| 12 | Only then: MCP servers and agents, read-only first | Automate a working process, not a missing one |
12. Pitfalls I have paid for
- Logic hidden in the CI UI. Click-configured steps nobody can review or move. Keep logic in files in the repo.
- Unpinned everything. “latest” images and action versions change under you on a Friday.
- Keystore only on one laptop. Or committed to git. Both end badly.
- Overpowered service accounts. A CI token that can publish to production from any branch.
- Secrets exposed to pull requests from forks or printed into logs.
- No cache, no timeout. Slow builds and runaway jobs that burn minutes.
- Flaky emulator tests as a hard gate. People learn to re-run until green. Quarantine flakes.
- Jenkins snowflakes. One hand-built agent that nobody dares to restart.
- Giving an agent more access than a new hire would get.
- Thin CI files that call Gradle and fastlane.
- Pinned versions, updated on purpose with a bot such as Dependabot or Renovate.
- Separate credentials per environment, rotated on a schedule.
- A release job that is boring, repeatable and approved by a human.
13. Takeaways and glossary
- DevOps is part of software engineering for Android: it is how your code reaches people, safely and repeatedly.
- Think in six stations: source, build, quality, distribute, store, observe. Choose the tool for each.
- Start with the CI closest to your code, keep logic in Gradle and fastlane, and protect signing keys and service accounts first.
- MCP and agents are new access paths into the same systems. Apply the old rules: least privilege, review, audit.
| Term | In plain words |
|---|---|
| CI / CD | Continuous integration builds and tests every change. Continuous delivery gets the result to testers or users. |
| Runner / agent | The machine that executes a CI job. |
| Workflow / pipeline | The ordered set of jobs that run for a change. |
| Artifact | A file a job produces, such as an AAB or APK. |
| AAB | Android App Bundle, the format you upload to Google Play. |
| Upload key | The key you sign uploads with when Play App Signing is on. |
| Service account | A non-human identity that automation uses to call an API. |
| Lane | A named fastlane task such as internal or beta. |
| MCP | Model Context Protocol, a standard way for AI apps to use tools and data. |
| stdio / HTTP transport | How an MCP client talks to a server: through a local process, or over the network. |
| Prompt injection | Hidden instructions in content an AI reads, trying to steer its actions. |
| Least privilege | Give an identity only the access it needs and nothing more. |
Links and official documentation
- CI: GitHub Actions · CircleCI · Bitrise · Jenkins
- Build and release: Android build · fastlane · Gradle Play Publisher · Google Play Developer API · App signing
- Distribution and monitoring: Firebase App Distribution · DeployGate · Crashlytics
- Quality and containers: ktlint · detekt · SonarQube · Docker
- AI tooling: Model Context Protocol · MCP servers · Claude Code and MCP
- On this site: The Android release lifecycle · Release validation strategy · App Distribution overview · Android Studio plugin for Firebase distribution · Jira to pull request agent · Automation and teams