Guidance for running Docker containers in automated tests with Testcontainers.
When to Use
ALWAYS use this skill when the user mentions:
- "testcontainers", "Testcontainer", "test containers"
- "integration testing", "docker testing"
- "database testing", "PostgreSQL test", "MySQL test"
- "Java Testcontainers", "Python Testcontainers"
- "test environment setup"
Java — Testcontainers
PostgreSQL Example
@Testcontainers
@SpringBootTest
class OrderRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void configure(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Test
void shouldSaveAndFindOrder() {
Order saved = repository.save(new Order(...));
assertThat(repository.findById(saved.getId())).isPresent();
}
}
Redis Example
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
// Get connection string
String redisUrl = "redis://" + redis.getHost() + ":" + redis.getMappedPort(6379);
Kafka Example
@Container
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.6.0"));
String bootstrapServers = kafka.getBootstrapServers();
Python — testcontainers-python
import pytest
from testcontainers.postgres import PostgresContainer
from testcontainers.redis import RedisContainer
@pytest.fixture(scope="module")
def postgres():
with PostgresContainer("postgres:16-alpine") as pg:
yield pg.get_connection_url()
def test_database_connection(postgres):
import psycopg2
conn = psycopg2.connect(postgres)
assert conn.status == psycopg2.STATUS_READY
Container Lifecycle
// Singleton: one container for all tests
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
// Per-test: new container for each test
@Test
void test() {
try (var redis = new GenericContainer<>("redis:7")) {
redis.start();
// test...
} // auto-closed by try-with-resources
}
CI/CD Configuration
# GitHub Actions — DinD (Docker-in-Docker)
jobs:
test:
runs-on: ubuntu-latest
services:
docker:
image: docker:27-dind
options: --privileged
steps:
- uses: actions/checkout@v4
- run: ./gradlew test
Workflow — Recommended Integration Process
Step 1: Add dependencies: testImplementation 'org.testcontainers:testcontainers:1.20.6'
Step 2: Define containers: @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
Step 3: Inject configuration: @DynamicPropertySource Inject container connection information into Spring configuration
Step 4: Write tests: @Test void shouldSaveAndFind() Normal business testing
Step 5: CI acceleration: Enable withReuse(true) + GHA cache
Gotchas — Common Pitfalls
- CI slow startup: Container images need to be pulled in CI. → Recovery: Pre-warm by adding
docker pull postgres:16-alpinebefore test step; enablewithReuse(true)in.testcontainers.properties. - Port conflicts in CI: Testcontainers picks random ports — no conflicts. But hardcoded ports in tests will fail. → Recovery: Always use
container.getMappedPort(5432)instead of hardcoding port numbers. - Resource cleanup: Ryuk auto-removes containers. In CI, ensure DinD or socket access. → Recovery: If
TESTCONTAINERS_RYUK_DISABLED=true, add@AfterAllcleanup; check Docker socket is mounted in CI. - Static vs instance containers: Static
@Containerfields share ONE container across the class. Instance fields create per-test containers. → Recovery: Use static for databases (shared state OK); use instance for isolation-critical tests; check@TestInstance(TestInstance.Lifecycle.PER_CLASS)for JUnit 5.
Boundary — Capability Boundaries (Applicable and Non-Applicable Scenarios)
| Category | Scenario | Description |
|---|---|---|
| ✅ Can do | Java/Python integration testing | Containerized database testing with PostgreSQL/MySQL/Redis/Kafka, etc. |
| ✅ Can do | Spring Boot testing | Automatic injection with @Testcontainers + @DynamicPropertySource |
| ✅ Can do | CI integration | Automatic startup/cleanup in GitHub Actions/Jenkins |
| ⚠️ Conditional | Cross-JVM reuse | Requires configuration testcontainers.reuse.enable=true |
| ⚠️ Conditional | Non-Java language testing | Use the corresponding Testcontainers library for each language |
| ❌ Out of scope | Manual testing/debugging | Use docker-run |
| ❌ Out of scope | Production database testing | Connect to production instances instead of containers |
| ❌ Out of scope | Performance/stress testing | Dedicated tools (JMeter/K6) |
When NOT to Use This Skill
| ❌ Skip | ✅ Use Instead |
|---|---|
| Manual testing | docker-run |
| Production database testing | docker-storage + real instance |
| Mock testing | Use Mockito/unittest.mock |
| Docker basics | docker-basics |
Security & Stability
- Testcontainers use random ports — no port conflicts with running services.
- Containers are automatically cleaned up by Ryuk after test completion.
- Never use Testcontainers in production code — test scope only.
- Use
withReuse(true)for local development to speed up tests (container survives between runs).
📚 Official Documentation References
| Documentation | Address |
|---|---|
| Testcontainers Overview | https://docs.docker.com/testcontainers/ |
| Testcontainers for Java | https://java.testcontainers.org/ |
| Testcontainers Cloud | https://testcontainers.com/cloud/ |
| Spring Boot Integration | https://java.testcontainers.org/modules/spring-boot/ |
| CI Configuration | https://java.testcontainers.org/supported_docker_environment/continuous_integration/ |
🧭 Docker Skills Journey
📍 You are here:
docker-testcontainers— test containers
← Prev: docker-troubleshooting — Problem debugging
→ Next: docker-ai-ml — AI/ML workloads
FAQ
Q1: How do I quickly get started with this skill? A: Refer to the Quick Start section above and follow the steps.
Q2: What should I do if I encounter version incompatibility issues? A: Check dependency versions, lock them using the lock file, and refer to the Common Pitfalls section.
Q3: How can I use it in a production environment? A: Refer to the Best Practices section, ensure the configuration is correct, and implement proper monitoring and logging.
Q4: How can performance be optimized? A: Refer to the performance optimization documentation and use methods such as caching and indexing.
Q5: How can I contribute or provide feedback? A: Submit an Issue or Pull Request in the GitHub repository.
Q6: Is Chinese supported? A: Chinese documentation and Chinese comments are supported; see the domestic adaptation section for details.