
Docker Testcontainers
- 10 installs
- 2 repo stars
- Updated July 29, 2026
- full-statck-skills/docker-skills
Run real Docker services like PostgreSQL, Redis, and Kafka in automated integration tests using Testcontainers in Java and Python.
About
Guides Testcontainers for running real Docker services in automated integration tests in Java and Python. A developer uses it to test against real databases and services instead of mocks.
- Java @Testcontainers with PostgreSQL/Redis/Kafka/Elasticsearch containers
- Python testcontainers with pytest, container reuse, and CI Docker-in-Docker/Ryuk
Docker Testcontainers by the numbers
- 10 all-time installs (skills.sh)
- Ranked #1,550 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
npx skills add https://github.com/full-statck-skills/docker-skills --skill docker-testcontainersAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 10 |
|---|---|
| repo stars | ★ 2 |
| Last updated | July 29, 2026 |
| Repository | full-statck-skills/docker-skills ↗ |
What it does
Run real Docker services like PostgreSQL, Redis, and Kafka in automated integration tests using Testcontainers in Java and Python.
Files
Docker Testcontainers — 集成测试容器
Guidance for running Docker containers in automated tests with Testcontainers.
When to Use
ALWAYS use this skill when the user mentions:
- "testcontainers", "Testcontainer", "测试容器"
- "集成测试", "docker 测试"
- "数据库测试", "PostgreSQL test", "MySQL test"
- "Java Testcontainers", "Python Testcontainers"
- "测试环境搭建"
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_READYContainer 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 testWorkflow — 推荐集成流程
Step 1: 添加依赖: testImplementation 'org.testcontainers:testcontainers:1.20.6' Step 2: 定义容器: @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine") Step 3: 注入配置: @DynamicPropertySource 将容器连接信息注入 Spring 配置 Step 4: 编写测试: @Test void shouldSaveAndFind() 正常业务测试 Step 5: CI 加速: 启用 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 — 能力边界(适用与不适用场景)
| 分类 | 场景 | 说明 |
|---|---|---|
| ✅ 能做 | Java/Python 集成测试 | PostgreSQL/MySQL/Redis/Kafka 等容器化数据库测试 |
| ✅ 能做 | Spring Boot 测试 | @Testcontainers + @DynamicPropertySource 自动注入 |
| ✅ 能做 | CI 集成 | GitHub Actions/Jenkins 中自动启动/清理 |
| ⚠️ 需条件 | 跨 JVM 复用 | 需配置 testcontainers.reuse.enable=true |
| ⚠️ 需条件 | 非 Java 语言测试 | 使用各语言对应 Testcontainers 库 |
| ❌ 超范围 | 手动测试/调试 | 使用 docker-run |
| ❌ 超范围 | 生产数据库测试 | 连接生产实例而非容器 |
| ❌ 超范围 | 性能/压测 | 专用工具(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).
📚 官方文档参考
| 文档 | 地址 |
|---|---|
| Testcontainers 概述 | https://docs.docker.com/testcontainers/ |
| Testcontainers for Java | https://java.testcontainers.org/ |
| Testcontainers Cloud | https://testcontainers.com/cloud/ |
| Spring Boot 集成 | https://java.testcontainers.org/modules/spring-boot/ |
| CI 配置 | https://java.testcontainers.org/supported_docker_environment/continuous_integration/ |
🧭 Docker Skills Journey
📍 You are here: `docker-testcontainers` — 测试容器
← Prev: docker-troubleshooting — Problem debugging → Next: docker-ai-ml — AI/ML workloads
FAQ
Q1: 如何快速上手此技能? A: 参考上方的快速开始章节,按步骤操作即可。
Q2: 遇到版本不兼容问题怎么办? A: 检查依赖版本,使用 lock 文件锁定,参考常见陷阱章节。
Q3: 如何在生产环境使用? A: 参考最佳实践章节,确保配置正确,做好监控和日志。
Q4: 性能如何优化? A: 参考性能优化相关文档,使用缓存、索引等手段。
Q5: 如何贡献或反馈问题? A: 在 GitHub 仓库提交 Issue 或 Pull Request。
Q6: 是否支持中文? A: 支持中文文档和中文注释,详见国内适配章节。
Java Testcontainers with PostgreSQL + Redis
@Testcontainers
@SpringBootTest
class OrderServiceTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.redis.host", redis::getHost);
registry.add("spring.redis.port", () -> redis.getMappedPort(6379));
}
@Autowired
private OrderService orderService;
@Test
void shouldCreateAndRetrieveOrder() {
Order order = orderService.create(new CreateOrderRequest("item-1", 2));
assertThat(orderService.findById(order.getId())).isPresent();
}
}build.gradle
testImplementation "org.testcontainers:testcontainers:1.20.0"
testImplementation "org.testcontainers:postgresql:1.20.0"
testImplementation "org.testcontainers:junit-jupiter:1.20.0"Spring Boot + PostgreSQL 集成测试全流程
build.gradle
dependencies {
testImplementation 'org.testcontainers:testcontainers:1.20.6'
testImplementation 'org.testcontainers:postgresql:1.20.6'
testImplementation 'org.testcontainers:junit-jupiter:1.20.6'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}基础测试
@SpringBootTest
@Testcontainers
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void shouldSaveAndFindUser() {
User user = new User("test@example.com", "Alice");
userRepository.save(user);
Optional<User> found = userRepository.findByEmail("test@example.com");
assertThat(found).isPresent();
}
}多容器集成(PostgreSQL + Redis + Kafka)
@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@Container
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.6.1"));
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.data.redis.host", redis::getHost);
registry.add("spring.data.redis.port", () -> redis.getMappedPort(6379));
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
@Test
void shouldCreateOrderAndSendEvent() {
// 测试完整订单流程
}
}单例容器(所有测试类共享,加速)
// 父类
@Testcontainers
public abstract class AbstractIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
static {
postgres.start();
}
}
// 子类直接继承
class UserTest extends AbstractIntegrationTest { ... }
class OrderTest extends AbstractIntegrationTest { ... }CI 配置
# .github/workflows/test.yml
- name: Run integration tests
run: ./gradlew test
# Testcontainers 自动启动/清理,无需额外配置
Testcontainers Lifecycle
Singleton (static field — one container for all tests in class):
@Container
static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>();
→ Container starts once before all tests
→ Container shared across all test methods
→ Faster but tests must not conflict on data
Per-test (instance field — new container per test):
@Container
PostgreSQLContainer<?> pg = new PostgreSQLContainer<>();
→ New container for EACH test method
→ Slower but complete isolation
→ Good for tests that modify schema
Manual lifecycle:
try (var pg = new PostgreSQLContainer<>("postgres:16")) {
pg.start();
// test...
} // auto-closed
Reuse (local development speed):
static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>()
.withReuse(true);
→ Container survives JVM exit, reused on next run
→ Requires ~/.testcontainers.properties: testcontainers.reuse.enable=trueTestcontainers 通用模式与最佳实践
容器生命周期
| 注解/方式 | 生命周期 | 场景 |
|---|---|---|
@Container (static) | 所有测试类共享 | 数据库、消息队列 |
@Container (instance) | 每个测试方法重建 | 需要隔离的测试 |
new XxxContainer() + try-finally | 手动控制 | 复杂生命周期 |
单例模式(性能最佳)
// 所有测试共享一个数据库容器,大幅减少启动时间
public abstract class SharedPostgresContainer {
static PostgreSQLContainer<?> postgres;
static {
postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withReuse(true); // 跨 JVM 复用!
postgres.start();
Runtime.getRuntime().addShutdownHook(new Thread(postgres::stop));
}
}启用跨 JVM 复用:~/.testcontainers.properties
testcontainers.reuse.enable=true等待策略
// HTTP 等待
new GenericContainer<>("myapp").waitingFor(
Wait.forHttp("/health").forPort(8080).forStatusCode(200)
);
// 日志等待
new GenericContainer<>("myapp").waitingFor(
Wait.forLogMessage(".*Started.*", 1)
);
// 健康检查等待
new GenericContainer<>("myapp").waitingFor(
Wait.forHealthcheck()
);
// TCP 等待
new GenericContainer<>("redis").waitingFor(
Wait.forListeningPort()
);常用容器速查
| 容器 | 使用方式 |
|---|---|
| PostgreSQL | new PostgreSQLContainer<>("postgres:16-alpine") |
| MySQL | new MySQLContainer<>("mysql:8.4") |
| Redis | new GenericContainer<>("redis:7-alpine").withExposedPorts(6379) |
| Kafka | new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.6.1")) |
| MongoDB | new MongoDBContainer<>("mongo:7") |
| Elasticsearch | new ElasticsearchContainer<>("elasticsearch:8.15.0") |
| LocalStack (AWS) | new LocalStackContainer<>(DockerImageName.parse("localstack/localstack:3.7")) |
| WireMock | new WireMockContainer("wiremock/wiremock:3.9.1") |
CI 加速
# 使用 Ryuk 自动清理(Testcontainers 默认)
# 无需额外配置
# 启用跨 JVM 复用减少启动时间
- name: Configure Testcontainers
run: |
echo "testcontainers.reuse.enable=true" >> ~/.testcontainers.properties常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
Could not find a valid Docker environment | Docker 未运行 | 启动 Docker Desktop / colima |
| 端口冲突 | 随机端口已占用 | Testcontainers 自动处理随机端口 |
| CI 中启动慢 | 首次拉取镜像 | 预拉取镜像 / 使用 withReuse(true) |
| Mac 上性能差 | Docker Desktop 文件共享慢 | 使用 OrbStack 或 colima |