When working with Testcontainers, most Java developers love the ability to spin up real dependencies like PostgreSQL, Kafka, or Redis in lightweight, disposable containers.
But in practice, containers may fail to start, network issues may block communication, or CI/CD environments may misbehave. That’s where debugging and troubleshooting Testcontainers becomes an essential skill.
This guide will walk you through common problems, debugging strategies, logs, configuration fixes, and real-world troubleshooting techniques to ensure your tests run reliably both locally and in CI pipelines.
Common Issues in Testcontainers
1. Container Startup Failures
- Incorrect Docker setup (e.g., Docker not running, wrong permissions).
- Image pull errors (network/firewall blocking Docker Hub).
- Incompatible versions between Testcontainers and Docker.
2. Networking Issues
- Host machine unable to map ports correctly.
- Services in multi-container tests not connecting.
- Containers failing in CI/CD due to limited resources.
3. Resource Constraints
- Memory or CPU limits in Docker engine.
- Containers being killed by OOM (Out-of-Memory).
- Parallel test execution exhausting ports.
4. Flaky Tests
- Containers take too long to start.
- Random failures due to race conditions.
- Improper lifecycle management leading to test contamination.
Debugging with Logs
Enabling Debug Logs
Testcontainers provides verbose logs for container startup and lifecycle. Add the following JVM property:
-Dorg.testcontainers=true -Dorg.slf4j.simpleLogger.log.org.testcontainers=DEBUG
This gives insights into:
- Which container images are pulled.
- Health check status.
- Networking setup (port mappings, exposed services).
Example Output
INFO 🐳 Starting container: postgres:15
INFO Container postgres:15 started with ID 9a8f1c...
DEBUG Exposed port 5432 -> 32770
Strategies for Troubleshooting
1. Verify Docker Environment
Run:
docker ps
docker info
Ensure containers can run independently of Testcontainers.
2. Explicit Wait Strategies
Sometimes containers need extra time before becoming ready.
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(60)));
3. Inspect Container Logs
Use Testcontainers API to fetch logs directly:
System.out.println(postgres.getLogs());
This helps identify startup failures inside the container.
4. Reusable Containers
If tests frequently fail due to startup timeouts, configure reusable containers:
@Testcontainers
public class MyIntegrationTest {
@Container
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withReuse(true);
}
Enable reuse in ~/.testcontainers.properties
:
testcontainers.reuse.enable=true
Debugging in CI/CD Pipelines
1. Resource Limits
- Increase available memory/CPU for CI runners.
- Use smaller images if possible (e.g.,
alpine
-based).
2. Docker-in-Docker (DinD)
- Many CI environments run Docker inside containers.
- Ensure privileged mode or remote Docker API access is configured.
3. Caching Images
Avoid re-pulling images in every CI run by caching Docker layers.
This speeds up builds and reduces failures due to network slowness.
Advanced Debugging Techniques
Network Simulation
Test flaky network conditions:
GenericContainer<?> container = new GenericContainer<>("nginx:latest")
.withNetworkAliases("my-service")
.withCreateContainerCmdModifier(cmd -> cmd.withCapAdd(Capability.NET_ADMIN));
Combine with toxiproxy for latency and packet loss simulation.
Attach a Shell to Running Container
If something feels off, jump inside:
docker exec -it <container_id> /bin/sh
Inspect Lifecycle Events
Attach event listeners for container start/stop:
postgres.addLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()));
Best Practices for Reliable Debugging
- Always run
docker system prune
to clean stale images/volumes. - Pin container image versions (
postgres:15
, notpostgres:latest
). - Configure explicit health checks instead of relying on defaults.
- Use parallel-safe ports to avoid conflicts in CI.
- Prefer reusable containers for local development.
Case Study: Debugging a Failing PostgreSQL Test
A Spring Boot microservice integration test was failing intermittently in GitHub Actions.
Root Cause: Container startup was slower than expected in shared CI runners.
Solution: Increased startup timeout and added Wait.forHealthcheck()
.
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.waitingFor(Wait.forHealthcheck().withStartupTimeout(Duration.ofSeconds(90)));
After applying the fix, the flaky failures disappeared.
Version Tracker
- JUnit 4 → JUnit 5: Migration enabled better integration with extensions like
@Testcontainers
. - Mockito Updates: Added support for mocking final classes and static methods.
- Testcontainers Evolution: Introduced reusable containers, new modules (Kafka, LocalStack, RabbitMQ), and CI/CD optimizations.
Conclusion and Key Takeaways
Debugging Testcontainers isn’t just about reading stack traces — it’s about understanding lifecycle management, networking, and container internals.
By enabling debug logs, inspecting container output, and tuning wait strategies, you can eliminate most flaky failures.
Key Takeaways:
- Enable debug logging for better visibility.
- Always verify Docker environment before blaming Testcontainers.
- Use explicit wait strategies for slow-starting services.
- Optimize CI/CD pipelines with caching and resource adjustments.
- Leverage reusable containers for faster, stable local development.
FAQ
Q1: What’s the difference between unit and integration tests?
Unit tests isolate code, while integration tests validate real dependencies like databases or APIs.
Q2: How do I mock a static method in Mockito?
Use Mockito.mockStatic()
(since Mockito 3.4+).
Q3: How can Testcontainers help in CI/CD pipelines?
They provide disposable, real infrastructure that ensures parity between dev and production.
Q4: Why do my Testcontainers tests run slower in CI than locally?
CI environments have limited CPU/memory. Adjust startup timeouts and use smaller images.
Q5: How do I fix flaky Testcontainers tests?
Add explicit wait strategies, increase timeouts, and check container logs.
Q6: Can I use Testcontainers with Docker Compose?
Yes, Testcontainers provides DockerComposeContainer
for multi-service testing.
Q7: What is the difference between TDD and BDD?
TDD focuses on verifying code correctness, while BDD emphasizes behavior and specifications.
Q8: How do I capture container logs in Java?
Use container.getLogs()
or container.followOutput(...)
.
Q9: How do I run Testcontainers in parallel?
Assign dynamic ports and isolate container networks to avoid conflicts.
Q10: Do I always need to clean up containers?
Testcontainers auto-cleans containers, but enabling reusable containers speeds up dev workflows.