CI/CD Build Patterns, Caching, Test Sharding, Artifact Promotion, and Ephemeral Build Agents: Concepts, Architecture, and Mental Model
Move from a reliable local build to a reliable pipeline. Learn which state an ephemeral CI agent may restore, which identities must be re-proved on every run, how test shards become trustworthy evidence, and why promotion must move the same artifact bytes instead of rebuilding them.
Learning objectives
- Explain why an ephemeral CI agent should be assumed to start without trusted mutable local state.
- Separate Wrapper/JDK/toolchain identity from dependency caches, build caches, workspace outputs, and exported pipeline artifacts.
- Design test shards whose union is complete, whose overlap is intentional or rejected, and whose reports remain independently auditable.
- Explain build-once/promote-same-artifact as a byte-identity contract rather than a deployment convenience.
- Identify cache write authority, credentials, logs, and shared artifacts as CI trust boundaries.
Version baseline. Gradle 9.7.1, Maven 3.9.16, Maven Wrapper 3.3.4, JDK 21 build runtime/toolchain, and Java 17 target. Hosted CI products are intentionally not prerequisites.
1. CI turns hidden workstation assumptions into production failures
A local build can succeed because the developer already has a
compatible JDK, a warm dependency cache, previously generated files,
locally installed artifacts, credentials, or an old
build//target/ tree. An ephemeral CI agent
removes those accidental conveniences. That is useful: if the
pipeline can rebuild from a reviewed commit plus explicitly approved
reusable state, the team gains stronger evidence that the build is
actually reproducible.
The goal is not “always delete everything.” The goal is to classify state. Dependencies may be cached. Cacheable task outputs may be shared under a controlled Build Cache. But a release JAR should enter deployment because the pipeline exported and verified that exact JAR—not because a later agent happened to rebuild something with the same filename.
2. Mental model: source → verified toolchain → evidence → immutable promotion
flowchart TD A[Reviewed commit] --> B[Clean ephemeral workspace] B --> C[Verified Wrapper + JDK/toolchain] D[Approved dependency/build caches] --> C C --> E[Test shards] E --> F[Complete report set] C --> G[Package once] G --> H[Artifact + SHA-256 + run identity] F --> I[Verification gate] H --> I I --> J[Promotion stage] J --> K[Same artifact bytes in release repository] L[CI secrets] --> C L --> J
The arrows describe trust transitions. The cache can accelerate resolution or task execution, but it does not replace source identity, verification metadata, tests, or artifact checksums. Promotion has no arrow back to compilation: that is deliberate.
3. Ephemeral agents: isolate mutable state, preserve deliberate evidence
An ephemeral agent is a worker whose workspace and process state are treated as disposable after the job. It may still restore approved caches. The distinction is that the pipeline does not depend on undocumented leftovers from a previous job. A clean workspace therefore becomes part of the evidence model, not a ritual.
For Gradle, use the committed Wrapper and consider an isolated
GRADLE_USER_HOME in labs or tightly managed CI images.
For Maven, the Wrapper plus -Dmaven.repo.local=... can
isolate the local repository. Do not globally purge a developer's
normal ~/.gradle or ~/.m2 just to simulate
CI.
4. Record four identities before trusting the build
Every run should be able to answer four questions: which source revision? which Wrapper/build-tool distribution? which JVM ran the build? and which compiler/toolchain target produced the artifact? Chapter 23 separated those JVM identities; CI must record them again because stages may run on different workers.
git rev-parse HEAD
java -version
./gradlew --version
# Maven lane:
./mvnw --version
Capturing these lines as a small text artifact is stronger than assuming a runner image tag means every stage used the same toolchain.
5. Cache taxonomy: reusable state is not all the same thing
| State | Safe CI interpretation | Typical location | Trust rule |
|---|---|---|---|
| Source checkout | Authoritative repository input for the run. | Ephemeral workspace. | Start clean; verify commit/ref before executing build logic. |
| Wrapper distribution | Reusable tool bootstrap bytes after checksum verification. | Gradle/Maven Wrapper cache. | Key by exact wrapper/tool version; never accept a changed checksum merely to restore cacheability. |
| Dependency cache | Downloaded dependency/plugin artifacts and metadata. | Gradle User Home dependency caches; isolated Maven local repository. | Treat as untrusted reusable state: resolution/verification controls must still validate it. |
| Build cache | Reusable task outputs keyed from declared task inputs. | Local/remote Gradle Build Cache. | Separate write authority from read authority; protected CI is a common producer. |
| Project build outputs | Outputs of this particular workspace invocation. | build/, target/. |
Do not restore them as if they were dependency caches or release truth. |
| Pipeline artifact | Deliberately exported immutable deliverable/report from a successful stage. | CI artifact store or disposable local handoff directory. | Record checksum and producer run identity; downstream stages consume, not rebuild. |
| Release repository | Authoritative distribution location after promotion. | Repository manager / file-repo simulation. | Promote the same reviewed bytes under immutable coordinates. |
6. Cache keys and poisoning: speed is a trust decision
A cache key determines which jobs may reuse a stored namespace. Correctness inputs commonly include build-tool version, JDK/toolchain identity, dependency lock/verification state, OS/architecture where relevant, and sometimes the source branch or trust class. A cache key that allows an untrusted pull request to write entries later consumed by a protected release branch creates a poisoning path.
Gradle's remote Build Cache is specifically designed for reusable
task outputs. Current Gradle guidance commonly has CI populate a
remote cache while developers read it. The same idea generalizes:
keep cache write authority narrower than cache read authority.
Dependency caches and build caches are also different systems;
copying build/ into a dependency cache is not a
shortcut—it erases provenance.
8. Build once, promote the same artifact bytes
Build once/promote same artifact means the
candidate artifact is created in one controlled build, hashed,
tested, and then copied or repository-promoted unchanged through
environments. Rebuilding for staging and production creates two
artifacts that may differ because of time, toolchain, dependency,
environment, or configuration drift—even if both claim version
1.0.0.
The handoff record should minimally bind the artifact checksum to source revision, build run, Wrapper/build-tool identity, JDK/toolchain identity, and test evidence. Environment-specific configuration belongs outside the immutable application binary where possible.
9. CI secrets and repository credentials: inject late, expose minimally
Credentials are inputs to repository access or publication, not ordinary build metadata. Do not put tokens in build files, command lines that echo into logs, test reports, or Build Scan values. Prefer the CI platform's secret mechanism, environment/provider APIs that avoid printing the value, least-privilege repository accounts, and stages that only receive secrets when they actually need them.
Untrusted pull requests should not automatically receive release credentials or cache-push authority. A build script is executable code: running an unreviewed change with a production token is equivalent to handing that code the token.
10. Read-only first: inspect before optimizing or mutating
Before introducing caches or shards, record a clean baseline. These commands inspect tool identity and the selected task/test model without changing shared infrastructure:
# Gradle
./gradlew --version
./gradlew tasks --group verification
./gradlew dependencies
# Maven
./mvnw --version
./mvnw -DskipTests help:effective-pom
./mvnw dependency:tree
Only after the baseline is explainable should you cache, shard, or promote. Otherwise performance changes can conceal an existing modeling defect.
11. Production CI build contract
A defensible build pipeline can state: clean source identity; verified Wrapper/tool distribution; explicit JDK/toolchain; reviewed dependency/plugin repositories and verification; cache namespaces with documented trust; deterministic test partition and report collection; package exactly once; checksum the candidate; and promote the same bytes. CI-platform YAML is merely one implementation of that operating contract.
Knowledge check
Why is an ephemeral agent compatible with caching?
Ephemeral describes the workspace/process lifecycle; approved reusable caches may still be restored as explicitly classified inputs.
Why is restoring build/ or target/ as a dependency cache dangerous?
Those directories are workspace outputs. Treating them as dependency truth can bypass tasks, tests, or provenance and create false-green builds.
What must a sharded test pipeline prove besides “all shard jobs were green”?
That the union of shard assignments equals the intended suite, with no accidental gaps or unjustified duplicates, and that reports/failures were aggregated.
What does build-once/promote-same-artifact prohibit?
Recompiling or repackaging the release candidate in later environment/promotion stages.
Which branch should normally have broader cache write authority: an untrusted PR or protected CI?
Protected/trusted CI. Untrusted changes should not be able to poison state later trusted by releases.
Official references and version notes
- Gradle 9.7.1 release notes — pinned Gradle baseline.
- Gradle dependency caching — dependency-cache state and guidance for ephemeral builds.
- Gradle Build Cache — local/remote task-output cache and CI push/read trust model.
- Build Cache use cases — CI-produced cache entries and cross-machine reuse.
-
Gradle task outcomes
—
UP-TO-DATEversusFROM-CACHE. - Gradle on GitLab CI — current Wrapper-first CI guidance and cache considerations.
- Maven release history — Maven 3.9.16 GA baseline.
- Maven Wrapper 3.3.4 — current stable Wrapper baseline.
- Maven Surefire test goal — explicit test selection and failure behavior.
Version-sensitive statements were rechecked against primary documentation on 2026-08-24. Mandatory labs remain local/free; hosted CI, remote caches, artifact repositories, and secret stores are represented as optional production mappings only.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.