skip to content

In Testcontainers, what does withReuse(true) do and what else must be configured for it?

level: middleimportance: must knowfreq 48%

answer

  1. Two switches, not one
  2. Something must survive the run
  3. A per-machine properties file
  4. Configuration hash decides the match
  5. Old state is the price

basics

~10 s

withReuse(true) tells Testcontainers to leave the container running after the run instead of removing it, and to attach to a matching one next time. It only takes effect when testcontainers.reuse.enable=true is set in ~/.testcontainers.properties.

solid answer

~50 s

Reuse is a two-part switch. The code marks a container reusable with `withReuse(true)`, and the machine opts in with `testcontainers.reuse.enable=true` in `~/.testcontainers.properties`; without the second half the flag is ignored and you get normal behaviour. When both are on, Testcontainers computes a hash of the container's configuration — image, environment, exposed ports, commands, copied files — and looks for a running container carrying that hash. If it finds one it attaches to it; otherwise it starts a new one. At the end of the run the container is deliberately left running and is excluded from Ryuk's reaping, which is what makes it available to the next run. That saves the whole startup on every run after the first, and the price is that state survives between runs: yesterday's rows are still there. Change any part of the configuration and the hash changes, so a fresh container starts and the old one lingers until you remove it.

code

kotlin · 5 lines
kotlin
val postgres = PostgreSQLContainer("postgres:16-alpine")
    .withReuse(true)

// hash covers image, env, ports, command, copied files:
// change any of them and a new container is started

go deeper

for a junior

Recall that reuse needs both withReuse(true) in the code and testcontainers.reuse.enable=true in ~/.testcontainers.properties on your machine.

for a middle

Explain the configuration hash used to match an existing container, and that the container is deliberately left running and not reaped.

for a senior

Show the judgment: reuse is a local-loop optimisation whose cost is stale state, and tests must clean up at the start of a run to survive it.

for a principal

Own the policy — where reuse is allowed, why CI is deliberately excluded, and how developers are stopped from committing a workaround for state that leaks between runs.

## What reuse actually changes A normal container's life is bounded by the test run: started at the beginning, removed at the end by your code or by the Ryuk reaper. Reuse widens that boundary to span *runs*. The container stays up after the JVM exits, and the next run adopts it instead of starting a new one. ## The two-part switch Both halves are required, and this is the single most common source of "reuse does not work" confusion: 1. **In code**: `.withReuse(true)` on the container. 2. **On the machine**: `testcontainers.reuse.enable=true` in `~/.testcontainers.properties`. The split is intentional. Reuse changes what your test run leaves behind on the machine, so the library refuses to make that decision for you from code alone. A developer who wants faster local loops opts in; a machine that has not opted in — a shared CI runner, a colleague's laptop — runs the suite exactly as before, with no code change and no leaked containers. ## How a container is matched Testcontainers derives a hash from the container's configuration: the image name, environment variables, exposed ports, command, labels, copied files and so on. The hash is attached to the container as a label, and on the next start Testcontainers searches running containers for a match. Same configuration, same hash, container adopted. This is why editing a single environment variable causes a brand-new container to start: it is a different configuration, therefore a different hash, therefore no match. The previously reused container is not cleaned up for you — it keeps running until you remove it with `docker rm -f`. ## What reuse deliberately breaks The container is excluded from Ryuk's reaping and is not stopped at the end of the run; that is the whole point, and it is also the cost. Concretely: - **State persists across runs.** Rows, topics, keys and files written yesterday are present today. Any test that assumed a fresh database now depends on cleanup being done at the *start* of a run, not only at the end. - **Migrations become re-runnable.** Schema setup that assumed an empty database must tolerate an already-migrated one. - **Resources accumulate.** Configuration churn leaves a trail of orphaned containers on the machine. ## Why it is a laptop feature The documented use case is the local edit-run-debug loop, where saving several seconds on every iteration is worth a slightly dirty database. On CI the calculus inverts: runners are ephemeral or shared, a container left running is either wasted immediately or leaks onto a machine other jobs use, and a build that reuses state across jobs is no longer reproducible. The standard position is to leave the properties file untouched on CI, so the same code runs without reuse there. ## Reuse versus the singleton pattern They stack and solve different problems. The singleton pattern removes repeated startup *within* one JVM run; reuse removes it *between* runs. Reaching for reuse to fix a suite that starts a container per test class is treating the symptom — fix the sharing inside the run first. ## Interview framing Name both halves of the switch, name the configuration hash as the matching mechanism, and volunteer the state-leak trade-off with the reason it is per-machine opt-in. Candidates who only know the method call usually cannot explain why their reuse never engaged.

  • Why is the machine-level opt-in a properties file rather than a code flag?
    Because reuse changes what a test run leaves behind on the host, and that is the machine owner's decision, not the test author's. The same test code can then run with reuse on a developer laptop and without it on a shared CI runner, with no branching in the code and no risk of leaking containers onto machines that never asked for it.
  • You enabled reuse but a new container starts on every run. What do you check?
    First that testcontainers.reuse.enable=true is really present in ~/.testcontainers.properties for the user running the build. Then whether the container's configuration is stable between runs — anything that varies, such as a generated environment value, a timestamped label or a changing copied file, changes the hash and prevents a match.
  • How should tests be written so a reused container does not make them flaky?
    Move cleanup to the start of the test rather than the end: truncate or recreate the data a test depends on before exercising it, and make schema setup idempotent so it tolerates an already-initialised container. Tests that generate unique identifiers per run are naturally immune.

saying these in an interview costs you the question

  • Says withReuse(true) alone is enough to enable reuse
  • Enables reuse on shared CI runners to speed builds up
  • Assumes reuse gives a clean container each run
  • Cannot explain why a config change starts a new container
  • Expects Ryuk to remove the reused container

context