Tutorial
Building and Signing Android TWAs with GitHub Actions
October 21, 2026 · 9 min read
When converting a Progressive Web App (PWA) to an Android application using a Trusted Web Activity (TWA), the initial compilation is usually done on a local developer machine. However, as your application grows, relying on local manual builds becomes highly inefficient. Whenever you update your native splash screen, change your app icon, adjust permission configurations, or target a new Android SDK level, you must rebuild and resign your app.
A Continuous Integration and Continuous Deployment (CI/CD) pipeline solves this problem. By using GitHub Actions, you can automate the process of building, signing, and exporting your production-ready Android App Bundle (AAB) and testing Android Package (APK) files every time you push code. This guide details exactly how to build a robust, secure automation pipeline for your TWA.
Why Automate Your TWA Build Pipeline?
Automating your TWA builds removes human error from the release process. Android app generation requires a specific Java Development Kit (JDK) version, Android SDK command-line tools, and Gradle. Managing these local environments across a development team often leads to version mismatches and broken builds.
Furthermore, Google Play requires all new apps to be submitted as Android App Bundles (AAB), while developers still need traditional APKs for local sideloading and testing. A CI/CD pipeline can generate both formats simultaneously, sign them with your secure production keystore, and upload the build artifacts automatically without exposing your private credentials.
Step 1: Preparing Your Android Keystore for CI/CD
Before you can automate the build, you must address Android code signing. To publish on Google Play, your AAB must be signed with a valid private key. To put this key into GitHub Actions, you cannot simply upload your keystore file (usually named release.jks or my-release-key.keystore) directly to a public repository, as this would compromise your app security.
Instead, you must convert your binary keystore file into a base64-encoded text string. This text string can then be safely stored in GitHub Secrets and decoded dynamically during the runtime of your automated workflow.
To generate the base64 string on your local machine, run the appropriate terminal command. On macOS or Linux, use: base64 -i release.jks -o keystore_base64.txt. On Windows PowerShell, use: [Convert]::ToBase64String([IO.File]::ReadAllBytes("release.jks")) | Out-File keystore_base64.txt. Copy the resulting text content of the generated file.
Step 2: Configuring GitHub Secrets
To keep your signing keys secure, you must store your configuration as Encrypted Secrets inside your GitHub repository. Navigate to your repository settings on GitHub, select Secrets and Variables, then choose Actions.
| Secret Name | Description | Example Value |
|---|---|---|
| ANDROID_KEYSTORE_BASE64 | The entire base64 encoded text string of your .jks file. | U3VwZXJTZWN1cmV... |
| ANDROID_KEYSTORE_PASSWORD | The password protecting your master keystore file. | your_keystore_password |
| ANDROID_KEY_ALIAS | The alias name given when generating the signing key. | my-twa-alias |
| ANDROID_KEY_PASSWORD | The password specifically for the alias key (often same as keystore). | your_alias_password |
Storing these variables as secrets prevents them from being leaked in public build logs, as GitHub automatically redacts any matched secret values in the console output.
Step 3: Creating the GitHub Actions Workflow
To instruct GitHub Actions how to build your TWA, you must create a configuration file inside your repository. Create a new directory path named .github/workflows and create a file named android-twa-build.yml inside it.
The workflow structure begins by defining when the automation should trigger. Setting it to run on pushes to your main branch, or manually via the workflow_dispatch trigger, is standard practice. The process executes on a hosted virtual machine running the latest Ubuntu environment.
The Setup Steps
First, the runner must check out your repository code using the checkout action. Next, because Android building requires Java, you must set up the Java Development Kit on the runner. For modern TWA builds, setting up JDK version 17 or higher using the setup-java action is recommended.
Decoding the Keystore
Once Java is ready, the runner must reconstruct your binary keystore file from the base64 secret you stored in Step 2. The pipeline runs a shell script to decode the base64 secret back into a physical file on the runner. This is achieved using the command: echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > app/release.jks, passing the environment variable securely.
Building the AAB and APK
With the environment configured, the runner executes the Gradle wrapper script included in your Android project. To build a clean, optimised production package, the workflow executes the clean and bundleRelease tasks. If you also need a testing APK, you can append the assembleRelease task. This produces the necessary compilation files in the build output directories.
Step 4: Signing the Output Files
While some custom Gradle configurations sign the app automatically during compilation, you can also sign the files as an explicit step in your pipeline. This keeps your build scripts cleaner and decoupled from hardcoded credentials.
You can use a dedicated signing action from the GitHub marketplace or utilize the standard Android SDK build tools already installed on the runner. Using the apksigner tool, the pipeline signs both the AAB and APK files using the decoded keystore file and the passwords retrieved from your GitHub Secrets. This ensures the output is fully verified and ready to pass the strict verification checks of the Google Play Console.
Step 5: Archiving and Uploading the Artifacts
The final step in your pipeline is to extract the signed files from the virtual machine runner so you can download them. You use the upload-artifact action to save your generated files. You specify the paths pointing to your signed AAB and APK files, which are located in the build outputs directory.
Once the workflow completes, you can navigate to the Actions tab of your GitHub repository, click on the successful run, and download the ZIP file containing your signed production AAB. This is the exact file you upload to the Google Play Console, which requires a one-off 25 USD registration fee to set up your developer account.
Verifying Your Output Fingerprint
After your automated pipeline outputs your signed app, you must verify that the SHA-256 fingerprint of the CI/CD generated app matches your Digital Asset Links configuration. If the pipeline signs your app with a key different from your original local builds, your app will show the browser URL bar on user devices.
To check the fingerprint of your generated APK, you can run the keytool command against the signed file. Ensure this SHA-256 matches the fingerprint declared on your website in your /.well-known/assetlinks.json file. Once confirmed, your automated build pipeline is fully operational, allowing you to update your app wrapper securely and seamlessly with every push of your code.
Ready to ship your Android app?
Paste your PWA URL, get a signed APK and a Google Play ready AAB in minutes.
Build my app