I’m Mohammad Jashem — a Senior Mobile Architect shipping Flutter, React Native, and Expo to production for 7+ years. I maintain the NCC App across Google Play, the App Store, and Amazon Appstore, and I haven’t cut a release by hand in years. Three stores, three review processes, three device matrices — all on a pipeline I built once and have kept shippable since.
This is the practitioner’s guide I wish existed when I was wiring this up. Not “use GitHub Actions.” The actual stack: self-hosted Drone for Android build and deploy, Fastlane for store integration, Xcode Cloud for iOS, Bitbucket Pipelines for AI code review, tag-based test-build distribution, and Google Chat reporting. This is what a production-grade mobile release process looks like.
Why mobile CI/CD is its own discipline#
“We have GitHub Actions” is something I hear from founders every week. It is not enough. Web CI/CD and mobile CI/CD share vocabulary and almost no actual constraints. Here’s what makes mobile different.
Signing and provisioning. A web deploy is a static bundle behind a CDN. A mobile deploy is a cryptographically signed artifact, produced from a keychain that lives in one of three places — Apple Developer, Google Play App Signing, or Amazon’s uploader key — each with its own rotation, revocation, and access-control model. Lose control of the signing identity and you have an incident, not a deploy.
Three store review processes. App Review at Apple is a human-in-the-loop gate with policy interpretations that change week to week. Google Play has a tiered track system (internal, closed, open, production) plus a review window that tightened sharply in 2024. Amazon Appstore is its own review with its own metadata. Shipping to all three triples your release surface area.
Device matrices. A web build runs in a browser. A mobile build runs on a Samsung A-series handset with a Mali GPU, an iPhone 12 mini, a Pixel 6a, a foldable. The matrix isn’t theoretical — I’ve shipped the Kahf Kids parental-control app where a platform-view ANR on specific GPUs was the reason we migrated frameworks. Your CI/CD has to validate against that matrix, not against an emulator that passes everything.
Native toolchain coupling. iOS builds require macOS runners and a pinned Xcode. Android builds require the right NDK, the right Java LTS, and Gradle versions that don’t agree with each other across majors. A mobile runner is a zoo of pinned toolchains that drift every quarter.
If your team is still manually running flutter build ipa on someone’s laptop and uploading it through Transporter, you don’t have a release process. You have a ritual.
The stack#
The stack I run on NCC — and on every mobile engagement where I own the pipeline — splits the work by what each tool does best. No single tool clears all three stores — orchestrators don’t speak the store APIs, Fastlane speaks the store APIs but doesn’t schedule builds, and Xcode Cloud runs iOS on Apple infrastructure but does nothing for Android. The division of labor is forced on you.
Drone CI orchestrates the Android leg. It runs build and deploy as a pipeline declared in a .drone.yml checked into the repo. I self-host it on Proxmox behind a Cloudflare Tunnel because it keeps my secrets, build cache, and signing material inside infrastructure I control. Drone’s container-per-step model means each stage runs in an isolated image with the exact toolchain pinned — a custom Flutter CI image for the build, the Fastlane image for the deploy step. Each step runs as its own container, so the build step and the deploy step don’t share state unless I explicitly mount it, and the toolchain (Flutter, the Android SDK, NDK, Gradle, Kotlin) is baked into the image rather than resolved at build time — a runner that resolves its toolchain at build time breaks the day a transitive dependency bumps.
Fastlane is the store integration layer. It’s the only mature open-source tool that speaks the Google Play Developer API, the App Store Connect API, and the Amazon Appstore API coherently. Fastlane handles version bumps, metadata sync, screenshot uploads, signed-build production, and track promotion. There is no second-best option.
Xcode Cloud is the iOS-specific layer. The constraint is non-negotiable: you cannot produce a signed App Store ipa without macOS and a pinned Xcode. Self-hosting a Mac runner is a maintenance liability I no longer recommend — the hardware drifts, OS updates break the runner, somebody has to reboot it. Xcode Cloud runs on Apple’s infrastructure and keeps signing inside Apple’s trust boundary: no .p12 to leak, no provisioning profile to renew. The build hook Apple requires at clone time installs the pinned Flutter version, reads the release version, and runs the iOS release build unsigned — flutter build ios --release --no-codesign — and Xcode Cloud signs the artifact inside its own workflow and pushes it to TestFlight. The same merge that kicks off Drone’s Android leg is watched by Xcode Cloud, which builds and ships the iOS leg.
Bitbucket Pipelines runs a separate stage I’ll spend a section on: an AI code review of every pull request, before a human ever looks at it.
Google Chat is the reporting layer. Every pipeline event — build started, passed, failed, published — posts into a team channel with links. This is the part most teams skip and it’s the part that makes the pipeline feel real to the rest of the company.
The end-to-end shape: a developer opens a pull request. Bitbucket Pipelines runs the AI code review on the diff and posts inline comments. The PR merges. Drone picks up the push, builds the Android release artifacts, and hands them to Fastlane, which uploads to Google Play and Amazon and promotes to the internal track. In parallel, Xcode Cloud builds the iOS leg and ships to TestFlight. Google Chat reports every transition. A human promotes from internal to production when the vitals look clean.
Auto-deploy to Google Play, App Store, Amazon Appstore#
This is where most teams give up and do it by hand. Here is what “all from a merge” actually means.
Version bumps. The release version is read from a committed version file and written into the build before the upload step runs. No developer touches the version manually. That eliminates an entire category of “I shipped 1.4.2 over 1.4.3 and now the store thinks the new build is older” incidents.
Signing. The signing keys live in the CI environment — the Google Play service-account JSON and the Amazon uploader key in Drone’s secret store, the iOS signing inside Apple’s trust boundary on Xcode Cloud. The keys never touch a developer laptop. Access is auditable and revocable. When a contractor rotates off a project, I revoke one CI secret and the surface area is closed.
Store metadata. Fastlane’s metadata directory holds the per-store, per-locale release notes, descriptions, keywords, and screenshots — checked into git, reviewed in PRs, synced to the stores on every release. A copywriter can update the Italian release note without asking an engineer. A marketing fix becomes a one-line PR.
Screenshots. Fastlane’s snapshot tooling generates the per-device, per-locale screenshot set on every release. New device sizes ship from Apple and Google roughly twice a year — Fastlane produces the new artwork without a manual capture-and-upload pass.
Rollout. Android goes to the internal track, then closed, then a staged production rollout (10%, 50%, 100% over a few days). iOS goes to TestFlight, then a phased App Store release. Amazon goes to the live testing group, then production. Promotion is a Fastlane command, not a UI click. If a stage shows regression in the store vitals — Play Console or App Store Connect crash data — or the API uptime monitor blips, I roll back with another command.
The output: a developer who merges a PR doesn’t ask anyone “what happens next.” The merge is the release trigger. Promotion is the only human gate, and it’s a deliberate one — the moment a senior engineer looks at the vitals and decides the rollout is safe.
Tag-based test-build distribution#
This is the single highest-leverage thing I’ve added to my pipeline in the last three years.
Pre-pipeline, the test-build conversation goes like this: a QA engineer or stakeholder needs to test the latest build. They ask a developer. The developer stops what they’re doing, switches to a release branch, runs the build locally, signs it, uploads it to Drive or TestFlight, and sends a link. The whole thing takes twenty minutes of human time per request. On a busy project it happens several times a day.
Post-pipeline: any developer — junior or contractor — pushes a git tag matching the configured pattern. Drone sees the tag and runs a build-only pipeline for the signed Android APK; Xcode Cloud builds the iOS leg from the same change and ships it to TestFlight. Download links for both land in Google Chat. The person who asked for the test build gets a link in the channel minutes later. No engineer was interrupted.
The tag carries the platform — one prefix for Android, one for iOS, one for both — and the tag name maps directly back to a git ref, so a tester reporting a bug references a build that maps to an exact commit.
The deeper benefit: test builds stop being a scarce resource controlled by whoever has the signing keys. A QA engineer reproducing a bug can self-serve a build at the exact commit. A product manager can pull a build for a specific feature branch. The engineering team stops being a build factory.
Google Chat notifications#
The reporting layer is the part of CI/CD most teams under-build. I do it because a pipeline that runs in the background and emails you when it’s done is invisible to everyone except the developer who pushed the commit. A pipeline that posts into a shared channel is visible to the whole team.
I use a notifier that posts a structured card into a Google Chat webhook on every Drone transition — status, branch, commit, author, and links to the build artifacts. A card that says Build started: feature/payment-redesign, pushed by Sara, then Build passed: artifact uploaded to Play internal track, or Build failed: deploy-amazon step, logs linked — gives the team what it actually needs. Product managers see how often features land. QA sees which builds are testable. The on-call engineer sees a failure the instant it happens, with the failing step linked.
Slack, Teams, or Discord all work. What matters is that the pipeline talks to a shared room the team already lives in, instead of burying its output in a dashboard no one watches.
AI code review in the pipeline#
Every pull request on this codebase is reviewed before it ships — not just by a human, by an AI code-review stage that runs against the diff. The stage lives in Bitbucket Pipelines, separate from the Drone build/deploy pipeline. On PR open and on every push to an open PR, it pulls the diff, hands it to Claude Code through a z.ai API endpoint, and writes the review back as inline comments anchored to specific lines — the same way a human reviewer would.
I wrote about how AI code review fits in the pipeline in detail elsewhere. The short version: every PR gets an automated first-pass review against the codebase’s conventions, the platform contracts, and the architectural patterns I’ve codified — before a human ever looks at it. Review time went from days to seconds for the first pass.
In the context of release automation, this matters more than it sounds. A staged rollout catches what only real devices and real users surface. AI code review catches what a senior would have caught on a read of the diff — missing teardown, state reaching across feature boundaries, a network call that bypasses the repository layer, an unhandled error path. The two are complementary. A pipeline that ships staged rollouts but skips the diff review is shipping on half a safety net.
I cover the broader workflow in AI-assisted mobile development — generation is half the loop, review is the other half. If you’re building a release pipeline in 2026 and AI code review isn’t in it, you’re leaving the cheapest quality win on the table.
The 7-year maintenance story#
CI/CD isn’t a thing you build once. It’s a thing you maintain for the life of the app. The NCC App is the longest-running proof I have — over seven years on Flutter, from Flutter 1.x to the latest stable, across three stores, with zero major production incidents in the maintenance window.
The discipline behind that record is a calendar: manual verification on the load-bearing flows (auth, payments, the API contract), staged rollouts on every store, conservative dependency upgrades, and a pipeline that makes all of it reproducible. I won’t pretend a deep automated test suite is part of that list — it isn’t, and I wrote honestly about why in maintaining a Flutter app for 7 years. When Flutter went null-safe in 2021, the migration went through the same pipeline as a feature release — staged, track-promoted, rolled back once when a leaf package broke, fixed, promoted again. The pipeline is what let those migrations happen without missing a release cadence.
The CI/CD story is the spine of it — without a reproducible pipeline, “conservative and staged” isn’t a strategy, it’s a hope.
A pipeline running for seven years on the same app is a different artifact than one built last quarter. It has absorbed Flutter 1 through 3.x, the App Store’s evolving review policy, Google Play’s tightening review window, the switch to the App Store Connect API, and the deprecation of half a dozen Fastlane plugins. Maintaining the pipeline is itself engineering work — the part that doesn’t show up in a “we set up CI in a day” blog post.
Self-hosted vs cloud tradeoffs#
I self-host Drone on Proxmox behind a Cloudflare Tunnel, and I also use cloud CI — Bitbucket Pipelines for the AI review stage, Xcode Cloud for iOS — depending on what each step needs. Founders ask me which to pick. The honest answer is “it depends on three things.”
Cost. Cloud CI is priced per minute. A team running 30 mobile builds a day, each 15–25 minutes, is burning real money monthly — and macOS minutes on every cloud provider cost roughly 5–10x what Linux minutes cost. Self-hosted Drone on a box you already own amortizes to near-zero per build. The crossover is roughly when cloud monthly spend would have paid for the hardware in 6–9 months.
Control. Self-hosted means secrets never leave your infrastructure. Signing keys, App Store Connect API keys, Play service-account JSON — all stay on hardware you control. Cloud CI secrets are stored encrypted at the provider, and for most engagements that’s fine. For banking, healthcare, or regulated markets, self-hosted is often non-negotiable.
Operational overhead. Self-hosted means you patch the host, upgrade Drone, maintain runner images, and debug the 3am “build server is down” pages. Cloud CI means none of that. If you don’t have someone who wants to own the build infrastructure as a first-class responsibility, self-hosted will rot. I have that role; I self-host the build/deploy leg. Most small teams don’t; they should use cloud.
The practical recommendation: start on cloud (Bitbucket Pipelines, GitHub Actions, or Codemagic) until build volume makes the cost real. Then move high-volume, secrets-sensitive workloads to self-hosted Drone and keep the cloud runner for what it does best — Xcode Cloud for iOS, a cloud step for AI review. A hybrid setup is not a compromise — it’s the correct architecture for most mid-sized mobile teams.
One note on Cloudflare Tunnel: do not expose your self-hosted Drone directly to the internet. The tunnel terminates at the edge and reaches your box over an outbound connection — no inbound ports, no exposed origin IP, Cloudflare’s auth in front. This is the only sane way to expose a self-hosted CI server in 2026.
Conclusion#
Mobile CI/CD is a discipline of its own because mobile is — three stores, three review processes, three device matrices, a signing story, a toolchain that drifts every quarter. The stack I run — self-hosted Drone, Fastlane, Xcode Cloud, Bitbucket Pipelines AI review, Google Chat, tag-based test distribution — is the production answer I’ve refined over seven years. It’s the reason I can maintain the NCC App for seven years with zero major incidents, and the reason every engagement I take on ships from day one instead of accumulating release debt.
If you want to read the adjacent pieces, AI-assisted mobile development covers the generation half of the workflow, and how AI code review fits in the pipeline covers the review half. The seven-year maintenance story lives in maintaining a Flutter app for 7 years. The NCC App case study is the project behind all of it.
If your release process is the bottleneck on your mobile project — manual signing, ad-hoc test builds, releases that queue on one engineer’s laptop — that’s the problem I solve. My mobile CI/CD and release automation service is built around bringing this stack to your team, and a conversation is the cheapest way to find out if your project fits.
Frequently Asked Questions#
How do you automate Flutter releases to Google Play, the App Store, and Amazon? A self-hosted Drone pipeline builds the Android release and invokes Fastlane to sign and upload to the Google Play internal track and the Amazon live-testing group. Xcode Cloud builds the iOS leg and ships to TestFlight. A merge is the trigger; promotion to production is the only human gate.
What’s in a production mobile CI/CD stack? An orchestrator for Android build and deploy (I self-host Drone), Fastlane for store integration, Xcode Cloud for iOS, a cloud step for AI code review on every PR, and a chat notifier (Google Chat) for every pipeline transition. Tag-based test-build distribution lets anyone on the team self-serve a build.
Do I need a Mac to ship a Flutter iOS app? Yes. Apple requires macOS and a pinned Xcode to produce a signed App Store build. I use Xcode Cloud instead of self-hosting a Mac runner, because it keeps signing inside Apple’s trust boundary and removes the hardware-drift liability.
Self-hosted or cloud CI for a mobile app? Start on cloud until build volume makes per-minute pricing expensive, then move high-volume or secrets-sensitive workloads to self-hosted behind a Cloudflare Tunnel. Keep Xcode Cloud for iOS (Apple requires macOS) and a cloud step for AI review. Hybrid is the correct architecture for most mid-sized mobile teams.
How do you keep releases safe without slowing them down? Staged, reversible rollouts on every store — internal track, then closed testing, then a phased production release — with store-vitals monitoring and a one-command rollback, plus AI code review on every PR’s diff. The merge is fast; the promotion is the deliberate human gate.
