LEARN · DEBUGGING GUIDE

Debugging Testcontainers Container Startup Timeout

When Testcontainers hangs and times out during container startup, it's usually not a bug in your code. The real culprits are Docker daemon resource starvation, Ryuk cleanup deadlock, or image pull throttling.

IntermediateTesting6 min read

What this usually means

Testcontainers relies on Docker daemon responsiveness. A startup timeout indicates that either the Docker daemon couldn't pull the image, allocate resources, or start the container within the default 60-second window. The most common root causes are: Docker needing more CPU/memory/disk, Ryuk (the cleanup container) failing to start, or network issues slowing image pulls. In CI environments, resource limits on shared runners are a frequent culprit.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Run `docker system df` to check disk usage; if over 90%, prune images.
  • 2Check Docker daemon logs: `journalctl -u docker -n 50` (Linux) or tail Docker Desktop logs.
  • 3Verify Ryuk container is running: `docker ps | grep ryuk`. If absent, check Ryuk logs.
  • 4Increase Testcontainers timeout temporarily: `@Testcontainers(parallelism=1, timeout=120)` in JUnit config or set `testcontainers.timeout=120`.
  • 5Run a manual `docker pull <image>` for the container image to isolate pull vs. start delay.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • search/var/log/docker.log or Docker Desktop diagnostics
  • searchTestcontainers output: look for 'Ryuk' or 'container' lines with timestamps
  • searchSystem resource monitor: `docker stats` during test execution
  • searchTestcontainers config file: `~/.testcontainers.properties` for Ryuk disabled flag
  • searchCI runner logs: check if Docker daemon was restarted or had OOM events
  • searchApplication test logs: search for 'TimeoutException' with stack trace
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningDocker daemon resource exhaustion: CPU, memory, or disk space is insufficient
  • warningDocker image pull throttling due to Docker Hub rate limits (anonymous pulls limited to 100/6h)
  • warningRyuk (Reaper) container fails to start, blocking container cleanup and subsequent starts
  • warningPort conflicts: Testcontainers tries to bind to a port already in use
  • warningNetwork issues: Docker network bridge misconfiguration or DNS resolution failures
  • warningTestcontainers version mismatch: old version with known bugs (e.g., <1.16.0 had timeout issues)
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildIncrease Docker resources: in Docker Desktop settings, allocate at least 4GB RAM and 2 CPUs
  • buildDisable Ryuk for local debugging: set `testcontainers.reuse.enable=true` and `testcontainers.ryuk.disabled=true` in `~/.testcontainers.properties`
  • buildUse a Docker Hub pull-through cache or mirror registry to avoid rate limits
  • buildSet explicit timeout per container: `.withStartupTimeout(Duration.ofSeconds(120))`
  • buildPrune stale resources: `docker system prune -a --volumes` to free disk space
  • buildUpdate Testcontainers to latest version (1.20.x as of 2025) to get timeout improvements
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedRun the failing test with increased timeout and confirm it passes
  • verifiedMonitor Docker stats: `docker stats --no-stream` during test should show container starting within limits
  • verifiedCheck that Ryuk container appears immediately on test start
  • verifiedVerify image pull times: `docker pull <image>` should complete in <30 seconds
  • verifiedRun tests sequentially (no parallelism) to confirm not a race condition
  • verifiedAfter fix, run the full test suite three times consecutively with no timeouts
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningBlindly increasing timeout without diagnosing the root cause — masks the issue
  • warningForgetting to disable Ryuk when debugging locally, causing it to interfere
  • warningIgnoring Docker Desktop resource limits on macOS/Windows (default 2GB RAM is too low)
  • warningUsing multiple Testcontainers tests in parallel without ensuring sufficient Docker resources
  • warningRunning `docker system prune` in production without checking for running containers
( 07 )War story

CI Pipeline Fails Randomly with Testcontainers Timeout

Senior Backend EngineerJava 17, Spring Boot 3.2, Testcontainers 1.19.3, PostgreSQL 15, GitHub Actions (ubuntu-latest runner)

Timeline

  1. 09:15CI pipeline fails on integration test stage with 'container startup timeout' for PostgreSQL container
  2. 09:20Check Docker logs on runner: no errors, but `docker ps` shows no containers
  3. 09:30Re-run job with `testcontainers.timeout=180` — test passes, but takes 100 seconds
  4. 09:45Observe that `docker pull postgres:15` takes 50 seconds on this runner
  5. 10:00Check Docker Hub rate limits: runner's IP has hit anonymous pull limit (100 pulls/6h)
  6. 10:15Add Docker Hub authentication to CI: `DOCKER_USERNAME` and `DOCKER_PASSWORD` secrets
  7. 10:20Re-run with auth: pull time drops to 10 seconds, test passes in 25 seconds
  8. 10:30Set up a Docker Hub mirror cache in CI to prevent future rate limits

We had a suite of integration tests using Testcontainers with PostgreSQL. They ran fine locally on my Mac, but in CI on GitHub Actions, they'd fail about 30% of the time with a 'container startup timeout' after 60 seconds. The failure was intermittent, which made it look like a race condition.

