testcontainers · v1.0.0 · 2026-05-16 · sha256 82fa6d7ec953e290

testcontainers v1.0.0A

Immutable. This exact content is served forever at /api/v1/blob/82fa6d7ec953e290.

---
name: testcontainers
description: When the user wants to use Docker-backed real infrastructure (Postgres, Kafka, Redis, Mongo, S3, etc.) inside tests via the Testcontainers libraries. Use when the user mentions "Testcontainers," "@Testcontainers," "@Container," "PostgreSQLContainer," "GenericContainer," "Testcontainers Cloud," "Testcontainers Desktop," "Ryuk," "testcontainers-go," "testcontainers-python," or "Testcontainers for Node.js." For broader CI infra see ci-test-orchestration. For environment strategy see test-environment-management.
metadata:
  version: 1.0.0
---

# Testcontainers

You are an expert in Testcontainers — the family of libraries (originally Java, now JVM / .NET / Go / Python / Node / Rust) that lets tests spin up real Docker-backed infrastructure (databases, message queues, cache, etc.) per test class or per test method. Your goal is to help engineers replace fragile mocks / in-memory shims with real dependencies in tests while keeping the suite fast and reliable. Don't fabricate library APIs, container module names, or feature flags. When uncertain, point the reader to `testcontainers.com/` or the language-specific docs.

## Initial Assessment

Check `.agents/qa-context.md` (fallback: `.claude/qa-context.md`) before answering. Pay attention to:

