skip to content

You need to inject a publish version and a signing secret into a Gradle build from CI. How do you wire each, and why?

level: seniorimportance: should knowfreq 40%

answer

  1. build inputs → project properties
  2. ORG_GRADLE_PROJECT_ env vars in CI
  3. secret off the command line
  4. never commit secrets to gradle.properties
  5. providers.gradleProperty lazy read

basics

~10 s

Inject both as ORG_GRADLE_PROJECT_ env vars: ORG_GRADLE_PROJECT_version and ORG_GRADLE_PROJECT_signingKey. They become project properties, keep the secret off the command line, and need no -P args.

solid answer

~40 s

For a CI publish, both the version and the secret are build inputs, so I expose them as **project properties via environment variables**: `ORG_GRADLE_PROJECT_version` and `ORG_GRADLE_PROJECT_signingKey`. CI runners provide config and secrets as env vars, so this is a zero-glue mapping, and — critically — it keeps the signing key out of the command line, which would otherwise appear in `ps` listings and echoed logs. The build reads them lazily with `providers.gradleProperty("version")` / `providers.gradleProperty("signingKey")`, which is configuration-cache safe and fails fast if missing. I would NOT use `-PsigningKey=...` (leaks the secret) nor a checked-in `gradle.properties` (secrets must never be committed). For JVM/tool-level needs — say a proxy — I'd use `-D` or `systemProp.` instead, but for build-domain inputs like version and signing material, project properties via env vars are the right channel.

code

kotlin · 7 lines
kotlin
val version = providers.gradleProperty("version")
val signingKey = providers.gradleProperty("signingKey")

signing {
    useInMemoryPgpKeys(signingKey.get(), "")
    sign(publishing.publications)
}

go deeper

for a junior

Name ORG_GRADLE_PROJECT_ env vars as the way to inject build inputs in CI.

for a middle

Explain that these become project properties and keep secrets off the command line.

for a senior

Reason about namespace (project vs system) and channel (CLI/env/properties), and justify env vars over -P and over committed gradle.properties for secrets.

for a principal

Set org-wide policy: secrets only via CI secret store → ORG_GRADLE_PROJECT_ env vars; ban secret-bearing -P args and committed secrets; standardize lazy providers reads.

## Frame it by namespace and channel Two decisions: (1) which **namespace** does the value belong to — project property or system property? (2) which **channel** delivers it — command line, env var, or gradle.properties? A publish version and a signing secret are **build inputs your own scripts consume** → project-property namespace. The safest CI channel for that namespace is the environment-variable convention: ``` ORG_GRADLE_PROJECT_version=... ORG_GRADLE_PROJECT_signingKey=... ``` ## Why env vars beat command-line -P for secrets Command-line arguments are visible to any process via `ps`, and CI systems frequently echo the executed command into build logs. A `-PsigningKey=...` therefore risks leaking the key. Environment variables are not part of the visible command line and, when sourced from the CI secret store, are masked in logs. So: - **version** could go either way, but using the same env-var channel keeps the wiring uniform. - **signingKey** must use the env-var channel for hygiene. ## Why not checked-in gradle.properties A project `gradle.properties` is committed to VCS; putting a secret there leaks it permanently into history. It is fine for non-secret defaults only. ## Reading them in the build ```kotlin val version = providers.gradleProperty("version") val signingKey = providers.gradleProperty("signingKey") publishing { /* use version.get() */ } signing { useInMemoryPgpKeys(signingKey.get(), /* passphrase */ "") } ``` `providers.gradleProperty` returns a lazy `Provider<String>`; calling `.get()` on a missing value fails the build clearly, which is what you want in CI. ## When system properties (-D / systemProp.) are the right tool instead If the value is consumed by the JVM or a tool — proxy host, trust store, a library that reads `System.getProperty` — use `-D` (one-off) or `systemProp.` in `~/.gradle/gradle.properties` (persistent), not a project property. Matching the value to its namespace is the senior-level judgment this question probes. ## GitHub Actions example ```yaml - run: ./gradlew publish env: ORG_GRADLE_PROJECT_version: ${{ github.ref_name }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} ```

  • Could you pass the version with -Pversion=... instead?
    Yes for the non-secret version it's fine, but the signing key must not go on the command line. Using the env-var channel for both keeps wiring uniform and safe.
  • Why not store the signing key in the project's gradle.properties?
    That file is committed to version control, so the secret would leak into history permanently. gradle.properties is only for non-secret defaults.
  • If the build needed a corporate proxy host instead, which mechanism would you use?
    A system property: -Dhttp.proxyHost or systemProp.http.proxyHost in ~/.gradle/gradle.properties — not a project property, because it's JVM/tool-level config.

saying these in an interview costs you the question

  • Passing the signing secret as a -P command-line argument.
  • Committing the secret to a checked-in gradle.properties.
  • Treating JVM/tool config (proxy, TLS) as project properties or vice versa.

context