{"id":49745,"date":"2025-06-21T13:24:18","date_gmt":"2025-06-21T13:24:18","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=49745"},"modified":"2025-06-21T13:24:18","modified_gmt":"2025-06-21T13:24:18","slug":"android-app-build-and-publishing-workflow-using-github-actions","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/android-app-build-and-publishing-workflow-using-github-actions\/","title":{"rendered":"Android app build and publishing workflow using GitHub Actions"},"content":{"rendered":"\n<p>To fully automate your Android app build and publishing workflow using <strong>GitHub Actions<\/strong>, we&#8217;ll break it into <strong>three main steps<\/strong>:<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 What This GitHub Action Will Do<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Build the APK<\/strong> using Gradle on a GitHub Actions runner.<\/li>\n\n\n\n<li><strong>Upload the APK to GitHub Releases<\/strong> as an asset (optional: GitHub Packages if you prefer).<\/li>\n\n\n\n<li><strong>Deploy the APK to Google Play Store &#8211; Internal\/Test Track<\/strong> using the <strong><code>r0adkll\/upload-google-play<\/code><\/strong> action.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcc1 Prerequisites<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. Play Store Setup<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Create a <strong>Google Play Console<\/strong> account.<\/li>\n\n\n\n<li>Upload the <strong>initial APK manually once<\/strong> to create the app entry.<\/li>\n\n\n\n<li>Create a <strong>Service Account<\/strong> with <strong>Release access<\/strong> and generate a <strong>JSON key file<\/strong>.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. GitHub Repository Secrets<\/h3>\n\n\n\n<p>Add these secrets to your repository settings:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Secret Name<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td><code>ANDROID_KEYSTORE_BASE64<\/code><\/td><td>Your keystore file, base64 encoded<\/td><\/tr><tr><td><code>ANDROID_KEYSTORE_PASSWORD<\/code><\/td><td>Keystore password<\/td><\/tr><tr><td><code>ANDROID_KEY_ALIAS<\/code><\/td><td>Key alias<\/td><\/tr><tr><td><code>ANDROID_KEY_PASSWORD<\/code><\/td><td>Key password<\/td><\/tr><tr><td><code>GOOGLE_PLAY_SERVICE_ACCOUNT_JSON<\/code><\/td><td>Contents of <code>service_account.json<\/code><\/td><\/tr><tr><td><code>RELEASE_SIGNING_ENABLED<\/code><\/td><td>true or false<\/td><\/tr><tr><td><code>PACKAGE_NAME<\/code><\/td><td>Your app\u2019s package name (e.g., <code>com.example.app<\/code>)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcdc GitHub Actions Workflow (<code>.github\/workflows\/android-release.yml<\/code>)<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: Android Release Pipeline\n\non:\n  push:\n    tags:\n      - <span class=\"hljs-string\">'v*'<\/span>  <span class=\"hljs-comment\"># Triggers on version tags like v1.0.0<\/span>\n\njobs:\n  build:\n    name: Build APK\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout Code\n      uses: actions\/checkout@v4\n\n    - name: Set up JDK\n      uses: actions\/setup-java@v3\n      with:\n        distribution: <span class=\"hljs-string\">'temurin'<\/span>\n        java-version: <span class=\"hljs-string\">'17'<\/span>\n\n    - name: Decode Keystore\n      <span class=\"hljs-keyword\">if<\/span>: env.RELEASE_SIGNING_ENABLED == <span class=\"hljs-string\">'true'<\/span>\n      run: |\n        <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"${{ secrets.ANDROID_KEYSTORE_BASE64 }}\"<\/span> | base64 -d &gt; my-release-key.keystore\n\n    - name: Build APK\n      run: .\/gradlew assembleRelease\n      env:\n        KEYSTORE_FILE: my-release-key.keystore\n        KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}\n        KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}\n        KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}\n\n    - name: Upload APK to GitHub Release\n      uses: softprops\/action-gh-release@v2\n      with:\n        name: <span class=\"hljs-string\">\"Release ${{ github.ref_name }}\"<\/span>\n        tag_name: ${{ github.ref_name }}\n        files: app\/build\/outputs\/apk\/release\/app-release.apk\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  upload_to_playstore:\n    name: Upload to Google Play\n    runs-on: ubuntu-latest\n    needs: build\n\n    steps:\n    - name: Download Release APK from GitHub\n      uses: actions\/download-artifact@v4\n      with:\n        name: app-release.apk\n\n    - name: Upload to Google Play (Internal)\n      uses: r0adkll\/upload-google-play@v1\n      with:\n        serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}\n        packageName: ${{ secrets.PACKAGE_NAME }}\n        releaseFiles: app\/build\/outputs\/apk\/release\/app-release.apk\n        track: internal  <span class=\"hljs-comment\"># use 'alpha', 'beta', 'production', or 'internal'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udccc Notes<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>This will <strong>auto-trigger when you push a tag like <code>v1.0.0<\/code><\/strong>.<\/li>\n\n\n\n<li><code>r0adkll\/upload-google-play<\/code> handles version codes and uploads the APK to the <strong>internal testing track<\/strong>.<\/li>\n\n\n\n<li>You can switch the track to <code>alpha<\/code>, <code>beta<\/code>, or <code>production<\/code> as needed.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd10 How to Encode Keystore<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">base64<\/span> <span class=\"hljs-selector-tag\">your-release-key<\/span><span class=\"hljs-selector-class\">.keystore<\/span> &gt; <span class=\"hljs-selector-tag\">keystore<\/span><span class=\"hljs-selector-class\">.txt<\/span>\n# <span class=\"hljs-selector-tag\">Copy<\/span> <span class=\"hljs-selector-tag\">contents<\/span> <span class=\"hljs-selector-tag\">and<\/span> <span class=\"hljs-selector-tag\">store<\/span> <span class=\"hljs-selector-tag\">in<\/span> <span class=\"hljs-selector-tag\">GitHub<\/span> <span class=\"hljs-selector-tag\">secret<\/span>: <span class=\"hljs-selector-tag\">ANDROID_KEYSTORE_BASE64<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>To fully automate your Android app build and publishing workflow using GitHub Actions, we&#8217;ll break it into three main steps: \u2705 What This GitHub Action Will Do \ud83d\udcc1 Prerequisites 1&#8230;. <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[2],"tags":[],"class_list":["post-49745","post","type-post","status-publish","format-standard","hentry","category-uncategorised"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49745","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=49745"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49745\/revisions"}],"predecessor-version":[{"id":49746,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49745\/revisions\/49746"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=49745"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=49745"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=49745"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}