I added a longer timeout to see if it was just slow, and the tests started passing but taking over 90 seconds. That told me the issue was either image pull or container start, not a hang. I manually ran `docker pull postgres:15` in a CI step and it took 50 seconds. Then I checked Docker Hub status and realized we were hitting anonymous pull rate limits.

The fix was to authenticate Docker pulls in CI using our organization's Docker Hub credentials. Pull time dropped to under 10 seconds. I also added a Docker mirror cache to avoid future rate limit issues. The lesson: always check Docker Hub rate limits when you see intermittent startup timeouts in CI, especially on shared runners.

Root cause

Docker Hub anonymous pull rate limit (100 pulls per 6 hours per IP) causing image pull delays exceeding the default 60-second startup timeout.

The fix

Added Docker Hub authentication via CI secrets and configured a pull-through cache mirror. Also increased Testcontainers timeout to 120 seconds as a safety net.

The lesson

Intermittent Testcontainers timeouts in CI are often due to Docker Hub rate limits. Always authenticate Docker pulls and consider a local registry mirror.

( 08 )How Testcontainers Manages Container Lifecycle and Timeouts

Testcontainers uses a default startup timeout of 60 seconds. This timeout covers the entire process: pulling the image, creating the container, starting it, and waiting for a port to become available. The timeout is configurable via `container.withStartupTimeout(Duration)` or globally via `testcontainers.timeout` system property.

The Ryuk reaper container is started alongside your test containers to clean them up after the test. If Ryuk fails to start (e.g., due to resource limits), Testcontainers may hang waiting for Ryuk's healthy status before proceeding. You can disable Ryuk for debugging by setting `testcontainers.ryuk.disabled=true` in `~/.testcontainers.properties`.

( 09 )Diagnosing Docker Daemon Resource Exhaustion

Check Docker daemon resource usage: on Linux, `docker system df` shows disk usage. If `Images` or `Containers` exceed 90% of allocated space, prune with `docker system prune -a --volumes`. Also check memory: `docker info` under 'Memory Limit' shows if memory limits are enforced.

On macOS/Windows, Docker Desktop runs in a VM. By default, it allocates 2GB RAM and 1 CPU. For multiple Testcontainers, increase to 4GB RAM and 2 CPUs in Docker Desktop settings. You can also monitor resource usage with `docker stats` during test execution to see if containers are being OOM-killed.

( 10 )Ryuk (Reaper) Failures and How to Fix Them

Ryuk is a privileged container that removes Testcontainers after tests. If Ryuk fails to start, Testcontainers will hang. Check if Ryuk is running: `docker ps | grep ryuk`. If not, look at Ryuk logs: `docker logs <ryuk-container-id>` (capture it early).

Common Ryuk failures include: insufficient privileges (SYS_ADMIN capability needed), port 6379 conflict (Ryuk uses TCP port 6379), or Docker daemon not supporting privileged containers. Disable Ryuk temporarily with `testcontainers.ryuk.disabled=true` to see if that resolves the timeout.

( 11 )Docker Image Pull Throttling and Rate Limits

Docker Hub imposes rate limits: anonymous users get 100 pulls per 6 hours, authenticated users get 200 pulls per 6 hours. If you run many tests in CI on a shared runner, you may hit this limit. Symptoms: `docker pull` takes >30 seconds or fails with 'toomanyrequests'.

Solution: authenticate Docker pulls in CI by setting `DOCKER_USERNAME` and `DOCKER_PASSWORD` environment variables (or use `docker login`). Better: set up a local Docker registry mirror/cache. You can also use `testcontainers.timeout` to increase the timeout as a temporary workaround.

Frequently asked questions

How do I increase the Testcontainers startup timeout globally?

Set the system property `testcontainers.timeout` when running tests. For example, in Maven: `mvn test -Dtestcontainers.timeout=120` sets a 120-second timeout. You can also set it in `~/.testcontainers.properties` with `testcontainers.timeout=120`.

What is Ryuk and why does it cause timeouts?

Ryuk (Reaper) is a container that runs with elevated privileges to clean up Testcontainers after tests. If Ryuk fails to start, Testcontainers will wait for it indefinitely until the timeout. You can disable Ryuk for debugging by adding `testcontainers.ryuk.disabled=true` to your configuration.

Can Docker Desktop resource limits cause Testcontainers timeouts?

Yes. Docker Desktop on macOS/Windows runs in a VM with limited resources. Default is 2GB RAM and 1 CPU. Running multiple containers in parallel can exhaust memory, causing containers to be OOM-killed or start slowly. Increase resources to at least 4GB RAM and 2 CPUs.

How do I check if Docker Hub rate limiting is causing my timeout?

Run `docker pull <image>` manually and note the time. If it takes >30 seconds or returns a 'toomanyrequests' error, you're rate limited. Check your Docker Hub account for current pull counts. Authenticate pulls or use a mirror to resolve.

Why does my test pass locally but timeout in CI?

Common reasons: CI runner has less CPU/memory; Docker images need to be pulled fresh (no cache); Docker Hub rate limits on shared IPs; or CI environment has different network latency. Use the same Docker version and resource specs as local, and authenticate pulls.