Skip to main content

Android Release Builds Without Android Studio on Linux, Windows and CI

Produce a signed Android AAB for a Capacitor app with only the command line: keytool keystore, Gradle from a terminal, Play Console service account, and a Capgo Build cloud alternative that keeps the keystore off laptops.

Article credits

Martin Donadieu

Writer

Valeria

Reviewer

Jordan

Editor

Android Release Builds Without Android Studio on Linux, Windows and CI

Android Studio is a fine IDE, but it is not required to produce a store-ready Android build. The generated android/ folder in a Capacitor project contains a Gradle wrapper that builds and signs from any terminal. That matters for Linux servers without a display, for Windows machines where Studio is slow, and for CI.

This guide builds a signed AAB with the command line only, then shows the cloud variant with Capgo Build for teams that do not want keystores on laptops.

Prerequisites

  • JDK 21 (java -version prints 21).
  • Android command line SDK with platform-tools, build-tools and the platform matching your Capacitor version (API 36 for Capacitor 8, API 35 for Capacitor 7). The Linux guide and Windows guide cover installation.
  • ANDROID_HOME exported, or android/local.properties with sdk.dir=....
  • A Capacitor app with android/ generated by bunx cap add android.

1) Create the signing keystore

keytool is part of the JDK on every platform:

keytool -genkeypair -v \
  -keystore release.jks \
  -keyalg RSA -keysize 2048 -validity 10000 \
  -alias release

Answer the prompts and choose a store password and a key password. Store the file and both passwords in your password manager or secret vault now. Never commit release.jks.

Enable Play App Signing when you create the app in Play Console. Google then keeps the final signing key and your keystore becomes an upload key, which can be reset if lost.

2) Tell Gradle how to sign

Keep secrets out of build.gradle. Create android/keystore.properties (gitignored):

storeFile=/absolute/path/to/release.jks
storePassword=your-store-password
keyAlias=release
keyPassword=your-key-password

Then reference it in android/app/build.gradle:

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file("keystore.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    signingConfigs {
        release {
            if (keystorePropertiesFile.exists()) {
                storeFile file(keystoreProperties['storeFile'])
                storePassword keystoreProperties['storePassword']
                keyAlias keystoreProperties['keyAlias']
                keyPassword keystoreProperties['keyPassword']
            }
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled false
        }
    }
}

Add android/keystore.properties and *.jks to .gitignore.

3) Bump the version

Google Play rejects uploads with a versionCode it has seen before. Edit android/app/build.gradle:

defaultConfig {
    versionCode 12
    versionName "1.4.0"
}

Teams usually derive versionCode from CI run numbers or from the git commit count to avoid manual edits.

4) Build the AAB from the terminal

bun run build
bunx cap sync android
cd android
./gradlew bundleRelease

On Windows use .\gradlew.bat bundleRelease. The signed bundle is written to android/app/build/outputs/bundle/release/app-release.aab.

Want an APK for direct installs with adb? ./gradlew assembleRelease produces app-release.apk under outputs/apk/release/. Install it with adb install; the adb install guide covers the edge cases.

5) Verify the signature

$ANDROID_HOME/build-tools/36.0.0/apksigner verify --print-certs app/build/outputs/apk/release/app-release.apk

For an AAB, jarsigner -verify -verbose app-release.aab confirms the signature and the certificate fingerprint. Compare the SHA-256 with the one shown in Play Console under App integrity.

6) Upload to Google Play from the command line

Manual upload through Play Console works. For automation, create a service account in Google Cloud, grant it access in Play Console under Users and permissions, and download its JSON key. Then upload with the Google Play Developer API or a tool such as fastlane supply:

bundle exec fastlane supply --aab android/app/build/outputs/bundle/release/app-release.aab \
  --track internal --json_key play-service-account.json --package_name com.example.app

The first upload of a new app must be done manually in Play Console; the API only accepts builds for apps that already exist.

7) The cloud alternative: Capgo Build for Android

Everything above works, but it leaves a keystore on every machine that builds. Capgo Build keeps the keystore in an encrypted vault, builds from the prepared android/ project you upload, and can push the AAB to a Play track.

Save credentials once:

bunx @capgo/cli@latest build credentials save --platform android \
  --keystore ./release.jks \
  --keystore-alias "release" \
  --keystore-key-password "key-password" \
  --keystore-store-password "store-password" \
  --play-config ./play-service-account.json

Or let bunx @capgo/cli@latest build init --platform android guide you. Then every release is:

bun run build
bunx cap sync android
bunx @capgo/cli@latest build request com.example.app --platform android --build-mode release

Logs stream to the terminal. Add --output-upload to get a download link for the AAB or APK, and --ai-analytics to have a failed build explained automatically. The same command runs from Windows, Linux, macOS and CI. A GitHub Actions example lives in the Android build guide.

Because Capgo receives only the prepared native project, private npm registries and web build secrets stay in your environment.

8) Stop rebuilding for web changes

A signed native build is needed for plugin, permission, icon and Capacitor upgrades. For everything in dist/, Capgo Live Updates push the new web bundle directly to installed apps:

bunx @capgo/cli@latest bundle upload --channel production

That removes most Gradle runs from your week.

Troubleshooting

  • SDK location not found: set ANDROID_HOME or create android/local.properties.
  • Keystore was tampered with, or password was incorrect: wrong store password, or the file was corrupted by a CRLF conversion in Git. Mark *.jks as binary in .gitattributes if it must be versioned, which it should not be.
  • Unsupported class file major version: the JDK is newer or older than 21.
  • Play rejects the AAB: “signed with a different key”: Play App Signing expects your upload key. Use the keystore registered in Play Console, or request an upload key reset.
  • Build is slow on Windows: exclude ~/.gradle and the SDK from Defender, and keep the project on a short local path.
  • Out of memory in Gradle: add org.gradle.jvmargs=-Xmx4g to android/gradle.properties.

Summary

A Capacitor Android release needs a JDK, the SDK and the Gradle wrapper, not Android Studio. Create the keystore with keytool, sign through keystore.properties, build with bundleRelease, and upload with the Play API. When you want the keystore out of laptops and the same command on every OS, Capgo Build does the signing and upload from the prepared project.

Live updates for Capacitor apps

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

Latest from our Blog

Capgo gives you the best insights you need to create a truly professional mobile app.