DEV Community

Cover image for Deploying Unity Builds to Google Play Store with Buildalon
Alon Farchy
Alon Farchy

Posted on

Deploying Unity Builds to Google Play Store with Buildalon

Getting your game onto the Google Play Store involves a complex dance of signing keys, version codes, and console configurations. Manually building an Android App Bundle (AAB), signing it, and uploading it to the Play Console every time you want to test a change is a recipe for burnout.

In this guide, we'll walk through how to automate your Unity builds for Android and deploy them directly to the Google Play Store internal testing track using GitHub Actions.

Table of Contents

Prerequisites

Before we start, make sure you have:

  1. Google Play Developer Account: You need to be able to access the Google Play Console.
  2. Working Unity Build: You should have a basic automated build pipeline set up. Check out our GitHub Actions Unity guide if you're starting from scratch.

Setup Overview

Automating Android deployments involves two key security components: Android Keystores (for Android Signing) and Service Accounts.

  1. App Registration: Create the app placeholder in Google Play Console.
  2. Authentication: We'll use Workload Identity Federation to securely authenticate GitHub Actions Android workflows with Google Cloud.
  3. Keystore: We need to securely provide our signing keystore to the build server.
  4. Build & Upload: We'll update the workflow to build an Android App Bundle (.aab) and upload it.

Setting up Google Play Console

First, let's make sure Google is expecting our app.

  1. Log in to Google Play Console.
  2. Click Create app.
  3. Fill in the app details (Name, Default language, Game/App type).
  4. Important: Manually build your APK/AAB in Unity and upload it to the Internal Testing track via the web interface. This initializes the track, sets the Package Name permanently, and allows you to answer the initial regulatory questions (Data Safety, etc.).

Configuring Google Cloud Access

To allow GitHub Actions to upload via the API, we need to link our Google Play account to a Google Cloud project. We will use a modern, secure method called Workload Identity Federation.

1. Enable the Google Play Android Developer API

  1. Go to the Google Cloud Console.
  2. Select or create a project for your game.
  3. Search for Google Play Android Developer API and enable it.

Google Cloud Play API

2. Create a Service Account

  1. In Google Cloud Console, go to IAM & Admin > Service Accounts.
  2. Click Create Service Account.

Google Cloud Service Accounts

  1. Name it something like unity-cicd-uploader and click Create and Continue.
  2. Skip the role assignment steps (click Continue then Done). This service account does not need specific Google Cloud permissions, only Google Play permissions.
  3. Copy the email address (e.g., unity-cicd-uploader@my-project.iam.gserviceaccount.com).
  4. Go back to Google Play Console > Users and permissions and invite this email. Google Play Console Users and Permissions
  5. In the permissions tab, grant it App Access to your game, and under Account permissions, ensure it has Releases to play store > Release to testing tracks (or Admin if you want full control). Google Play Console Release Options

3. Setup Workload Identity Federation

Next we need to give have the action runner authenticate with the service account we created. A secure way to do this is to use Workload Identity Federation, which procides an secure authentication scheme between GitHub Actions and Google Cloud.

  1. In Google Cloud Console, go to IAM & Admin > Workload Identity Federation.
    Google Cloud Workload Identity Federation

  2. Create a Pool (e.g., unity-cicd-pool).

  3. Create a Provider inside that pool:

    • Type: OpenID Connect (OIDC)
    • Provider name: unity-cicd-provider (or any descriptive name - you will reference it in the workflow)
    • Issuer (URL): https://token.actions.githubusercontent.com
    • Audience: sts.amazonaws.com (Standard default for GitHub Actions).
    • Provider attributes (attribute mapping):
    • google.subject = assertion.sub
    • attribute.repository = assertion.repository
    • attribute.ref = assertion.ref
    • attribute.actor = assertion.actor
    • Condition: Use a minimal repo lock like attribute.repository == "owner/repo", for example attribute.repository == "virtualmaker-net/proxima"
  4. After creating the provider, go back to the page for your pool and at the top click Grant Access. Select Grant access using service account impersonation and choose your service account created in the previous section. Set the principals to repository and the value to your owner/repo.
    Connect Workflow Identity Federation to Service Account

