The CheckOps Engineer
Testcontainers
Testcontainers starts throwaway Docker containers from tests so integration tests can use real databases, brokers, and browsers.
Latest public signals
Not refreshed yet. Refresh reads the public site and, when the entry has a GitHub repository, the latest release.
Introduction
Testcontainers is a set of libraries that start Docker containers for the length of a test and tear them down afterward. A Java test can ask for PostgreSQL, Kafka, or a browser and receive a mapped port, instead of pointing at a shared database someone else left in an unknown state. Modules exist for common images, and GenericContainer runs an image you name yourself.
The original and largest codebase is testcontainers/testcontainers-java (MIT). Ports exist for Go, .NET, Node.js, Python, Rust, and other languages, each in its own repository under the Testcontainers organization. Docker acquired AtomicJar, the company that employed much of the maintainer team. The libraries stay open source. Testcontainers Desktop, from Docker, is a separate local app for reuse and debugging and is not required to run the libraries.
Features
- Disposable containers: A container starts in a test or a setup method and is removed when the test scope ends, including the Ryuk reaper on the Java side if the JVM dies early.
- Modules: First-party modules cover PostgreSQL, MySQL, MongoDB, Kafka, LocalStack, Nginx, and Selenium-style browsers, among others. The module waits until the service is ready, not only until the container process has started.
- Wait strategies: Log message, HTTP, port, or healthcheck. A test that connects too early is a bug in the wait, not a flake to retry blindly.
- Network and ports: The library maps a container port to a random host port and gives you the JDBC URL or HTTP URL to inject into the app under test.
- Docker Compose modules: An existing compose file can be started for tests that need several services, with the same lifecycle.
- Image configuration: You can set env vars, copy init scripts, and pin the image tag. Pinning the tag is what makes yesterday’s test match today’s.
- Language ports: The idea is the same in
@testcontainers/postgresqlfor Node,testcontainers-python, Testcontainers for Go, and Testcontainers for .NET. APIs are native to each language and are not a single shared package. - Desktop app: Docker’s Testcontainers Desktop can keep containers between runs and show logs. It is optional. CI uses the library and a Docker daemon.
Pros
- The dependency is real: A test against PostgreSQL finds SQL dialect issues that an in-memory stand-in misses.
- No shared mutable database: Each test class or session gets its own container, so order and leftovers matter less.
- CI matches laptops: The same library runs in GitHub Actions once the job has a Docker socket. You do not maintain a hand-built “test database” VM.
- Modules encode readiness: Kafka or Postgres “is up” is defined once, not copied as a sleep into every suite.
- Free libraries: MIT for the Java project. You pay for CI CPU and image pulls, not a Testcontainers seat.
Cons
- A container runtime is mandatory: No Docker (or a supported stand-in) means the tests do not start. Rootless setups, remote daemons, and restricted CI policies need explicit configuration.
- Slower than mocks: Pulling and booting Postgres is seconds to tens of seconds. Use it for integration tests, not for every unit.
- Image drift and registry limits: Unpinned
latesttags change under you. Docker Hub rate limits fail cold CI agents. - Resource use: Several modules in parallel will exhaust laptop RAM. Session-scoped containers help, and they also increase coupling between tests.
- Language ports differ: A Java module’s API is not the Node API. “We use Testcontainers” still requires reading the port you actually import. Desktop and any cloud reuse features are Docker products with their own terms, not part of the MIT library.
Usage and Links (Example)
Java, with JUnit 5:
@Testcontainers
class OrderRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@Test
void migrates() {
String jdbcUrl = postgres.getJdbcUrl();
// point the app or a JDBC client at jdbcUrl
}
}
The testcontainers BOM and the postgresql module artifact must be on the test classpath, and Docker must be running. Node’s equivalent is @testcontainers/postgresql and a await new PostgreSqlContainer("postgres:16-alpine").start().
Resources:
Pricing
- Libraries: Free and open source. Java is MIT. Other language ports publish their own license files; the widely used ones are also permissive open source.
- Docker: You need a container runtime. Docker Desktop’s license terms apply if that is how developers run the daemon. CI runners that include Docker do not add a Testcontainers line item.
- Testcontainers Desktop: A Docker app for local reuse and debugging. It is not required. Check Docker’s current Desktop terms before you assume it is free for your company size.
Recommended for?
- Service integration tests that should talk to Postgres, MySQL, Kafka, or Redis for real.
- JUnit 5 and pytest suites (and the other language ports) where a shared staging database is causing order-dependent failures.
- CI already allowed to run Docker, including GitHub-hosted runners with the default engine.
- Not for pure unit tests. Mock the port. Start a container only when the behaviour under test crosses a process boundary.
Conclusion
Testcontainers is the standard way to give an integration test a real dependency without a permanent environment. Pin image tags, wait for readiness, and keep containers off the unit-test path. The Java library is the reference implementation; pick the port that matches the test language. Desktop tooling from Docker is optional and licensed separately from the libraries.