How to Turn a GitHub Repo into a Signed Android App (Flutter, Gradle, or Capacitor)
If your Android project lives in a Git repo — a Flutter app, a native Gradle project, or a Capacitor project with an android/ folder — you can go from repo URL to a signed APK and AAB without installing anything. Connect the repo (or upload a ZIP of it) to a cloud source build, and the service clones it into a clean workspace, runs ./gradlew assembleRelease on a toolchain matched to your project's era, signs the output with your keystore, and hands back the binaries. The source is deleted after the build.
That's the whole answer. The rest of this post is the part that actually matters: why “just clone it and build” fails so often, what the pipeline really does to your repo, and when GitHub Actions is honestly the better choice.
The real problem: “just run ./gradlew” assumes a machine you don't have
There's a whole genre of forum thread — XDA has been hosting them for a decade, and GitHub's own community discussions get the same question — that goes: “this repo has no Releases page and no APK anywhere. How do I get the app on my phone?” The README usually answers with one breezy line: clone and run ./gradlew assembleRelease.
Here's what that one line actually requires on your machine: a JDK — and not just any JDK, the major version the project's Gradle can parse; the Android SDK with the right platform and build-tools installed and licenses accepted; and a Gradle version compatible with the project's Android Gradle Plugin version. Get any pair wrong and you don't get a helpful message, you get things like:
That last one deserves a special mention because it's pure Git hygiene, not code. A lot of repos carry a global *.jar ignore rule, which silently excludes gradle/wrapper/gradle-wrapper.jarfrom the repo. The project builds fine on the author's machine (the jar exists locally) and breaks for literally everyone who clones it.
The standard advice at this point is “set up GitHub Actions.” That works — we'll compare it honestly below — but be clear about what you're signing up for: writing a workflow YAML, base64-encoding your release keystore into a repository secret plus three more secrets for the passwords and alias, adding a decode step and a signing step, and then living with CI mechanics: artifacts require a logged-in GitHub account with read access to download, they expire (90 days by default), and private repos burn through a free-minute quota (2,000 Linux minutes a month on the free plan, as of mid-2026). None of that is hard for a CI person. All of it is overhead if what you wanted was one binary from one repo.
What actually happens when you connect a repo
Code2Native's source build is the other path: you hand it the repo, it hands you binaries. Mechanically, here is what runs — no magic claimed, just a pipeline we operate so you don't have to:
- Clone into an ephemeral workspace. Each build gets a clean, isolated environment. When the build finishes, the workspace and your source are deleted — the code is not retained.
- Detect the project type.
pubspec.yamlmeans Flutter;capacitor.config.*plus anandroid/folder means Capacitor;settings.gradleat the root means native Android. - Match the toolchain to the project's era. This is the part that makes old repos build. The pipeline reads the pins your repo already carries —
gradle-wrapper.properties, the AGP version, the Flutter SDK constraint in pubspec — and selects a build image with a Flutter, Gradle, and JDK combination from that era. An older project pinned to Gradle 7.x / AGP 7.x compiles against a matching-era image instead of failing against today's AGP 8 and JDK 21. Locally you'd have to downgrade your own machine to do this. - Auto-repair the common walls. The known failure classes from the terminal above get fixed mechanically: a missing Gradle wrapper is synthesized, AGP and Kotlin versions below the working floor get bumped, the AGP 8
namespacerequirement is filled in from your manifest's package, and the JDK is chosen to match (the JDK 17 matrix catches most modern projects). These are deterministic fixes for deterministic errors — not an AI rewriting your code. - Run the build command. The default for Android is
./gradlew assembleRelease(Flutter projects go throughflutter buildfor both APK and app bundle). You can override this per platform. - Sign and return. Your release keystore signs the output — APK for sideloading and testing, AAB for Google Play.
The override lives in a code2native.json at the repo root. It exists to keep builds deterministic — one declared command, one declared artifact directory, no interactive prompts, no guessing:
If your repo doesn't have one, the default command runs — most single-app repos never need the file. Where it earns its keep is monorepo-ish layouts and projects whose release task isn't the standard one (a bundleRelease-only setup, a flavor like assembleProdRelease, a pre-build codegen step).
How signing keys work (and why the keystore never goes in the repo)
Signing is where most first-time Android publishers get hurt, so here is the model plainly. Android updates are only accepted if the new binary is signed with the same key as the installed one. Lose the key, lose the ability to update. That has two practical consequences for a Git-based build:
- Never commit the keystore. A
.jksfile plus its passwords in a repo is a compromised key. This is also why the GitHub Actions route makes you base64 the keystore into secrets — the repo itself must stay clean. - The key must be stable across builds. On Code2Native you upload your release keystore once per project and every subsequent build of that project signs with it, so version 2 installs cleanly over version 1. If you don't have a keystore yet, one is generated for you and attached to the project the same way. Either way you get both artifacts per build: APK for direct installs, AAB for Play.
Repo to APK, step by step
Check the repo builds deterministically
The project root should contain android/ (Flutter/Capacitor) or settings.gradle(native). No interactive prompts in the build, no secrets committed. If the release task isn't standard, add code2native.json.
Create a project and pick Source Code Build
In the wizard, choose Source Code → Git and paste the repo URL and branch. Private repo? Use a read-only access token. Prefer not to connect Git at all? A ZIP of the project root works identically — that's the flow the Flutter APK online guide walks through.
Attach signing
Upload your release keystore (alias + passwords), or let the platform generate one and keep it attached to the project for every future build.
Build
The pipeline clones, detects, era-matches, repairs if needed, and runs the build command. You watch the same Gradle output you'd see locally, streamed as logs.
Download APK + AAB
Sideload the APK onto a device immediately; upload the AAB to Google Play when you're ready. One build costs one credit, and a failed build refunds it automatically.
Honest comparison: this vs GitHub Actions vs Codemagic vs local
Competitor and platform details are accurate as of mid-2026 — verify current numbers before committing to any of them.
| Factor | Code2Native source build | GitHub Actions | Codemagic | Local setup |
|---|---|---|---|---|
| Setup before first signed binary | Connect repo + keystore | Workflow YAML + 4 secrets + signing steps | codemagic.yaml or workflow editor | SDK + JDK + Gradle install |
| Old-project handling | Era-matched toolchain + auto-repair | You pin every version yourself | You pin versions in YAML | Downgrade your machine |
| Builds on every commit (real CI) | ||||
| Artifact access | Direct download from dashboard | GitHub login + repo read access; ~90-day expiry | Dashboard / links | It's on your disk |
| Cost model | 1 credit/build; first build free; $29/mo = 10 credits | Free for public repos; 2,000 min/mo free private | 500 free macOS mins/mo (personal); Linux paid ~$0.045/min | Free, paid in your time |
| Keystore handling | Uploaded once, reused per project | Base64 into secrets, decode in workflow | Encrypted env vars / UI upload | Local file you must not lose |
| Maintenance over time | None (pipeline is operated for you) | Actions/runner updates break YAML occasionally | YAML upkeep | SDK rot every few months |
Read that table fairly and two conclusions fall out. If you ship from this repo continuously— every merge should produce a build, you want test runs, you live in GitHub anyway — GitHub Actions is the right tool, and for a public repo it's free. The signing setup is an afternoon once, then it runs. Codemagic occupies a similar space with friendlier mobile defaults and 500 free macOS minutes a month on personal accounts. The cloud source build wins the other case: you need a signed binary from arepo — yours, a client's, an open-source project with no releases, a two-year-old codebase your laptop's toolchain no longer speaks — and you don't want to adopt a CI system to get it.
When NOT to use this
Your repo is React Native, Ionic, or Expo
As of mid-2026 those source types are on the waitlist, not live — React Native, Ionic, and Expo repos can't be built here today. Read the React Native cloud build rundown for the current state and the alternatives that do handle RN well (EAS in particular). A Capacitor repo with a committed android/folder is live, though — if you're weighing that route, here's the honest Capacitor vs native comparison.
You already have working CI
If your Actions workflow builds and signs today, keep it. Migrating a working pipeline to save YAML you've already written buys you nothing.
The build needs your infrastructure
Builds that pull from private package registries behind a VPN, exotic monorepo orchestration (Bazel and friends), or custom NDK toolchains are CI-shaped problems. A deterministic ./gradlewinvocation in a clean workspace is the contract here; if your build can't satisfy it, self-hosted runners are the better fit.
FAQ
Does my repo need a GitHub Actions workflow file?
No. The pipeline never reads .github/workflows/ — it clones the repo and runs the build command directly (default ./gradlew assembleRelease, or whatever your code2native.json declares). Existing CI config is simply ignored, so connecting a repo changes nothing about your current setup.
Can I build someone else's public repo — an open-source app with no releases?
Yes, and it's one of the most common uses: fork or reference the public repo, connect it, and sign the output with your own keystore. Keep the project's license in mind — building for personal use is almost always fine, redistributing the binary depends on the license terms.
What happens to my source code after the build?
The workspace is ephemeral: source is cloned for the build and deleted afterwards. What persists is what you need for continuity — your project settings, your signing keystore (so updates keep installing over previous versions), and the built artifacts for you to download.
My project fails to build on my own machine. Why would it build here?
Because most local failures aren't code errors — they're toolchain mismatches. The pipeline selects a Flutter/Gradle/JDK image era from your project's own pins and mechanically repairs the recurring walls: a synthesized Gradle wrapper when the jar was gitignored, AGP/Kotlin bumped to a working floor, the AGP 8 namespace filled in, JDK 17 where required. Genuine compile errors in your Dart/Kotlin/Java are still yours to fix — the logs show the exact command and output, and a failed build auto-refunds its credit.
What about iOS from the same repo?
Limited, and worth stating precisely: on Pro and up, cloud builds on Apple silicon can produce an unsigned .ipa from source that you re-sign with your own Apple Developer certificate and submit yourself. Android is the fully-signed, fully-automated path; treat iOS-from-source as a head start, not a turnkey deliverable.
What does it cost?
The first build is free (one credit, watermarked). Starter is $29/month for 10 build credits, Pro $99/month for 30, Agency $199/month for 100. One build consumes one credit; failed builds refund automatically. There's a $49 urgent option if you need a build prioritized, and payment works with a card via Stripe or with USDT.
Point it at a repo and see
The first build is free, so the cheapest way to evaluate this is empirical: connect the repo that's been refusing to build locally and read the logs. Either you get a signed APK, or you get the exact error and your credit back.
Code2Native Engineering
Engineering team
Written by the Code2Native engineering team — the people who build and operate the cloud build pipeline.