Skip to content

TestContainers Cheatsheet

Installation

Prerequisites

Requirement Version Verification Command
Docker Latest stable docker --version
Java JDK 8 or higher java -version
Maven/Gradle Latest mvn -v or gradle -v

Maven Dependencies

<!-- Core TestContainers -->
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

<!-- JUnit 5 Integration -->
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

<!-- Database Modules -->
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mysql</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mongodb</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

Gradle Dependencies

// Kotlin DSL
testImplementation("org.testcontainers:testcontainers:1.19.3")
testImplementation("org.testcontainers:junit-jupiter:1.19.3")
testImplementation("org.testcontainers:postgresql:1.19.3")
testImplementation("org.testcontainers:mysql:1.19.3")
testImplementation("org.testcontainers:kafka:1.19.3")
// Groovy DSL
testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'
testImplementation 'org.testcontainers:postgresql:1.19.3'

Docker Setup by Platform

Platform Installation Command
Ubuntu/Debian curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
macOS brew install --cask docker
Windows Download Docker Desktop from docker.com
Linux Post-Install sudo usermod -aG docker $USER && newgrp docker

Basic Commands

Container Lifecycle Operations

Command Description
container.start() Start a container instance
container.stop() Stop and remove the container
container.isRunning() Check if container is currently running
container.getContainerId() Get Docker container ID
container.getDockerImageName() Get the image name used
container.getLogs() Retrieve all container logs as string
container.getHost() Get host address (usually localhost)
container.getMappedPort(int) Get host port mapped to container port
container.getExposedPorts() List all exposed container ports
container.getContainerInfo() Get detailed container information

Generic Container Creation

Command Description
new GenericContainer("image:tag") Create container from any Docker image
.withExposedPorts(port1, port2) Expose container ports to host
.withEnv("KEY", "value") Set environment variable
.withCommand("cmd", "arg1") Override container command
.withLabel("key", "value") Add Docker label to container
.withNetworkMode("host") Set container network mode
.withPrivilegedMode() Run container in privileged mode
.withCreateContainerCmdModifier(cmd) Modify container creation command
.withStartupTimeout(Duration) Set maximum startup wait time
.withReuse(true) Enable container reuse across tests

Database Containers

Command Description
new PostgreSQLContainer("postgres:15") Create PostgreSQL container
new MySQLContainer("mysql:8.0") Create MySQL container
new MongoDBContainer("mongo:6.0") Create MongoDB container
.withDatabaseName("dbname") Set database name
.withUsername("user") Set database username
.withPassword("pass") Set database password
.getJdbcUrl() Get JDBC connection URL
.getReplicaSetUrl() Get MongoDB replica set URL (MongoDB)
.withInitScript("init.sql") Run SQL script on startup
.withConfigurationOverride("path") Override database configuration

Advanced Usage

Wait Strategies

Command Description
.waitingFor(Wait.forListeningPort()) Wait until port is listening
.waitingFor(Wait.forHttp("/health")) Wait for HTTP endpoint to respond
.waitingFor(Wait.forLogMessage(".*ready.*", 1)) Wait for specific log message
.waitingFor(Wait.forHealthcheck()) Wait for Docker healthcheck to pass
.waitingFor(Wait.forSuccessfulCommand("cmd")) Wait for command to succeed
.forStatusCode(200) Specify expected HTTP status code
.forStatusCodeMatching(code -> code < 500) Custom status code matcher
.withStartupTimeout(Duration.ofMinutes(2)) Set startup timeout duration
.withReadTimeout(Duration.ofSeconds(10)) Set HTTP read timeout
.forResponsePredicate(response -> true) Custom response validation

File and Volume Operations

Command Description
.withFileSystemBind("host", "container") Bind mount host directory
.withFileSystemBind(path, target, BindMode.READ_ONLY) Mount with specific mode
.withClasspathResourceMapping("res", "path") Mount classpath resource
.copyFileToContainer(MountableFile, "path") Copy file into container
.withCopyFileToContainer(file, path) Copy file during startup
.withTmpFs(Map.of("/tmp", "rw")) Mount tmpfs filesystem
MountableFile.forClasspathResource("file") Reference classpath file
MountableFile.forHostPath("/path") Reference host file system path
BindMode.READ_WRITE Allow read and write access
BindMode.READ_ONLY Allow only read access