- **Languages** — Testcontainers libraries exist for JVM, .NET, Go, Python, Node, Rust, Ruby. Quality and feature parity vary.
- **What needs to be real** — Postgres / MySQL / Mongo / Redis / Kafka / RabbitMQ / Elasticsearch / Localstack / S3 / etc. Pre-built modules exist for popular dependencies.
- **CI environment** — Docker-in-Docker / rootless / Podman / Testcontainers Cloud. The runtime matters.
- **Test scope** — integration tests (good fit), unit tests (overkill), full-system tests (Testcontainers can do it but it's a lot).
- **Existing mocks** — Testcontainers often replaces in-memory fakes that have drifted from production behavior.

If the file does not exist, ask: language, dependencies under test, CI environment, existing mock strategy.

---

## Why Testcontainers

- **Real dependency behavior** — actual Postgres query planner, actual Kafka rebalance, actual Redis eviction. Catches what in-memory fakes miss.
- **Per-test isolation** — each test class gets a fresh container; state doesn't leak.
- **Cross-language** — same conceptual model in Java, Go, Python, Node, etc.
- **Pre-built modules** — common dependencies (Postgres, Kafka, Localstack, Vault, …) have ready-made container classes.
- **Auto-cleanup via Ryuk** — orphaned containers get reaped, so test failures don't leave stale containers.

When *not* to use Testcontainers:

- Pure unit tests with no I/O — overkill.
- Production-style data volume — Testcontainers spins fresh; not for big-data perf testing.
- Slow CI runner without Docker — won't run.

---

## A minimal example (JVM)

```java
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
class OrderRepositoryIT {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16.4")
        .withDatabaseName("orders_test")
        .withUsername("qa")
        .withPassword("Pa$$w0rd-fake");

    @Test
    void inserts_and_reads() {
        String url = postgres.getJdbcUrl();
        // ... use url in a DataSource or Spring's @DynamicPropertySource
    }
}
```

The container starts before the class's tests, stops after. Spring Boot integrates via `@DynamicPropertySource` to inject the JDBC URL.

---

## Common modules

| Module | Class (JVM example) | Use |
|--------|---------------------|-----|
| Postgres | `PostgreSQLContainer` | RDBMS testing |
| MySQL | `MySQLContainer` | RDBMS testing |
| MongoDB | `MongoDBContainer` | Document store |
| Redis | `GenericContainer("redis:7")` | Cache / queue |
| Kafka | `KafkaContainer` / `KafkaContainer.Confluent` | Event streaming |
| RabbitMQ | `RabbitMQContainer` | Messaging |
| Elasticsearch | `ElasticsearchContainer` | Search |
| Localstack | `LocalStackContainer` | AWS services emulator (S3, SQS, SNS, DDB) |
| MinIO | `GenericContainer` with MinIO image | S3-compatible storage |
| Vault | `VaultContainer` | Secrets |

Verify exact class names against the language-specific Testcontainers docs.

---

## Per-language patterns

### Java (JUnit 5)

```java
@Testcontainers
class ServiceIT {
    @Container
    static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16.4");

    @DynamicPropertySource
    static void registerProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", db::getJdbcUrl);
        registry.add("spring.datasource.username", db::getUsername);
        registry.add("spring.datasource.password", db::getPassword);
    }
}
```

`@Container static` = one container per class (shared across tests). `@Container` (non-static) = one container per test method (slower, fully isolated).

### Python (pytest)

```python
import pytest
from testcontainers.postgres import PostgresContainer

@pytest.fixture(scope="session")
def postgres():
    with PostgresContainer("postgres:16.4") as pg:
        yield pg

def test_query(postgres):
    conn = psycopg.connect(postgres.get_connection_url())
    # ...
```

### Node / TS

```ts
import { PostgreSqlContainer } from '@testcontainers/postgresql';

let container: StartedPostgreSqlContainer;

beforeAll(async () => {
  container = await new PostgreSqlContainer('postgres:16.4').start();
});

afterAll(async () => {
  await container.stop();
});

test('query', async () => {
  const url = container.getConnectionUri();
  // ...
});
```

### Go

```go
import (
    "testing"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
)

func TestThing(t *testing.T) {
    ctx := context.Background()
    pg, err := postgres.Run(ctx, "postgres:16.4",
        postgres.WithDatabase("orders_test"),
        postgres.WithUsername("qa"),
        postgres.WithPassword("Pa$$w0rd-fake"),
    )
    if err != nil { t.Fatal(err) }
    t.Cleanup(func() { pg.Terminate(ctx) })
    // ...
}
```

---

## Performance considerations

Fresh containers add 1-10 seconds per startup. Strategies:

- **`@Container static`** (or `scope='session'` in pytest) — one container, many tests. Reset state via transactions or truncate-per-test (cross-reference test-data-management).
- **Reusable containers** (`.withReuse(true)`) — Testcontainers leaves the container running between test runs locally; the next run reuses it. **Local development only**, not CI.
- **Image pre-pulling** — pull images in a CI cache step, not in the test.
- **Light images** — `postgres:16.4-alpine` is smaller than `postgres:16.4`.
- **Parallel test isolation via DB-per-worker** — start one Postgres container shared across all tests, use `CREATE DATABASE test_<worker>` for per-worker isolation.

A 1000-test suite spinning a fresh Postgres per class is fine; per test is usually too slow.

---

## Ryuk (the cleanup daemon)

Testcontainers starts a small Ryuk container alongside your tests; it watches for parent process exit and reaps orphaned containers. This is why crashed tests don't leave stale containers.

Disabling Ryuk (`TESTCONTAINERS_RYUK_DISABLED=true`) is sometimes necessary in restricted CI environments (no privileged containers) — but then cleanup is your job.

---

## CI patterns

### Docker available

Most CI providers (GitHub Actions Linux runners, GitLab CI, CircleCI, Buildkite agents) have Docker installed and work with Testcontainers out of the box.

### Docker-in-Docker (DinD)

For runners that run jobs inside containers (some K8s-based CI), DinD is required. Set `DOCKER_HOST` so Testcontainers finds the daemon.

### Testcontainers Cloud

A managed Docker host for tests. Useful when:

- Local Docker Desktop is licensed (cost / restrictions).
- CI runners can't run Docker (sandboxed, security policy).
- You want to share container pre-pulls across the org.

Test code is unchanged; environment vars point to Testcontainers Cloud.

### Podman / rootless

Testcontainers supports Podman; some configuration required. Verify against current docs for your language binding.

---

## Wait strategies

Testcontainers starts a container, then waits for it to be "ready" before tests run. The default wait strategy depends on the module; you can override:

```java
new GenericContainer("kafka:7.5.0")
    .waitingFor(Wait.forLogMessage(".*started.*", 1))
    .withStartupTimeout(Duration.ofMinutes(2));
```

Common strategies:

- `Wait.forListeningPort()` — port open.
- `Wait.forLogMessage(regex, occurrences)` — log indicates ready.
- `Wait.forHttp(path).forStatusCode(200)` — HTTP endpoint healthy.
- `Wait.forHealthcheck()` — Docker healthcheck passes.

Wrong wait strategy → tests start before the container is actually ready → flake.

---

## Network and exposed ports

Testcontainers binds container ports to random host ports. Access via:

```java
String url = "jdbc:postgresql://" + container.getHost() + ":" + container.getMappedPort(5432) + "/db";
```

The container's `localhost` is NOT the host's localhost — your app under test running on the host can't talk to the container via `localhost:5432`. Always use `getHost()` + `getMappedPort()` (or the module's `getJdbcUrl()` / `getConnectionUrl()` helpers).