Handling the Keystore

Your Android Keystore is the identity of your app. If you lose it, you can't update your app. If it's stolen, someone else can update your app.

For CI/CD, we have two common approaches:

  1. Base64 Encode: Encode the .keystore file to a base64 string and store it as a GitHub Secret.
  2. Repo Storage: Commit the keystore file to your repository (if private) or encrypted.

Method 1: Base64 Encoding (Recommended)

This method keeps your keystore out of your repository entirely.

  1. Encode: Run one of the following commands to turn your file into a text string.

    macOS / Linux:

    openssl base64 -in user.keystore -out user.keystore.base64.txt
    

    Windows (PowerShell):

    [Convert]::ToBase64String([IO.File]::ReadAllBytes("user.keystore")) | Set-Content user.keystore.base64.txt
    
  2. Store: Copy the contents of the text file and add it as a secret named ANDROID_KEYSTORE_BASE64.

Method 2: Repo Storage

You can commit the keystore file to your repository (e.g. in Assets/Keys/user.keystore), but this is risky. If your repo is ever compromised or made public, your signing key is exposed. If you choose this route, ensure your .gitignore does not ignore .keystore files.

Regardless of the method, go to your GitHub Repository > Settings > Secrets and variables > Actions and add:

  • ANDROID_KEYSTORE_PASSWORD: The password for your keystore.
  • ANDROID_KEYALIAS_PASSWORD: The password for your key alias (often the same as the keystore, but not always).

Updating the Build Pipeline

Now let's update our workflow to include the Android build, Google Cloud authentication, and Play Store upload steps. We'll be using the following actions:

Create or update your .github/workflows/unity-build.yml file with the following configuration:

name: Deploy to Google Play

on:
  push:
    branches:
      - main

# Required for Workload Identity Federation
permissions:
  id-token: write
  contents: read

jobs:
  build-and-deploy:
    name: Build & Deploy to Google Play
    runs-on: buildalon-windows

    steps:
      # Checkout the repository
      - uses: actions/checkout@v6
        with:
          lfs: true

      # Setup Unity with Android build support
      - uses: buildalon/unity-setup@v2
        with:
          build-targets: Android

      # Activate Unity License
      - uses: buildalon/activate-unity-license@v2
        with:
          license: 'Personal'
          username: ${{ secrets.UNITY_EMAIL }}
          password: ${{ secrets.UNITY_PASSWORD }}

      # Add the Buildalon command line package to your Unity project
      - name: Add Build Pipeline Package
        working-directory: ${{ env.UNITY_PROJECT_PATH }}
        run: |
          npm install -g openupm-cli
          openupm add com.virtualmaker.buildalon

      # Decode base64 keystore from GitHub Secrets
      - name: Decode Keystore
        run: |
          echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | openssl base64 -d -out ${{ github.workspace }}/user.keystore

      # Build the Android App Bundle (.aab)
      - uses: buildalon/unity-action@v3
        name: Build Android AAB
        with:
          build-target: Android
          args: >-
            -quit -batchmode
            -executeMethod Buildalon.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild
            -keystorePath ${{ github.workspace }}/user.keystore
            -keystorePass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}"
            -keyaliasName "my-key-alias"
            -keyaliasPass "${{ secrets.ANDROID_KEYALIAS_PASSWORD }}"
            -appBundle
          log-name: Android-Build

      # Authenticate with Google Cloud using Workload Identity Federation
      - uses: google-github-actions/auth@v3
        with:
          # The service account email from Google Cloud
          service_account: unity-cicd-uploader@my-project.iam.gserviceaccount.com
          # The full resource name of the Workload Identity Provider
          workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/unity-cicd-pool/providers/unity-cicd-provider

      # Java is required by the Play Console upload tool
      - uses: actions/setup-java@v5
        with:
          distribution: temurin
          java-version: 21

      # Upload the AAB to the Google Play Internal Testing track
      - uses: RageAgainstThePixel/upload-google-play-console@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

          # Directory containing the .aab file
          release-directory: ${{ env.UNITY_PROJECT_PATH }}/Builds/Android

          # The track to upload to (internal, alpha, beta, production)
          track: internal

          # Draft status lets you review before rolling out
          status: draft

          # Optional release notes
          metadata: |
            {
              "releaseNotes": {
                "language": "en-US",
                "text": "${{ env.RELEASE_NOTES }}"
              }
            }