Networking

Command Description
Network.newNetwork() Create custom Docker network
.withNetwork(network) Attach container to network
.withNetworkAliases("alias") Set network alias for container
.dependsOn(otherContainer) Define container dependency
.withExtraHost("hostname", "ip") Add entry to /etc/hosts
.getNetworkAliases() Get all network aliases
.withAccessToHost(true) Allow container to access host
Network.SHARED Use shared network across tests
.withNetworkMode("bridge") Set specific network mode
.getNetwork() Get network container is attached to

Container Execution

Command Description
.execInContainer("cmd", "arg1") Execute command in running container
.execInContainer(Charset, "cmd") Execute with specific charset
result.getStdout() Get standard output from execution
result.getStderr() Get standard error from execution
result.getExitCode() Get command exit code
.copyFileFromContainer("path", consumer) Copy file from container
.followOutput(consumer) Stream container logs
new Slf4jLogConsumer(logger) Log output to SLF4J logger
new ToStringConsumer() Capture output as string
new WaitingConsumer() Wait for specific output

JUnit 5 Integration

Command Description
@Testcontainers Enable TestContainers extension
@Container Mark field as container to manage
static @Container Share container across all tests
@DynamicPropertySource Inject container properties
registry.add("prop", container::getUrl) Add dynamic property
@BeforeAll Setup containers before all tests
@AfterAll Cleanup containers after all tests
@Nested Group tests with shared containers
Startables.deepStart(containers).join() Start multiple containers in parallel
.withReuse(true) Reuse container across test runs

Configuration

TestContainers Properties

Create testcontainers.properties in src/test/resources:

# Docker host configuration
docker.host=unix:///var/run/docker.sock
# docker.host=tcp://localhost:2375

# Container reuse (requires Docker labels support)
testcontainers.reuse.enable=true

# Image pull policy
testcontainers.image.pull.policy=DefaultPullPolicy

# Ryuk container (cleanup)
testcontainers.ryuk.disabled=false
testcontainers.ryuk.container.image=testcontainers/ryuk:0.5.1

# Checks
testcontainers.checks.disable=false

Docker Compose Integration

@Container
static DockerComposeContainer environment = 
    new DockerComposeContainer(new File("docker-compose.yml"))
        .withExposedService("db", 5432)
        .withExposedService("redis", 6379)
        .withLocalCompose(true)
        .withPull(true)
        .waitingFor("db", Wait.forHealthcheck());

Custom Container Configuration

public class CustomPostgreSQLContainer extends PostgreSQLContainer<CustomPostgreSQLContainer> {
    private static final String IMAGE_VERSION = "postgres:15-alpine";

    public CustomPostgreSQLContainer() {
        super(IMAGE_VERSION);
        this.withDatabaseName("testdb")
            .withUsername("testuser")
            .withPassword("testpass")
            .withInitScript("init.sql")
            .withCommand("postgres -c max_connections=200");
    }
}

Spring Boot Integration

@SpringBootTest
@Testcontainers
class ApplicationTests {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
        .withDatabaseName("testdb");

    @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.jpa.hibernate.ddl-auto", () -> "create-drop");
    }

    @Test
    void contextLoads() {
        // Test with real database
    }
}

Common Use Cases

Use Case 1: PostgreSQL Integration Test

@Testcontainers
@SpringBootTest
class UserRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test")
        .withInitScript("schema.sql");

    @DynamicPropertySource
    static void registerProperties(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("john@example.com", "John Doe");
        userRepository.save(user);

        Optional<User> found = userRepository.findByEmail("john@example.com");
        assertTrue(found.isPresent());
        assertEquals("John Doe", found.get().getName());
    }
}

Use Case 2: Kafka Message Processing Test

@Testcontainers
class KafkaIntegrationTest {

    @Container
    static KafkaContainer kafka = new KafkaContainer(
        DockerImageName.parse("confluentinc/cp-kafka:7.5.0")
    );

