---
title: "Sign release builds only in CI · Agent Wranglers"
url: https://agentwranglers.se/playbook/mobile-apps/signing-and-release/sign-in-ci
updated: 2026-10-21
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Sign release builds only in CI

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Signing and release](https://agentwranglers.se/playbook/mobile-apps/signing-and-release.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers). Updated 2026-10-21.

- ID: MOB-8
- Level: Recommended
- Chapter: [Signing and release](https://agentwranglers.se/playbook/mobile-apps/signing-and-release)
- Rule: Build and sign every store upload in CI, with keys read from the secrets store at build time. No developer laptop signs a release.
- How: Give the CI job the upload keystore or the Apple distribution certificate as secrets, and pass passwords as environment variables. Keep `keystore.properties` and certificate files out of version control. Upload from the same job, with an API key that has the smallest role able to upload. The general method is in [build releases only in CI and verify them before deploy](https://agentwranglers.se/playbook/engineering/build-test-and-deploy/build-in-ci-and-verify). Keep the signing job away from builds of outside pull requests, as [keep secrets out of builds from strangers](https://agentwranglers.se/playbook/adversarial-testing/supply-chain-and-setup/ci-secrets) says.
- Check: A secret scanner finds no keystore, `.p12` or `.mobileprovision` file in the repository history. The last 10 store uploads each link to a CI run.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### Kotlin example

```kotlin
android {
    signingConfigs {
        create("release") {
            storeFile = file(System.getenv("UPLOAD_KEYSTORE_PATH"))
            storePassword = System.getenv("UPLOAD_KEYSTORE_PASSWORD")
            keyAlias = "upload"
            keyPassword = System.getenv("UPLOAD_KEY_PASSWORD")
        }
    }
    buildTypes { getByName("release") { signingConfig = signingConfigs.getByName("release") } }
}
```

### Plain text example

```ruby
lane :release_ios do
  api_key = app_store_connect_api_key(key_id: ENV["ASC_KEY_ID"], issuer_id: ENV["ASC_ISSUER_ID"], key_content: ENV["ASC_KEY"])
  match(type: "appstore", readonly: true, api_key: api_key)
  build_app(scheme: "FitTrack")
  upload_to_testflight(api_key: api_key)
end
```

## Related rules

- [Build releases only in CI and verify them before deploy](https://agentwranglers.se/playbook/engineering/build-test-and-deploy/build-in-ci-and-verify) in Engineering
- [Keep secrets out of builds from strangers](https://agentwranglers.se/playbook/adversarial-testing/supply-chain-and-setup/ci-secrets) in Security testing

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/mobile-apps/signing-and-release/sign-in-ci">Agent Wranglers mobile apps playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Let the store hold the signing key and back up the upload key](https://agentwranglers.se/playbook/mobile-apps/signing-and-release/signing-keys)
- Next: [Raise the build number on every upload](https://agentwranglers.se/playbook/mobile-apps/signing-and-release/build-numbers)
- [Signing and release](https://agentwranglers.se/playbook/mobile-apps/signing-and-release)
