The Gradle signing plugin can obtain a PGP signing key in two main ways: useGpgCmd() and useInMemoryPgpKeys(...). What is the difference between these two approaches and when would you reach for each?
answer
- gpg binary + keyring + agent
- in-memory = armored key string + passphrase
- signing.gnupg.* properties
- useInMemoryPgpKeys keyId overload
- CI = in-memory env vars
basics
~10 suseGpgCmd() shells out to the local gpg binary and its keyring/agent. useInMemoryPgpKeys() takes the raw ASCII-armored key text and passphrase directly, so no gpg install or keyring is needed — ideal for CI.
solid answer
~40 sBoth configure the `signing` extension to find the PGP secret key, but they get it from different places. `useGpgCmd()` delegates to the installed **gpg** command-line tool. Gradle invokes `gpg` (honoring `signing.gnupg.*` properties like `keyName`, `executable`, `useLegacyGpg`), so the key lives in the local GnuPG keyring and is unlocked by the gpg-agent. Great on a developer machine that already has gpg set up. `useInMemoryPgpKeys(key, password)` (or the 3-arg variant with a keyId) takes the **ASCII-armored private key as a string plus its passphrase**, supplied directly — typically from environment variables or Gradle properties. No gpg binary, no keyring, no agent. This is the standard choice for **CI**, where you inject the armored key and passphrase as secrets. Rule of thumb: gpg agent for laptops, in-memory keys for headless/CI runners.
code
kotlin · 11 linessigning {
// CI path: key material injected as secrets
useInMemoryPgpKeys(
System.getenv("SIGNING_KEY"), // -----BEGIN PGP PRIVATE KEY BLOCK-----
System.getenv("SIGNING_PASSWORD"),
)
sign(publishing.publications["maven"])
}
// Local-dev alternative (not both):
// signing { useGpgCmd(); sign(publishing.publications) }go deeper
Name the two methods and the one-line difference: gpg binary vs key string in memory; in-memory is for CI.
Explain keyring/agent vs armored-string+passphrase, name the signing.gnupg.* properties and the in-memory overloads, and justify the CI choice.
Discuss secret injection (env vars, base64-encoding multiline keys), why headless runners favor in-memory, and the BouncyCastle-vs-external-gpg signer distinction.
Frame it as a key-management/governance decision: where signing keys live, rotation, who can sign releases, and standardizing the CI signing approach across many repos.
## Why signing needs a key at all When you publish artifacts to Maven Central, the repository requires every artifact (jars, POM, sources, javadoc) to carry a **detached PGP signature** (`.asc` file). Gradle's `signing` plugin produces those signatures, but to do so it must locate a **PGP secret key** and **unlock it with a passphrase**. The two methods below differ only in *where the key comes from* and *how it is unlocked*. ## Approach 1 — `useGpgCmd()` This tells the signing plugin to shell out to the external **GnuPG** binary (`gpg` / `gpg2`) instead of using Gradle's built-in BouncyCastle-based signer. - The secret key lives in your local **GnuPG keyring** (`~/.gnupg`). - The passphrase is supplied (and cached) by the **gpg-agent**, so Gradle never sees it directly. - You select the key and tweak behavior via project properties, all under the `signing.gnupg.` namespace: - `signing.gnupg.keyName` — the key id / fingerprint to use - `signing.gnupg.executable` — path to a specific gpg binary - `signing.gnupg.useLegacyGpg` — true if you must call gpg 1.x - `signing.gnupg.homeDir`, `signing.gnupg.optionsFile`, `signing.gnupg.passphrase` Best when a real human's workstation already has gpg + a configured keyring + agent. The agent handles passphrase prompting/caching, which is convenient and keeps the secret out of build scripts. ## Approach 2 — `useInMemoryPgpKeys(...)` Here you hand Gradle the **ASCII-armored** secret key material and passphrase directly; Gradle's own (BouncyCastle) signer does the work. No gpg install, no keyring, no agent. Three overloads exist: - `useInMemoryPgpKeys(secretKey, password)` — the full ASCII-armored key block + passphrase. - `useInMemoryPgpKeys(keyId, secretKey, password)` — same, plus an explicit subkey id (the short/long key id) when the armored block contains multiple keys and you must pick one. - A 4-arg overload also exists in newer versions for the default-keyring-id case. The **ASCII-armored** format is the text block that starts with `-----BEGIN PGP PRIVATE KEY BLOCK-----`. You usually export it once (`gpg --armor --export-secret-keys KEYID`) and store it as a CI secret. Because env vars often can't hold raw newlines, teams frequently base64-encode the armored key and decode it in the build, or rely on the CI system's multiline-secret support. ## Why in-memory is the CI default CI runners are ephemeral and headless: installing gpg, importing a key into a keyring, and unlocking it via an agent on every build is brittle. Injecting the armored key + passphrase as two environment variables and feeding them to `useInMemoryPgpKeys` is deterministic and stateless. ```kotlin signing { val signingKey: String? = System.getenv("SIGNING_KEY") // ASCII-armored val signingPassword: String? = System.getenv("SIGNING_PASSWORD") useInMemoryPgpKeys(signingKey, signingPassword) sign(publishing.publications) } ``` ## Choosing - **Local dev with an existing gpg setup** → `useGpgCmd()` (let the agent manage the secret). - **CI / headless / reproducible** → `useInMemoryPgpKeys(...)` with env-injected armored key + passphrase. They are mutually exclusive for a given build — call one or the other, not both.
- Which approach requires the gpg executable to be installed on the build machine?useGpgCmd() — it shells out to the external gpg binary and uses its keyring/agent. useInMemoryPgpKeys() uses Gradle's built-in BouncyCastle signer and needs no gpg install.
- When would you use the 3-argument useInMemoryPgpKeys(keyId, secretKey, password) overload?When the armored secret-key block contains more than one (sub)key and you must explicitly select which one signs, by passing its short/long key id.
useGpgCmd() is like asking the building's locksmith (gpg-agent) to open the vault — they hold the key. useInMemoryPgpKeys() is handing the build the key itself in an envelope (the armored text) plus the combination (passphrase).
saying these in an interview costs you the question
- Saying useInMemoryPgpKeys still needs gpg installed — it does not.
- Claiming you can use both useGpgCmd() and useInMemoryPgpKeys() together for one build.
- Confusing the public key with the secret key — signing needs the ASCII-armored secret/private key.