CI/CD & App Store Deployment

Automating Google Play Releases with Fastlane Supply

16 min Lesson 9 of 12

Automating Google Play Releases with Fastlane Supply

Manually uploading an Android App Bundle (AAB) to the Google Play Console, writing release notes, attaching screenshots, and promoting builds between tracks is tedious and error-prone. Fastlane Supply automates every one of those steps from the command line — or from inside your CI pipeline — so a single command delivers a production-ready release to any Play Store track.

What Is Fastlane and Supply?

Fastlane is an open-source automation toolkit for iOS and Android. It organises automation into lanes — named sequences of actions — defined in a single Ruby file called Fastfile. Supply is the Fastlane action (and standalone tool) that talks to the Google Play Developer API: it can upload APKs/AABs, manage metadata, localised release notes, screenshots, and promote builds between tracks (internal → alpha → beta → production).

Note: Supply communicates with Google Play via a service account JSON key file. You must create a service account in the Google Play Console, grant it the correct permissions (Release Manager or higher), and download the key before running any Supply commands.

Installing Fastlane

Fastlane is a Ruby gem. Install it with Bundler (recommended) so every developer on the team uses the same version:

Install Fastlane via Bundler

# In your Flutter project root
gem install bundler

# Create a Gemfile
bundle init

# Add Fastlane to Gemfile:
#   gem "fastlane"
bundle add fastlane

# Initialise Fastlane (creates android/fastlane/ directory)
cd android
bundle exec fastlane init

After initialisation you will have:

  • android/fastlane/Fastfile — lane definitions
  • android/fastlane/Appfile — app identifier and service-account key path
  • android/fastlane/supply/ (created on first supply init) — metadata, changelogs, and screenshots organised by locale

Configuring the Appfile and Service Account

The Appfile tells Supply which app to manage and where to find the Google credentials:

android/fastlane/Appfile

json_key_file("fastlane/google-play-key.json")   # path to service-account JSON
package_name("com.example.myapp")                 # your app package name
Warning: Never commit google-play-key.json to version control. Add it to .gitignore and inject it as a CI secret (environment variable or encrypted file) at build time.

Writing a Deploy Lane in the Fastfile

A Fastlane lane is a Ruby method block. The deploy lane below builds the release AAB with Gradle, then calls supply to upload it to the internal track together with localised release notes:

android/fastlane/Fastfile — deploy lane

default_platform(:android)

platform :android do

  desc "Build release AAB and upload to Play Store internal track"
  lane :deploy do |options|
    track = options[:track] || "internal"   # override with: fastlane deploy track:beta

    # 1. Build the signed release AAB via Gradle
    gradle(
      task:          "bundle",
      build_type:    "Release",
      project_dir:   "../android",           # path from fastlane/ to android/
      print_command: false                   # hide the full Gradle command in logs
    )

    # 2. Upload AAB + release notes to the chosen track
    supply(
      track:           track,
      aab:             lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH],
      release_status:  "draft",              # "draft" | "completed" | "halted"
      skip_upload_apk: true,                 # we are using AAB, not APK
      skip_upload_images:       false,
      skip_upload_screenshots:  false,
      metadata_path:   "fastlane/supply"     # changelogs + store listing metadata
    )

    UI.success("Build uploaded to Play Store track: #{track}")
  end

end

Run this lane locally with:

  • bundle exec fastlane deploy — uploads to the default internal track
  • bundle exec fastlane deploy track:beta — uploads to the beta track
  • bundle exec fastlane deploy track:production — uploads directly to production

Localised Release Notes and Metadata

Supply reads changelogs from the fastlane/supply/ directory. The folder structure mirrors Play Console locales:

Release notes directory layout

android/fastlane/supply/
  metadata/
    android/
      en-US/
        changelogs/
          default.txt       # "What's new" text for English
        title.txt
        short_description.txt
        full_description.txt
      ar/
        changelogs/
          default.txt       # Arabic release notes
        title.txt
        short_description.txt
        full_description.txt
      images/
        phoneScreenshots/   # 2-8 screenshots (PNG/JPEG, max 8 MB each)
        sevenInchScreenshots/
Tip: Run bundle exec fastlane supply init once to pull your existing Play Store metadata into the correct folder structure. From then on, edit the text files directly and let Supply push them on the next deploy.

Integrating the Lane into a CI Pipeline

The deploy lane integrates cleanly with any CI provider (GitHub Actions, Bitbucket Pipelines, GitLab CI, Codemagic). Below is a minimal GitHub Actions workflow that triggers on every push to main:

.github/workflows/deploy-android.yml

name: Deploy Android to Play Store

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Java 17
        uses: actions/setup-java@v4
        with:
          java-version: "17"
          distribution: "temurin"

      - name: Set up Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: "3.22.0"

      - name: Install dependencies
        run: flutter pub get

      - name: Set up Ruby and Bundler
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: "3.3"
          bundler-cache: true
          working-directory: android

      - name: Decode service-account key
        run: |
          echo "${{ secrets.GOOGLE_PLAY_KEY_JSON }}" \
            > android/fastlane/google-play-key.json

      - name: Decode keystore
        run: |
          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode \
            > android/app/release.keystore

      - name: Run Fastlane deploy lane
        env:
          STORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS:      ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD:   ${{ secrets.KEY_PASSWORD }}
        run: bundle exec fastlane deploy
        working-directory: android
Note: Store all secrets (GOOGLE_PLAY_KEY_JSON, KEYSTORE_BASE64, passwords) in your CI provider's encrypted secret store — never hard-code them in the workflow file or the Fastfile.

Promoting Builds Between Tracks

Once a build passes testing on the internal track, Supply can promote it without re-uploading the AAB:

Promote lane in Fastfile

lane :promote_to_beta do
  supply(
    track:             "internal",
    track_promote_to:  "beta",
    skip_upload_aab:   true,    # no new AAB needed for promotion
    skip_upload_apk:   true,
    skip_upload_images:      true,
    skip_upload_screenshots: true,
    version_code: google_play_track_version_codes(track: "internal").first
  )
end

Summary

Fastlane Supply removes every manual step from a Google Play release. You install Fastlane with Bundler, configure Appfile with a service-account key, define a deploy lane that calls gradle then supply, place localised release notes under fastlane/supply/metadata/, and invoke the lane from CI by injecting secrets at runtime. The result is a fully reproducible, auditable, one-command deployment pipeline that targets any Play Store track.