Design a reliable, automated CI pipeline that publishes a library to Maven Central on a tagged release. What are the moving parts and failure modes?
answer
- tag-triggered, version from tag
- secrets: GPG key+passphrase, Central token
- release profile: gpg sign + central plugin autoPublish
- loopback pinentry, batch mode
- fail modes: javadoc, expired key, duplicate version
basics
~20 sOn a version tag, CI builds signed artifacts (jar/sources/javadoc/pom + GPG .asc) with full metadata, then uses central-publishing-maven-plugin to upload and auto-publish. Secrets (GPG key, passphrase, Central token) come from the CI secret store, not the repo.
solid answer
~40 sTrigger on a semver tag (e.g. `v1.2.0`). The job: checkout, set up JDK + GPG, import the ASCII-armored private key and trust it, set the version (drop -SNAPSHOT or derive from the tag), then `mvn -P release clean deploy`. The `release` profile activates maven-gpg-plugin (signs jar/sources/javadoc/pom) and central-publishing-maven-plugin (`publishingServerId=central`, `autoPublish=true`). Credentials are injected: Central user token via `settings.xml` generated from secrets (or `-Dcentral.username/-Dcentral.password`), GPG passphrase via `MAVEN_GPG_PASSPHRASE` with `--pinentry-mode loopback`. Guardrails: ensure javadoc actually builds (common failure), keys aren't expired, version is unique (immutability — re-tagging the same version fails), and the build is reproducible. Make the publish idempotent-ish by failing fast on duplicate versions, and keep a manual-approval gate for first releases. Notify on success/failure and record the released coordinates.
code
bash · 6 lines# CI release step (tag-triggered)
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
mvn -B -P release -s ci-settings.xml \
-Dgpg.passphrase="$GPG_PASSPHRASE" \
-Dgpg.args=--pinentry-mode=loopback \
clean deploygo deeper
Understands the build must run in CI and use secrets, not committed credentials.
Can wire the release profile, settings.xml secrets, and a tag trigger.
Designs for the common failure modes (javadoc, key expiry, duplicate version) and non-interactive signing.
Owns end-to-end supply-chain trust: token/key rotation, provenance/SBOM, approval gates, and org-wide release governance.
## Goal A hands-off, auditable release: pushing a version tag yields a signed, validated artifact on Maven Central, with no secrets in the repo. ## Moving parts 1. **Trigger** — a tag matching `v*` (or a manual `workflow_dispatch`). Derive the version from the tag to avoid drift. 2. **Build environment** — pinned JDK; `gpg` available; Maven with a generated `settings.xml`. 3. **Secrets** (from the CI secret store): - `GPG_PRIVATE_KEY` (ASCII-armored), `GPG_PASSPHRASE` - `CENTRAL_TOKEN_USER`, `CENTRAL_TOKEN_PASS` (Portal user token) 4. **Signing** — import the private key, then maven-gpg-plugin `sign` in `verify`. 5. **Publishing** — central-publishing-maven-plugin with `autoPublish=true`. 6. **Metadata** — name/description/url/license/scm/developers present (ideally enforced by CI lint before deploy). 7. **Verification + notification** — confirm the deployment validated; alert the team. ## Example settings.xml (generated in CI) ```xml <settings> <servers> <server> <id>central</id> <username>${env.CENTRAL_TOKEN_USER}</username> <password>${env.CENTRAL_TOKEN_PASS}</password> </server> </servers> </settings> ``` ## Example deploy step ```bash echo "$GPG_PRIVATE_KEY" | gpg --batch --import mvn -B -P release -s settings.xml \ -Dgpg.passphrase="$GPG_PASSPHRASE" \ clean deploy ``` ## Failure modes and mitigations - **Javadoc build fails** (strict doclint) → add `-Dmaven.javadoc.failOnError=false` deliberately or fix docs; it's the most common publish breakage. - **GPG key expired / not on keyserver** → rotate keys, publish public key, monitor expiry. - **Duplicate/immutable version** → re-tagging `1.2.0` after a release fails; enforce unique versions and never reuse. - **TTY/pinentry errors in CI** → use `--pinentry-mode loopback` and batch mode. - **Leaked secrets** → use the secret store, mask logs, scope tokens, rotate. - **Partial deploy** → with the Portal, a deployment is validated as a unit before publish; prefer `autoPublish` only once you trust validation, otherwise keep a manual approval gate. ## Governance - Protect the tag/release branch; require review. - Centralize metadata + plugin config in a shared parent POM. - Audit who can publish; rotate the Central token and GPG key on a schedule. - Consider provenance/SBOM and reproducible-build flags for supply-chain trust.
- What's the single most common reason a Central publish job fails mechanically?The javadoc jar fails to build (strict doclint or missing docs). Fix the docs or set maven.javadoc.failOnError appropriately; signing/metadata issues are next most common.
- How do you keep GPG and Central credentials out of the repository?Store them in the CI secret store, inject them as env vars or into a generated settings.xml at runtime, mask them in logs, and rotate them periodically.
- Why might you keep a manual approval gate even with autoPublish available?For first-time or high-risk releases, to let a human review the validated deployment before it becomes immutable and public; you can drop a validated-but-unpublished deployment, but never an already-released version.
saying these in an interview costs you the question
- Hardcoding the GPG passphrase or Central token in the POM or repo
- Reusing a version number across releases
- Ignoring javadoc build failures until the release breaks
- Letting the GPG key silently expire