Enter fullscreen mode Exit fullscreen mode

Key Steps Explained

  • Decode Keystore: Converts the base64-encoded keystore secret back into a file for signing. If you checked in your keystore, you can skip this step.
  • Build Android AAB: Runs the Unity build with keystore credentials and -appBundle to produce an .aab instead of an .apk. Replace my-key-alias with the alias you chose when creating your keystore in Unity (Project Settings > Player > Publishing Settings).
  • Authenticate with Google Cloud: Uses Workload Identity Federation to get a short-lived token. You need to replace two values:
    • service_account: The email address from step 2.5 above (visible on the Service Account details page in Google Cloud Console under IAM & Admin > Service Accounts).
    • workload_identity_provider: The full resource name of the provider. You can find this in IAM & Admin > Workload Identity Federation, click your pool, then click your provider, and copy the Default audience or Resource name value. It follows the format projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID. Your Project Number (not Project ID) is on the Google Cloud Console Dashboard or Project Settings page.
  • Upload to Play Console: Pushes the .aab to the internal testing track as a draft, so your team can test immediately.

NOTE: We are deploying to the internal track. This is usually the best place for automated builds so your team can test them immediately.

Deploying to Internal Testing

Once you push these changes, your workflow should:

  1. Build the Android App Bundle (.aab).
  2. Authenticate with Google Cloud.
  3. Upload the .aab to the Internal Testing track on the Play Console.

When the upload finishes, your testers in the Internal Testing email list will receive an update (or see it in the Play Store if they have it installed).

Common Gotchas

  • Android Version Code: Google Play requires a unique integer versionCode for every single upload. Ensure your build script increments this on every build (e.g., using ${{ github.run_number }}) in your Unity Android Build settings or build script.
  • Android Build Number: Similar to versionCode, the user-visible bundleVersion (or Unity App Version) should be updated to help testers identify which build they are on.
  • Play App Signing: When you create your app in the console, you likely opted into Play App Signing. This means Google manages the key used to sign APKs delivered to users. The keystore we configured above is your Upload Key. If you lose it, you can contact Google support to reset it, whereas the final signing key is safely stored by Google.
  • API Delays: Sometimes the Google Play API takes a few minutes to process a new build.
  • Permissions: If you get a 403 error, double-check that the Service Account email is added to the Google Play Console Users with the correct permissions and linked to the Google Cloud project.

Installing the Internal App and Inviting Testers

After your first successful upload to the internal track, you need to explicitly invite testers and share the opt-in link.

Invite Internal Testers

In the Google Play Console:

  1. Open your app and go to Testing > Internal testing.
  2. Open the Testers tab.
  3. Create an email list (or select an existing one) and add tester emails.
  4. Save the list and make sure it is attached to your internal test track.
  5. Copy the Opt-in URL from the internal testing page.

Share that opt-in URL with your testers. They must accept the invite using the same Google account you added to the tester list.

How Testers Install the Internal Version

  1. Tester opens the opt-in URL and taps Become a tester.
  2. Tester taps the Play Store link shown on that page.
  3. Tester installs the app (or updates it if already installed).

If the app is already installed from another source (for example, a local APK), the tester may need to uninstall it first if the signing key or package signature does not match the Play build.

For later releases, once users are opted in, they usually receive updates through the Play Store automatically (or via manual update if auto-update is disabled).

About Buildalon

Buildalon provides verified GitHub Actions and dedicated build infrastructure to streamline Unity development. Register for Buildalon to get support with your Unity build automation needs.

Top comments (0)