skip to content

Should you keep the Gradle daemon enabled in CI, and how does daemon reuse factor into a CI build strategy?

level: seniorimportance: should knowfreq 40%

answer

  1. reuse needs a 2nd build on same JVM
  2. ephemeral agent = no reuse = no benefit
  3. persistent runner = keep warm, win
  4. caches beat daemon for cross-run CI
  5. consistent JVM args on shared runners

basics

~20 s

On ephemeral CI agents that build once and are destroyed, daemon reuse never pays off, so --no-daemon avoids spawning a process you throw away. On persistent/self-hosted agents that run many builds, keeping a warm daemon speeds them up.

solid answer

~50 s

The decision hinges on **whether the agent lives long enough to reuse the daemon**. Daemon reuse only delivers speed across *multiple* builds on the *same* warm JVM. On classic **ephemeral CI** (a fresh container per pipeline, one build, then discarded), there's no second build to reuse the daemon, so the warm-up cost is pure overhead — Gradle even historically recommended `org.gradle.daemon=false` / `--no-daemon` there to avoid leaving zombie daemons and to make process lifecycle predictable. On **persistent or self-hosted runners** that execute many builds back-to-back, a kept-warm daemon is a real win: later builds skip JVM startup and JIT warmup. The nuance: modern Gradle is fine running with the daemon in CI, and remote/local **build caching** plus **configuration caching** usually deliver far bigger CI gains than daemon reuse. So in CI I'd lean on caches for cross-run speed, and choose daemon on/off by agent longevity — disabled (or simply unimportant) for single-shot ephemeral agents, enabled with consistent JVM args for reused runners.

code

bash · 5 lines
bash
# Ephemeral CI: reuse impossible — daemon is just overhead
./gradlew build --no-daemon

# Persistent self-hosted runner: keep daemon warm + consistent args
# gradle.properties: org.gradle.daemon=true and a fixed org.gradle.jvmargs

go deeper

for a junior

Know ephemeral CI usually doesn't benefit from the daemon since it builds once.

for a middle

Distinguish ephemeral vs persistent agents and the --no-daemon convention for single-shot CI.

for a senior

Reason about reuse economics and position build/configuration caching as the real cross-run CI lever.

for a principal

Design the org's CI build strategy: agent topology, cache infrastructure, and JVM-config standardization, treating daemon on/off as a minor consequence of agent longevity.

## The core question: does the agent live to reuse? Daemon reuse is a *cross-invocation* optimization — its benefit only materializes when a **second build** lands on the **already-warm** daemon. CI strategy is really about whether that ever happens. ## Ephemeral agents (the common cloud-CI case) Most hosted CI (GitHub Actions, GitLab SaaS runners, etc.) gives each pipeline a **fresh, short-lived** machine that runs one build and is destroyed. Here: - There is **no later build** on that machine, so the daemon is never reused. - The daemon's warm-up (and the leftover background process) is overhead with no payoff. - Historically Gradle recommended `org.gradle.daemon=false` (or `--no-daemon`) for CI to keep process lifecycle clean and avoid orphaned daemons. ```properties # In an ephemeral CI's gradle.properties (or via -Dorg.gradle.daemon=false) org.gradle.daemon=false ``` In practice on modern Gradle, leaving the daemon on for a single build is usually harmless too — the point is that **reuse isn't the lever** here. ## Persistent / self-hosted runners If you run your own runners that stay up and process **many builds**, the calculus flips: a warm daemon that survives between jobs lets each subsequent build skip JVM startup and JIT warmup — exactly the local-dev benefit, now in CI. To get it you must: - Keep `org.gradle.daemon=true`. - Keep **JVM args and JDK identical** across jobs so every build reuses the same daemon rather than spawning incompatible ones (sprawl is even worse on a shared runner). ## What usually matters more in CI For most teams, the dominant CI speed levers are **not** daemon reuse: - **Build cache** (local + remote): reuse task *outputs* across machines and runs — survives ephemeral agents because it's external storage. - **Configuration cache**: skip re-running the configuration phase. These give cross-run speedups even when each agent is thrown away, which the daemon cannot. So a sound CI strategy is: **lean on caching for cross-run speed; pick daemon on/off by agent longevity.** ## Summary decision | Agent type | Reuse possible? | Daemon stance | |---|---|---| | Ephemeral, single build | No | Off / irrelevant; rely on caches | | Persistent, many builds | Yes | On, with identical JVM args | The principle: don't pay to warm a daemon you'll never reuse — and where reuse is impossible, get your cross-run speed from caching instead.

  • If daemon reuse doesn't help ephemeral CI, what gives you cross-run speed there?
    Remote (and local) build cache to reuse task outputs across machines, plus the configuration cache to skip the configuration phase — both work across throwaway agents because they're external state.
  • On a shared persistent runner, why is consistent JVM config even more important?
    Inconsistent org.gradle.jvmargs/JDK spawns multiple incompatible daemons (sprawl) that eat the runner's RAM and never get reused, defeating the reason you kept the daemon on.
  • Is leaving the daemon enabled in ephemeral CI harmful on modern Gradle?
    Usually not harmful for a single build, but it brings no reuse benefit; the historical --no-daemon advice was mainly about clean process lifecycle and avoiding orphaned daemons.

saying these in an interview costs you the question

  • Claiming the daemon speeds up a single ephemeral CI build — reuse needs a second build that never comes.
  • Saying the daemon and the build cache are the same thing or interchangeable for CI speed.
  • Recommending the daemon as the main CI speed lever instead of build/configuration caching.

context