Android installs only signed APKs, and an update must be signed by the same key as the installed app. With Play App Signing (Play App Signing) the key you hold is an upload key, which only proves to Google that an upload comes from you. Create it with Release Signing's keytool command; here it became ~/keys/booknest-kt-upload.jks, alias upload, with its password in a chmod 600 file outside the project. (JDK 17's keytool signed its certificate with SHA256withRSA, JDK 21's with SHA384withRSA.)
The Kotlin DSL version of the signing config reads the three values as Gradle 19,597 properties, which may come from ~/.gradle/gradle.properties on your machine or from environment variables named ORG_GRADLE_PROJECT_<name> on a CI runner (Signing and Versioning used the same mechanism):
signingConfigs {
val store = providers.gradleProperty("BOOKNEST_UPLOAD_STORE_FILE").orNull
if (store != null) create("upload") { // 5.38.2 the upload key, from outside the repo
storeFile = file(store)
storePassword = providers.gradleProperty("BOOKNEST_UPLOAD_PASSWORD").get()
keyAlias = providers.gradleProperty("BOOKNEST_UPLOAD_KEY_ALIAS").get()
keyPassword = providers.gradleProperty("BOOKNEST_UPLOAD_PASSWORD").get()
}
}
// and in buildTypes.release, after the R8 lines of Section 5.37.1:
signingConfig = signingConfigs.findByName("upload") // 5.38.2 when the key is given
?: signingConfigs.getByName("debug") // otherwise a local test buildWithout the properties, assembleRelease still works and signs with the debug key, which keeps Performance's benchmarks and every teammate's local release build running; Play rejects such an upload, so nothing debug-signed can reach users by accident. Android Studio 234 's Build > Generate Signed App Bundle / APK wizard does the same interactively; only the build file is repeatable in CI.