compose
The compose-managed topology -- started once per JVM, on first access, exactly like KandraTestcontainers.container's by lazy { ... start() } pattern. Startup timeouts are generous (see class doc: two-node gossip convergence from cold is meaningfully slower than the single-node case).
Port selection and retry (GH #108 / ISS-095)
findFreePort only checks that a port is free at the moment it's picked -- there's an inherent, small race between that check and Docker actually binding it (another process, including an entirely unrelated Testcontainers-managed container from a concurrent test run, could grab the same ephemeral port in between). A naive "pick ports once, retry the same pair forever" approach makes this worse on retry, not better: if docker compose up fails after partially creating containers (e.g. dc1 started and bound its port, then something else in the topology failed), those containers are never automatically torn down here -- ComposeContainer only runs its cleanup (docker compose down) from ComposeContainer.stop, which is never called if ComposeContainer.start itself throws -- so a same-port retry would immediately collide with its own previous attempt's leftovers. To actually make retrying useful, each of up to 3 attempts here picks a fresh port pair and best-effort tears down its own compose project if it fails, before the next attempt tries again with different ports.
If every attempt fails -- Docker isn't running, or something other than a port collision is wrong -- the last failure is wrapped in KandraMultiDcFixtureException with an actionable message instead of propagating a raw Testcontainers/Docker exception.