    @DynamicPropertySource
    static void kafkaProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
    }

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Autowired
    private MessageListener messageListener;

    @Test
    void shouldProcessMessage() throws Exception {
        String topic = "test-topic";
        String message = "Hello Kafka!";

        kafkaTemplate.send(topic, message);

        // Wait for message processing
        await().atMost(Duration.ofSeconds(10))
               .until(() -> messageListener.getReceivedMessages().size() == 1);

        assertEquals(message, messageListener.getReceivedMessages().get(0));
    }
}

Use Case 3: Multi-Container Microservices Test

@Testcontainers
class MicroservicesIntegrationTest {

    static Network network = Network.newNetwork();

    @Container
    static PostgreSQLContainer<?> database = new PostgreSQLContainer<>("postgres:15-alpine")
        .withNetwork(network)
        .withNetworkAliases("database")
        .withDatabaseName("orders");

    @Container
    static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
        .withNetwork(network)
        .withNetworkAliases("redis")
        .withExposedPorts(6379);

    @Container
    static GenericContainer<?> orderService = new GenericContainer<>("order-service:latest")
        .withNetwork(network)
        .withExposedPorts(8080)
        .withEnv("DATABASE_URL", "jdbc:postgresql://database:5432/orders")
        .withEnv("REDIS_HOST", "redis")
        .dependsOn(database, redis)
        .waitingFor(Wait.forHttp("/actuator/health").forStatusCode(200));

    @Test
    void shouldCreateOrder() {
        String baseUrl = "http://" + orderService.getHost() + ":" 
                       + orderService.getMappedPort(8080);

        // Make HTTP request to order service
        RestTemplate restTemplate = new RestTemplate();
        Order order = new Order("item-123", 2);

        ResponseEntity<Order> response = restTemplate.postForEntity(
            baseUrl + "/orders", order, Order.class
        );

        assertEquals(HttpStatus.CREATED, response.getStatusCode());
        assertNotNull(response.getBody().getId());
    }
}

Use Case 4: MongoDB Integration Test

@Testcontainers
@DataMongoTest
class ProductRepositoryTest {

    @Container
    static MongoDBContainer mongodb = new MongoDBContainer("mongo:6.0")
        .withExposedPorts(27017);

    @DynamicPropertySource
    static void mongoProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.mongodb.uri", mongodb::getReplicaSetUrl);
    }

    @Autowired
    private ProductRepository productRepository;

    @Test
    void shouldFindProductsByCategory() {
        // Insert test data
        productRepository.save(new Product("Laptop", "Electronics", 999.99));
        productRepository.save(new Product("Mouse", "Electronics", 29.99));
        productRepository.save(new Product("Desk", "Furniture", 299.99));

        // Query by category
        List<Product> electronics = productRepository.findByCategory("Electronics");

        assertEquals(2, electronics.size());
        assertTrue(electronics.stream()
                  .allMatch(p -> p.getCategory().equals("Electronics")));
    }
}

Use Case 5: Docker Compose Test Environment

@Testcontainers
class FullStackIntegrationTest {

    @Container
    static DockerComposeContainer<?> environment = new DockerComposeContainer<>(
        new File("src/test/resources/docker-compose-test.yml")
    )
        .withExposedService("api", 8080, 
            Wait.forHttp("/health").forStatusCode(200))
        .withExposedService("postgres", 5432, 
            Wait.forListeningPort())
        .withExposedService("redis", 6379)
        .withLocalCompose(true);

    @Test
    void shouldConnectToAllServices() {
        String apiHost = environment.getServiceHost("api", 8080);
        Integer apiPort = environment.getServicePort("api", 8080);

        String apiUrl = "http://" + apiHost + ":" + apiPort;

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.getForEntity(
            apiUrl + "/health", String.class
        );

        assertEquals(HttpStatus.OK, response.getStatusCode());
    }
}

Best Practices

Container Management

  • Use static containers for test classes: Share containers across all test methods to reduce startup time and resource usage
  • Implement container reuse: Enable testcontainers.reuse.enable=true for faster local development test cycles
  • Clean up resources: Use @AfterAll or try-with-resources when manually managing containers outside JUnit integration
  • Use specific image tags: Avoid latest tag; use specific versions like postgres:15-alpine for reproducible tests

