skip to content

CI Push/Pull And Seeding

Splitting push from pull so trusted CI jobs seed the remote cache while developers read from it. Interviewers ask because letting everyone push is both a correctness and a security problem.

on this pageshow

questions

5

Where in a Gradle project do you configure the push/pull split for the remote build cache, and at what point in the build lifecycle does that configuration take effect?

level: juniorimportance: must knowfreq 45%

answer

  1. settings.gradle(.kts), not build.gradle
  2. buildCache { remote<HttpBuildCache> }
  3. initialization phase
  4. before projects/tasks
  5. --build-cache toggles, settings sets policy

basics

~20 s

In settings.gradle(.kts), inside the buildCache { remote<HttpBuildCache> { ... } } block. It takes effect very early — during settings evaluation, before any project is configured — because the cache must be ready before tasks run.

solid answer

~40 s

The push/pull split lives in the `buildCache {}` block of **`settings.gradle(.kts)`**, not in a project's `build.gradle(.kts)`. Inside it you declare the `remote<HttpBuildCache>` and set `isPush`/`isEnabled`: ```kotlin buildCache { remote<HttpBuildCache> { url = uri("https://cache.example.com/cache/") isPush = System.getenv("CI") != null } } ``` It belongs in settings because the build cache is part of the build's infrastructure: Gradle reads it during the **initialization phase** (settings evaluation), before the configuration phase builds the task graph and before any task executes. That ordering guarantees the cache is connected and the push policy decided before the first cacheable task looks for a hit. You can still override on/off per run with `--build-cache`/`--no-build-cache`, but the push policy itself is the settings code.

code

kotlin · 8 lines
kotlin
// settings.gradle.kts  (NOT build.gradle.kts)
buildCache {
  remote<HttpBuildCache> {
    url = uri("https://cache.example.com/cache/")
    isEnabled = true
    isPush = System.getenv("CI") != null
  }
}

go deeper

for a junior

Knows the block is in settings.gradle and roughly that it's set up early.

for a middle

Can name the initialization phase and the isPush/isEnabled knobs.

for a senior

Explains why settings (build-wide infra) and how overrides interact with the policy.

for a principal

Standardizes settings-level cache configuration (often via a convention/settings plugin) across many repos.

## The three Gradle phases A Gradle build runs in three phases: 1. **Initialization** — Gradle reads `settings.gradle(.kts)`, decides which projects make up the build, and configures build-wide infrastructure including the **build cache**. 2. **Configuration** — each project's `build.gradle(.kts)` runs, building the task graph. 3. **Execution** — the requested tasks run; cacheable tasks consult the cache for hits and may push outputs. ## Why the cache config is in settings The `buildCache {}` block must be in `settings.gradle(.kts)` because the cache is a property of the *whole build*, established in the **initialization** phase before any project (and any task) exists. If it lived in a project script, it would be configured too late and inconsistently across a multi-project build. So the push/pull decision — `isPush` and `isEnabled` on the `local {}` and `remote {}` caches — is settings-level code. ```kotlin // settings.gradle.kts buildCache { local { isEnabled = true } remote<HttpBuildCache> { url = uri("https://cache.example.com/cache/") isEnabled = true // pull for everyone isPush = System.getenv("CI") != null // push only on CI } } ``` ## Two knobs - `isEnabled` — participate in this cache (read). - `isPush` — allowed to write new entries. For the CI-seed pattern you keep `isEnabled = true` everywhere and flip `isPush` true only on CI. ## Command-line and property overrides The master on/off switch for caching at runtime is `--build-cache` / `--no-build-cache`, or the persistent `org.gradle.caching=true` in `gradle.properties`. These toggle whether caching happens at all; they don't replace the push/pull *policy*, which is the conditional logic in settings. So: settings code expresses the policy; the flag/property expresses per-run enablement. ## Common mistake Putting `buildCache {}` in a project `build.gradle(.kts)` — Gradle will error or ignore it because by configuration time it's too late to set up build-wide cache infrastructure. Always settings.

  • What happens if you put the buildCache block in a project's build.gradle.kts?
    It is too late — the cache is configured during initialization (settings), so a project-script block is ignored or errors. It must live in settings.gradle(.kts).
  • Does --no-build-cache change the push/pull policy?
    No. It disables caching entirely for that run. The push/pull policy is the conditional isPush logic in settings; the flag is just a global on/off.

saying these in an interview costs you the question

  • Putting buildCache configuration in build.gradle.kts.
  • Thinking the cache is set up during the configuration or execution phase rather than initialization.

context

open as a page

In a team that uses a shared remote build cache, why is it common to let only CI seed jobs push to the cache while developers pull read-only? How do you configure that split?

level: middleimportance: must knowfreq 55%

basics

~20 s

CI builds run in clean, trusted environments, so their outputs are reliable cache entries. Developers' machines vary and could poison the cache, so they only read. You set isPush = true on CI and false for developers in the remote(HttpBuildCache) block.

open as a page

On CI you have both PR builds and a main-branch build. How should each participate in the remote cache, and how do you express that split so PR builds don't degrade the shared cache?

level: middleimportance: should knowfreq 30%

basics

~20 s

Make the main-branch (or release) job the only pusher and let PR builds pull read-only. Drive it from an invocation flag/property the seed job passes, e.g. isPush = isMainBranch, while every job keeps isEnabled = true to read.

open as a page

Your shared HTTP cache started serving wrong outputs after a developer's machine pushed bad entries. How do you prevent this going forward, including at the credential/server level?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Make developers pull read-only (isPush = false) and reserve push for trusted CI. Enforce it at the server with separate credentials: developers get a read-only token, only the CI seed job holds a write-capable token. Then purge poisoned entries.

open as a page

How would you gate isPush and isEnabled on Gradle's startParameter rather than reading environment variables directly inside the buildCache block? Why prefer that?

level: seniorimportance: should knowfreq 35%

basics

~20 s

settings.startParameter exposes the actual invocation flags (e.g. isOffline, task names, --build-cache). You read those inside buildCache {} to decide isPush/isEnabled, so the policy reacts to how the build was launched rather than to ambient env state.

open as a page