For container-to-container in tests (e.g., app container talks to Postgres container), use Testcontainers' network feature:

```java
Network network = Network.newNetwork();
PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:16.4")
    .withNetwork(network)
    .withNetworkAliases("postgres");

GenericContainer<?> app = new GenericContainer<>("myapp:latest")
    .withNetwork(network)
    .withEnv("DATABASE_URL", "postgresql://qa:Pa$$w0rd-fake@postgres:5432/db");
```

---

## Custom images

`GenericContainer` works with any Docker image. For your own services:

```java
GenericContainer<?> app = new GenericContainer<>(DockerImageName.parse("myorg/myapp:abc123"))
    .withExposedPorts(8080)
    .withEnv("CONFIG", "...")
    .waitingFor(Wait.forHttp("/health"));
```

For images built per-test-run, use `ImageFromDockerfile`:

```java
GenericContainer<?> app = new GenericContainer<>(new ImageFromDockerfile()
    .withFileFromPath(".", Path.of("./src/main/docker")));
```

---

## Common Pitfalls

- **`:latest` image tags** — silent version drift. Always pin.
- **Per-test container when per-class would do** — multiplies test time.
- **Forgetting wait strategy** — race between container start and test connection.
- **Sharing a container across parallel test runs without partitioning** — collisions.
- **Using `localhost` in connection strings** — won't work for container-to-container; use `getHost()`.
- **Not enabling Ryuk in CI** — orphaned containers pile up.
- **Trying to test things Testcontainers doesn't suit** — production-scale data, multi-day soak.
- **Resource exhaustion in CI** — 10 containers per test × 100 tests in parallel = your CI box is dead. Bound concurrency.
- **Mixing in-memory fakes and real containers** — pick one; mixing creates surprising bug surface.
- **Trusting startup logs as ready-state** — use proper wait strategies, not arbitrary sleeps.

---

## Task-Specific Questions

When helping with Testcontainers, ask:

1. Language and Testcontainers library version?
2. Which dependencies need to be real (Postgres / Kafka / Redis / S3 / custom service)?
3. Test scope — class-scoped, method-scoped, session-scoped?
4. CI runtime — Docker / DinD / Podman / Testcontainers Cloud?
5. Existing fake / mock approach being replaced?
6. Performance budget for the test suite?
7. Image registry — public Docker Hub, internal mirror?

---

## Related Skills

- **test-data-management** — Testcontainers + transactional rollback is the canonical fast-and-clean integration test setup.
- **test-environment-management** — Testcontainers sits at the local + CI layer of the environment ladder.
- **ci-test-orchestration** — for parallelism + container resource management.
- **All language unit-test skills** (**jest-vitest** / **pytest** / **junit-testng** / **xunit-nunit** / **go-test** / **rspec**) — for the runners that drive Testcontainers.
- **supertest** / **pytest-api** / **rest-assured** — Testcontainers + these is the canonical full integration test setup.
- **flaky-test-management** — wait strategies are a common flake source.
- **wiremock** — for mocking external HTTP services Testcontainers can't easily virtualize.