Performance Optimization

  • Parallel container startup: Use Startables.deepStart() to start multiple independent containers simultaneously
  • Minimize container restarts: Use class-level @Container instead of method-level when possible
  • Choose lightweight images: Prefer Alpine-based images (e.g., postgres:15-alpine) to reduce pull time and disk usage
  • Implement wait strategies wisely: Use appropriate wait strategies to avoid unnecessary delays while ensuring container readiness

Testing Strategy

  • Test against production-like dependencies: Use real database engines and message brokers instead of embedded versions
  • Isolate test data: Ensure each test creates and cleans up its own data to prevent test interdependencies
  • Use network aliases: Create custom networks and use meaningful aliases for inter-container communication
  • Implement health checks: Always configure proper wait strategies to ensure containers are fully ready before tests execute

CI/CD Integration

  • Verify Docker availability: Ensure CI environment has Docker installed and accessible
  • Configure timeouts appropriately: Set realistic startup timeouts considering CI environment performance
  • Use image caching: Configure CI to cache Docker images to speed up subsequent runs
  • Monitor resource usage: Be aware of memory and CPU limits in CI environments; adjust container configurations accordingly

Security and Maintenance

  • Keep TestContainers updated: Regularly update to latest versions for security patches and new features
  • Use official images: Prefer official Docker images from verified publishers
  • Avoid privileged mode: Only use .withPrivilegedMode() when absolutely necessary
  • Review exposed ports: Only expose ports that are actually needed for testing

Troubleshooting

Issue Solution
Container fails to start: "Cannot connect to Docker daemon" Ensure Docker is running: docker ps. On Linux, add user to docker group: sudo usermod -aG docker $USER
Tests timeout during container startup Increase timeout: .withStartupTimeout(Duration.ofMinutes(5)) or check Docker resources (CPU/Memory)
Port binding conflicts: "Address already in use" Use dynamic ports: container.getMappedPort() instead of fixed ports, or stop conflicting services
Image pull fails: "manifest unknown" Verify image name and tag exist on Docker Hub. Use specific tags instead of latest
Container starts but tests fail to connect Check wait strategy is appropriate: add .waitingFor(Wait.forListeningPort()) or .waitingFor(Wait.forHealthcheck())
Out of memory errors during tests Reduce number of parallel containers, increase Docker memory limit in Docker Desktop settings, or use .withReuse(true)
"Ryuk container not found" warnings Disable Ryuk if needed: set testcontainers.ryuk.disabled=true in properties (not recommended for production)
Tests work locally but fail in CI Ensure CI has Docker installed, check timeout settings, verify network connectivity, review CI environment resource limits
Database connection refused Wait for database to be ready: use .waitingFor(Wait.forListeningPort()) and verify JDBC URL with container.getJdbcUrl()
Container logs show errors but tests pass Enable log following: container.followOutput(new Slf4jLogConsumer(log)) to debug container issues
Slow test execution Use container reuse, share containers at class level, start containers in parallel with Startables.deepStart()
"No such file or directory" when mounting volumes Use absolute paths or MountableFile.forClasspathResource() for classpath resources
Network communication between containers fails Create custom network: Network.newNetwork() and attach containers with .withNetwork(network) and .withNetworkAliases()
TestContainers properties not being read Ensure testcontainers.properties is in src/test/resources and properly formatted
Container cleanup not happening Check Ryuk container is running: docker ps | grep ryuk. Ensure tests complete normally without hanging

Available Modules

Module Maven Artifact Description
PostgreSQL testcontainers-postgresql PostgreSQL database containers
MySQL testcontainers-mysql MySQL database containers
MongoDB testcontainers-mongodb MongoDB document database
Kafka testcontainers-kafka Apache Kafka message broker
Redis testcontainers-redis Redis in-memory data store
Elasticsearch testcontainers-elasticsearch Elasticsearch search engine
Cassandra testcontainers-cassandra Cassandra NoSQL database
RabbitMQ testcontainers-rabbitmq RabbitMQ message broker
Selenium testcontainers-selenium Browser automation containers
LocalStack testcontainers-localstack AWS services emulation
Nginx