Skip to content

SYSTEM Cited by 1 source

Ryuk (Testcontainers reaper)

Ryuk (github.com/testcontainers/moby-ryuk) is a tiny Docker container shipped with Testcontainers whose only job is to kill orphaned containers after the parent JVM test process dies.

Problem

Testcontainers normally cleans up its containers via a JVM shutdown hook: when the test JVM exits cleanly, the hook runs .stop() on each container. But shutdown hooks don't fire in crash scenarios — SIGKILL, OOM kill, CI worker preemption, kill -9, laptop closed mid-run. Without a reaper, those containers would accumulate indefinitely.

Mechanism

On first container creation, Testcontainers launches a single Ryuk container with a Unix-domain socket into it. Testcontainers sends Ryuk a filter (label: testcontainers=true + a unique session id) and a heartbeat. As long as the heartbeat arrives, Ryuk does nothing.

When the heartbeat stops (JVM died), Ryuk waits for a configurable timeout, then deletes every container, network, volume, and image matching the filter. The JVM's containers are GC'd; Ryuk itself exits.

Why a separate container?

The reaper has to outlive the JVM. Putting it in the JVM would be self-defeating. Putting it in the Docker daemon requires daemon-side plugins or modifications, not portable. A small containerised sidecar is portable across Docker, Podman, remote runtimes — wherever Testcontainers runs.

Tradeoffs

  • Ryuk requires Docker socket access. In strict multi-tenant CI environments, this can be a security concern; some teams disable Ryuk (TESTCONTAINERS_RYUK_DISABLED=true) and manage cleanup via other means.
  • Not bullet-proof. If Ryuk itself dies (e.g. machine reboot), orphaned containers from its session survive. Periodic fleet-level cleanup is still recommended in long-running CI environments.
  • Startup cost — the first Testcontainers interaction in a JVM pays a small tax to launch Ryuk. Negligible relative to the Postgres / Localstack containers themselves.

Seen in

Last updated · 476 distilled / 1,218 read