Automating Android Play Store Releases, Part 1: Signing & Version Drift
Part 1 of a 5-part series on automating a multi-app Android release pipeline — from a free-tier build queue to a genuine self-hosted infrastructure migration. This part covers the setup and the first two real incidents. Part 2 → covers a Play Store error that lied about its own cause, a one-line bug that invalidated an entire workflow file, and separating "merged" from "released." Part 3 → and Part 4 → cover a real Actions storage-quota crisis and what it took to fix it for good. TL;DR We ship two separate Android apps (staff and student/parent) from one Expo React Native codebase , distinguished by an env var at build time. EAS's free build tier kept running out mid-sprint, and running builds locally instead was frying a developer's laptop for twenty minutes at a time — so the goal was to move Android builds and Play Store releases off EAS entirely and onto GitHub Actions. Two things nearly derailed that before the pipeline shipped anything real: EAS's free build tier ran out , and building locally doesn't scale as a substitute. A gitignored local counter decided our version numbers — it doesn't survive a fresh CI runner, so we silently shipped v1.0.1-b2 when we meant a release several versions ahead. Google Play's versionCode must strictly increase per app, across every track — not per track. An old, forgotten upload on an unrelated track can block every future release until you exceed it. Play Console's display name isn't the real number. A release literally labeled "(9)" had an actual enforced versionCode of 6 — a human-typed label, not something Play cross-checks against reality. If any of that made you wince in recognition, keep reading. The Setup: Two Apps, One Codebase, One Free-Tier Ceiling Cyfamod builds a school management platform. Two of its Android apps — one for staff, one for students and parents — ship from a single Expo React Native codebase, switched at build time by a route env var. Same components, same hooks, same infra. Two separate .apk / .aa