ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-06-26Advanced

Hand Over Generation and Shipping, but Never the Signing Key — Designing Key Custody and Handover for an AI-Driven Pipeline

Even in an era where AI Studio and Antigravity take over everything from generation to internal-test shipping, the app signing key is in a class of its own. Lose it, and that app can never be updated again. As a solo developer who has run several apps for years, here is how I design key custody — separating the upload key from the app signing key, storing it, and planning the handover for the worst case.

AI Studio5Antigravity271Android23Google Play5app-dev42

Premium Article

A few years ago I nearly lost the keystore for one of my apps. The night before wiping an old Mac, I was looking over my backups and realized the .jks file wasn't in any of them. If I hadn't caught it that night, that app would have permanently lost any way to ship an update. Code can be regenerated. A signing key cannot.

Now that AI Studio and Antigravity connect generation to internal-test shipping in a single screen, most of the distribution pipeline can be handed to a machine. And it should be — those steps are reversible. But the signing key is different in kind. The key is the identity of the app itself; lose it and the app becomes something else. So in this article I'll write, as a design, how to handle the signing key inside an increasingly automated pipeline, and where a human keeps holding on.

First, split the key into two kinds

Google Play signing involves two keys of different natures. Load automation onto the pipeline while conflating them, and you'll place a key that must never be exposed within reach of the machine.

One is the app signing key. It signs the APK that finally reaches the user's device, and with Play App Signing, Google manages it. It is the root of the app's identity, and as a rule neither human nor machine touches it day to day. Not touching it is what keeps it safe.

The other is the upload key. It signs the AAB when you send it to the Play Console, and Google has it registered as your upload key. If CI sends builds automatically, the upload key is what signs them. The important point: the upload key can be re-registered if lost. Lose it, and you can swap in a new upload key through support. By contrast, in an old setup where you hold your own app signing key without Play App Signing, the moment you lose that key, updates are over.

That difference decides the line of automation directly.

Key typeConsequence of lossMay automation touch it?
App signing key (Play-managed)Held by Google; not touchedNo (you don't even have it)
Upload keyRecoverable by re-registrationYes, inject into CI (store it strictly)
Self-held app signing key (legacy)Updates impossible foreverNever; consider moving to Play signing

I myself moved every app I could to Play App Signing. Running automated distribution while holding a self-managed key felt like passing an irreversible risk through the pipeline every night. If you still have an old app you haven't moved, I'd settle that before strengthening automation.

Feed automated signing without leaving the key in plaintext

I said the upload key may be injected into CI. Even so, committing a .jks to the repo or hard-coding the password into a script is out of the question — all the more so when generative AI touches the code. Keep the key itself, and the password that opens it, outside the world of code.

Locally, put the key password in the Keychain or a secret store, and pass it to Gradle in a form that never shows the value directly.

// app/build.gradle.kts — don't bake key details into code; receive them from the env
import java.io.FileInputStream
import java.util.Properties
 
android {
    signingConfigs {
        create("release") {
            // Receive the path from an env var; never put the key file in the repo.
            val keystorePath = System.getenv("UPLOAD_KEYSTORE_PATH")
            if (keystorePath != null) {
                storeFile = file(keystorePath)
                storePassword = System.getenv("UPLOAD_KEYSTORE_PASSWORD")
                keyAlias = System.getenv("UPLOAD_KEY_ALIAS")
                keyPassword = System.getenv("UPLOAD_KEY_PASSWORD")
            }
        }
    }
    buildTypes {
        getByName("release") {
            signingConfig = signingConfigs.getByName("release")
        }
    }
}

In CI, store the key file as an encrypted secret in Base64, unpack it only into the job's temp area, and delete it reliably on exit. You confine the time the key sits on disk in plaintext to the few minutes of the build.

# GitHub Actions example — unpack the key temporarily, with cleanup as part of the gate
- name: Restore upload keystore
  env:
    KEYSTORE_B64: ${{ secrets.UPLOAD_KEYSTORE_B64 }}
  run: |
    echo "$KEYSTORE_B64" | base64 -d > "$RUNNER_TEMP/upload.jks"
    echo "UPLOAD_KEYSTORE_PATH=$RUNNER_TEMP/upload.jks" >> "$GITHUB_ENV"
 
- name: Build & sign AAB
  env:
    UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
    UPLOAD_KEY_ALIAS: ${{ secrets.UPLOAD_KEY_ALIAS }}
    UPLOAD_KEY_PASSWORD: ${{ secrets.UPLOAD_KEY_PASSWORD }}
  run: ./gradlew bundleRelease
 
- name: Shred keystore
  if: always()
  run: shred -u "$RUNNER_TEMP/upload.jks" 2>/dev/null || rm -f "$RUNNER_TEMP/upload.jks"

The cleanup with if: always() is the crux — make sure the key file doesn't survive even when the build fails. When I first let an AI agent run all the way to automated signing, the pattern I fixed first was this "pair unpack with delete." Minimizing the time the key is exposed narrows the path by which it could slip into generated code or a temp log.

One caution: shred or rm only removes the file. Whether you've accidentally printed the key or password to the build log is a separate check. A setup that pipes the signing step's stdout straight into the log can, rarely, leak internal details. It's worth confirming your log masking settings once.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
How to split the upload key from the app signing key, and draw the line between keys automation may touch and keys it never gets
Implementation steps for feeding a key to automated signing without leaving it in plaintext, both in CI and locally
An operating design for recovery and handover against three irreversibles: loss, leak, and the maker's absence
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-06-24
The Day Generation, Device, and Internal-Test Shipping Became One Step — What I Refused to Hand Over
AI Studio now turns a text prompt into a Kotlin/Compose app and carries it through the emulator, a real device, and Google Play's internal test track from one screen. Behind that convenience sits a question: how much of the moment of shipping do you hand to the machine, and what do you keep in your own hands? Here is where I draw the line as a solo developer running several apps, and the implementation that holds that boundary.
App Dev2026-06-26
Green in the Embedded Emulator, Broken on the First Real Device — Putting a Parity Gate on AI-Generated Compose Apps
AI Studio now generates Kotlin/Compose apps from a prompt, runs them in an embedded emulator, and pushes them to a real device over USB — all from one screen. Yet a screen that passed in the emulator can break the first time it lands on a real phone. As a solo developer running several apps, here is how I put a gate that catches device parity issues before they ship.
App Dev2026-06-25
When an AI Studio App Won't Install on My Phone Because the Signatures Don't Match
You generate a Kotlin/Compose app in AI Studio, push it to a real device over USB, and the install dies with INSTALL_FAILED_UPDATE_INCOMPATIBLE. Here is why a debug-signed build can't overwrite your Play release, and how to test it without losing your production app.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →