or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcontainer-lifecycle.mddocker-client.mddocker-compose.mdimage-building.mdimage-management.mdimage-pull-policies.mdindex.mdjunit-jupiter-integration.mdlifecycle.mdmodules-overview.mdnetwork-configuration.mdoutput-handling.mdstartup-checks.mdutility-classes.mdwait-strategies.md
tile.json

docker-compose.mddocs/

Docker Compose Support

Launch and manage multi-container Docker Compose environments for integration testing. Testcontainers provides support for running complete Docker Compose applications, enabling tests against complex multi-service architectures defined in compose files.

Capabilities

DockerComposeContainer

Launch Docker Compose environments from compose files with full control over service exposure and lifecycle.

/**
 * Container that manages a Docker Compose environment.
 * Starts all services defined in compose file(s) and provides access to them.
 *
 * @param <SELF> Self-referential generic type for fluent API
 */
public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>>
    implements Startable {

    /**
     * Create a Docker Compose container from a single compose file.
     *
     * @param composeFile the compose file
     * @deprecated use constructor with DockerImageName as first parameter
     */
    @Deprecated
    public DockerComposeContainer(File composeFile);

    /**
     * Create a Docker Compose container from multiple compose files.
     * Files are merged in order (later files override earlier ones).
     *
     * @param composeFiles list of compose files
     * @deprecated use constructor with DockerImageName as first parameter
     */
    @Deprecated
    public DockerComposeContainer(List<File> composeFiles);

    /**
     * Create a Docker Compose container with a custom docker compose image.
     *
     * @param image custom docker compose image
     * @param composeFiles compose files
     */
    public DockerComposeContainer(DockerImageName image, File... composeFiles);

    /**
     * Create a Docker Compose container with a custom docker compose image.
     *
     * @param image custom docker compose image
     * @param composeFiles list of compose files
     */
    public DockerComposeContainer(DockerImageName image, List<File> composeFiles);

    /**
     * Create a Docker Compose container with custom image and identifier.
     *
     * @param image custom docker compose image
     * @param identifier unique identifier for the environment
     * @param composeFiles compose files
     */
    public DockerComposeContainer(DockerImageName image, String identifier, File... composeFiles);

    /**
     * Create a Docker Compose container with custom image, identifier, and single file.
     *
     * @param image custom docker compose image
     * @param identifier unique identifier for the environment
     * @param composeFile compose file
     */
    public DockerComposeContainer(DockerImageName image, String identifier, File composeFile);

    /**
     * Create a Docker Compose container with custom image, identifier, and list of files.
     *
     * @param image custom docker compose image
     * @param identifier unique identifier for the environment
     * @param composeFiles list of compose files
     */
    public DockerComposeContainer(DockerImageName image, String identifier, List<File> composeFiles);

    /**
     * Create a Docker Compose container with identifier.
     *
     * @param identifier unique identifier for the environment
     * @param composeFiles compose files
     * @deprecated use constructor with DockerImageName as first parameter
     */
    @Deprecated
    public DockerComposeContainer(String identifier, File... composeFiles);

    /**
     * Create a Docker Compose container with identifier.
     *
     * @param identifier unique identifier for the environment
     * @param composeFiles list of compose files
     * @deprecated use constructor with DockerImageName as first parameter
     */
    @Deprecated
    public DockerComposeContainer(String identifier, List<File> composeFiles);

    /**
     * Start only specific services (instead of all services in the compose file).
     *
     * @param services names of services to start
     * @return this container for method chaining
     */
    public SELF withServices(@NonNull String... services);

    /**
     * Expose a service port to the host.
     *
     * @param serviceName name of the service in docker-compose.yml
     * @param servicePort port number in the service
     * @return this container for method chaining
     */
    public SELF withExposedService(String serviceName, int servicePort);

    /**
     * Expose a service port with a wait strategy.
     *
     * @param serviceName name of the service
     * @param servicePort port number
     * @param waitStrategy wait strategy for the service
     * @return this container for method chaining
     */
    public SELF withExposedService(String serviceName, int servicePort, WaitStrategy waitStrategy);

    /**
     * Expose a specific instance of a scaled service.
     * Note: Returns DockerComposeContainer, not SELF generic type.
     *
     * @param serviceName name of the service
     * @param instance instance number (1-based)
     * @param servicePort port number
     * @return this container for method chaining
     */
    public DockerComposeContainer withExposedService(String serviceName, int instance, int servicePort);

    /**
     * Expose a specific instance of a scaled service with a wait strategy.
     * Note: Returns DockerComposeContainer, not SELF generic type.
     *
     * @param serviceName name of the service
     * @param instance instance number (1-based)
     * @param servicePort port number
     * @param waitStrategy wait strategy for the service
     * @return this container for method chaining
     */
    public DockerComposeContainer withExposedService(String serviceName, int instance, int servicePort, WaitStrategy waitStrategy);

    /**
     * Set a wait strategy for a service without exposing ports.
     *
     * @param serviceName name of the service
     * @param waitStrategy wait strategy for the service
     * @return this container for method chaining
     */
    public SELF waitingFor(String serviceName, WaitStrategy waitStrategy);

    /**
     * Scale a service to multiple instances.
     *
     * @param serviceName name of the service to scale
     * @param numInstances number of instances to run
     * @return this container for method chaining
     */
    public SELF withScaledService(String serviceName, int numInstances);

    /**
     * Control whether to pull images before starting.
     *
     * @param pull whether to pull images
     * @return this container for method chaining
     */
    public SELF withPull(boolean pull);

    /**
     * Control whether to build images before starting.
     *
     * @param build whether to build images
     * @return this container for method chaining
     */
    public SELF withBuild(boolean build);

    /**
     * Set an environment variable for all services.
     *
     * @param key environment variable name
     * @param value environment variable value
     * @return this container for method chaining
     */
    public SELF withEnv(String key, String value);

    /**
     * Set multiple environment variables for all services.
     *
     * @param env map of environment variables
     * @return this container for method chaining
     */
    public SELF withEnv(Map<String, String> env);

    /**
     * Set docker-compose command options.
     *
     * @param options command-line options
     * @return this container for method chaining
     */
    public SELF withOptions(String... options);

    /**
     * Configure image removal behavior after shutdown.
     *
     * @param removeImages which images to remove
     * @return this container for method chaining
     */
    public SELF withRemoveImages(RemoveImages removeImages);

    /**
     * Configure volume removal behavior after shutdown.
     *
     * @param removeVolumes whether to remove volumes
     * @return this container for method chaining
     */
    public SELF withRemoveVolumes(boolean removeVolumes);

    /**
     * Enable tailing logs from child containers.
     *
     * @param tailChildContainers whether to tail logs
     * @return this container for method chaining
     */
    public SELF withTailChildContainers(boolean tailChildContainers);

    /**
     * Set a log consumer for a specific service.
     *
     * @param serviceName name of the service
     * @param consumer consumer to receive log output
     * @return this container for method chaining
     */
    public SELF withLogConsumer(String serviceName, Consumer<OutputFrame> consumer);

    /**
     * Set the startup timeout for all services.
     *
     * @param startupTimeout the startup timeout duration
     * @return this container for method chaining
     */
    public SELF withStartupTimeout(Duration startupTimeout);

    /**
     * Copy files into containers based on include patterns.
     *
     * @param fileCopyInclusions patterns for files to copy
     * @return this container for method chaining
     */
    public SELF withCopyFilesInContainer(String... fileCopyInclusions);

    /**
     * Get the host for accessing a service.
     *
     * @param serviceName name of the service
     * @param servicePort port of the service
     * @return host address
     */
    public String getServiceHost(String serviceName, Integer servicePort);

    /**
     * Get the mapped port for accessing a service from the host.
     *
     * @param serviceName name of the service
     * @param servicePort original port in the service
     * @return mapped port on the host
     */
    public Integer getServicePort(String serviceName, Integer servicePort);

    /**
     * Get the container state for a specific service.
     *
     * @param serviceName name of the service
     * @return optional container state, empty if service not found
     */
    public Optional<ContainerState> getContainerByServiceName(String serviceName);

    /**
     * Start all services in the compose environment.
     */
    @Override
    public void start();

    /**
     * Stop all services in the compose environment.
     */
    @Override
    public void stop();

    /**
     * Image removal options for compose down operation.
     * Note: Each class (DockerComposeContainer and ComposeContainer) defines its own enum.
     */
    public enum RemoveImages {
        /** Remove all images used by any service */
        ALL,
        /** Remove only images that don't have a custom tag set by the `image` field */
        LOCAL
    }
}

Usage Examples:

import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.io.File;
import java.time.Duration;

// Basic Docker Compose setup
DockerComposeContainer<?> environment = new DockerComposeContainer<>(
        new File("docker-compose.yml"))
    .withExposedService("web", 8080)
    .withExposedService("db", 5432);

environment.start();

// Get connection details
String webHost = environment.getServiceHost("web", 8080);
Integer webPort = environment.getServicePort("web", 8080);
String webUrl = String.format("http://%s:%d", webHost, webPort);

// With wait strategies
DockerComposeContainer<?> withWaits = new DockerComposeContainer<>(
        new File("docker-compose.yml"))
    .withExposedService("api", 8080,
        Wait.forHttp("/health")
            .forStatusCode(200)
            .withStartupTimeout(Duration.ofSeconds(60)))
    .withExposedService("database", 5432,
        Wait.forListeningPort()
            .withStartupTimeout(Duration.ofSeconds(30)));

withWaits.start();

// Multiple compose files (override pattern)
DockerComposeContainer<?> multiFile = new DockerComposeContainer<>(
        List.of(
            new File("docker-compose.yml"),
            new File("docker-compose.test.yml")  // Overrides for testing
        ))
    .withExposedService("app", 8080);

// Service scaling
DockerComposeContainer<?> scaled = new DockerComposeContainer<>(
        new File("docker-compose.yml"))
    .withExposedService("worker", 8080)
    .withScaledService("worker", 3);  // Run 3 worker instances

scaled.start();

// Environment variables
DockerComposeContainer<?> withEnv = new DockerComposeContainer<>(
        new File("docker-compose.yml"))
    .withEnv("ENVIRONMENT", "test")
    .withEnv("LOG_LEVEL", "DEBUG")
    .withExposedService("app", 8080);

// Using try-with-resources
try (DockerComposeContainer<?> env = new DockerComposeContainer<>(
        new File("docker-compose.yml"))
    .withExposedService("web", 80)) {

    env.start();

    // Run tests
    String url = String.format("http://%s:%d",
        env.getServiceHost("web", 80),
        env.getServicePort("web", 80));

} // Environment automatically stopped and cleaned up

ComposeContainer

Modern Docker Compose V2 support (alternative to DockerComposeContainer).

/**
 * Modern Docker Compose V2 container implementation.
 * Uses either Compose V2 in Docker binary or containerized Compose V2.
 */
public class ComposeContainer implements Startable {
    // Constructors
    public ComposeContainer(DockerImageName image, File... composeFiles);
    public ComposeContainer(DockerImageName image, List<File> composeFiles);
    public ComposeContainer(DockerImageName image, String identifier, File... composeFiles);
    public ComposeContainer(DockerImageName image, String identifier, File composeFile);
    public ComposeContainer(DockerImageName image, String identifier, List<File> composeFiles);
    public ComposeContainer(File... composeFiles);
    public ComposeContainer(List<File> composeFiles);
    public ComposeContainer(String identifier, File... composeFiles);
    public ComposeContainer(String identifier, List<File> composeFiles);

    // Service configuration
    public ComposeContainer withServices(String... services);
    public ComposeContainer withExposedService(String serviceName, int servicePort);
    public ComposeContainer withExposedService(String serviceName, int instance, int servicePort);
    public ComposeContainer withExposedService(String serviceName, int servicePort, WaitStrategy waitStrategy);
    public ComposeContainer withExposedService(String serviceName, int instance, int servicePort, WaitStrategy waitStrategy);
    public ComposeContainer waitingFor(String serviceName, WaitStrategy waitStrategy);
    public ComposeContainer withScaledService(String serviceBaseName, int numInstances);

    // Environment and configuration
    public ComposeContainer withEnv(String key, String value);
    public ComposeContainer withEnv(Map<String, String> env);
    public ComposeContainer withOptions(String... options);
    public ComposeContainer withBuild(boolean build);
    public ComposeContainer withPull(boolean pull);
    public ComposeContainer withRemoveImages(RemoveImages removeImages);
    public ComposeContainer withRemoveVolumes(boolean removeVolumes);
    public ComposeContainer withStartupTimeout(Duration startupTimeout);
    public ComposeContainer withTailChildContainers(boolean tailChildContainers);
    public ComposeContainer withLogConsumer(String serviceName, Consumer<OutputFrame> consumer);
    public ComposeContainer withCopyFilesInContainer(String... fileCopyInclusions);

    // Service access
    public String getServiceHost(String serviceName, Integer servicePort);
    public Integer getServicePort(String serviceName, Integer servicePort);
    public Optional<ContainerState> getContainerByServiceName(String serviceName);

    // Lifecycle
    @Override
    public void start();

    @Override
    public void stop();

    /**
     * Image removal options for compose down.
     * Note: Each class (DockerComposeContainer and ComposeContainer) defines its own enum.
     */
    public enum RemoveImages {
        /** Remove all images used by any service */
        ALL,
        /** Remove only images that don't have a custom tag set by the `image` field */
        LOCAL
    }
}

Complete Usage Examples

Full Stack Application Testing

import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.time.Duration;

public class FullStackIntegrationTest {

    @Test
    public void testFullApplication() {
        // docker-compose.yml contains:
        // - postgres (database)
        // - redis (cache)
        // - api (backend service)
        // - web (frontend)

        try (DockerComposeContainer<?> environment =
                new DockerComposeContainer<>(new File("docker-compose.yml"))
                    // Database
                    .withExposedService("postgres", 5432,
                        Wait.forListeningPort()
                            .withStartupTimeout(Duration.ofSeconds(60)))
                    // Cache
                    .withExposedService("redis", 6379,
                        Wait.forListeningPort())
                    // API with health check
                    .withExposedService("api", 8080,
                        Wait.forHttp("/health")
                            .forStatusCode(200)
                            .withStartupTimeout(Duration.ofMinutes(2)))
                    // Frontend
                    .withExposedService("web", 80,
                        Wait.forHttp("/")
                            .forStatusCode(200))
                    // Environment configuration
                    .withEnv("ENVIRONMENT", "test")
                    .withEnv("LOG_LEVEL", "INFO")) {

            environment.start();

            // Get service URLs
            String apiUrl = String.format("http://%s:%d",
                environment.getServiceHost("api", 8080),
                environment.getServicePort("api", 8080));

            String webUrl = String.format("http://%s:%d",
                environment.getServiceHost("web", 80),
                environment.getServicePort("web", 80));

            // Run integration tests
            testApiEndpoints(apiUrl);
            testWebInterface(webUrl);
            testEndToEndFlow(webUrl, apiUrl);
        }
    }

    private void testApiEndpoints(String apiUrl) {
        // Test API endpoints
    }

    private void testWebInterface(String webUrl) {
        // Test frontend
    }

    private void testEndToEndFlow(String webUrl, String apiUrl) {
        // Test complete user flow
    }
}

Microservices Integration Testing

import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.io.File;

public class MicroservicesIntegrationTest {

    @Test
    public void testMicroservicesInteraction() {
        // docker-compose.yml contains multiple microservices:
        // - user-service
        // - order-service
        // - payment-service
        // - notification-service
        // - api-gateway

        DockerComposeContainer<?> services = new DockerComposeContainer<>(
                new File("microservices-compose.yml"))
            // Expose each service
            .withExposedService("user-service", 8081,
                Wait.forHttp("/actuator/health").forStatusCode(200))
            .withExposedService("order-service", 8082,
                Wait.forHttp("/actuator/health").forStatusCode(200))
            .withExposedService("payment-service", 8083,
                Wait.forHttp("/actuator/health").forStatusCode(200))
            .withExposedService("notification-service", 8084,
                Wait.forHttp("/actuator/health").forStatusCode(200))
            .withExposedService("api-gateway", 8080,
                Wait.forHttp("/health").forStatusCode(200))
            // Set test environment
            .withEnv("SPRING_PROFILES_ACTIVE", "test")
            .withPull(true)
            .withBuild(true);

        services.start();

        // Test through API gateway
        String gatewayUrl = String.format("http://%s:%d",
            services.getServiceHost("api-gateway", 8080),
            services.getServicePort("api-gateway", 8080));

        // Test inter-service communication
        testUserCreation(gatewayUrl);
        testOrderPlacement(gatewayUrl);
        testPaymentProcessing(gatewayUrl);
        testNotificationDelivery(gatewayUrl);

        services.stop();
    }
}

Scaled Services Testing

import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class LoadBalancingTest {

    @Test
    public void testLoadBalancedWorkers() {
        // docker-compose.yml contains:
        // - nginx (load balancer)
        // - worker (application service)
        // - redis (shared state)

        DockerComposeContainer<?> cluster = new DockerComposeContainer<>(
                new File("docker-compose.yml"))
            .withExposedService("nginx", 80,
                Wait.forHttp("/").forStatusCode(200))
            .withExposedService("redis", 6379,
                Wait.forListeningPort())
            // Scale workers to 5 instances
            .withScaledService("worker", 5)
            .withExposedService("worker", 8080,
                Wait.forHttp("/health").forStatusCode(200));

        cluster.start();

        // Get load balancer URL
        String lbUrl = String.format("http://%s:%d",
            cluster.getServiceHost("nginx", 80),
            cluster.getServicePort("nginx", 80));

        // Test that requests are distributed across workers
        List<String> workerIds = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            String workerId = makeRequest(lbUrl + "/worker-id");
            workerIds.add(workerId);
        }

        // Verify load balancing
        long uniqueWorkers = workerIds.stream().distinct().count();
        assertTrue(uniqueWorkers > 1, "Requests should be distributed");

        cluster.stop();
    }
}

Development Environment Testing

import org.testcontainers.containers.DockerComposeContainer;
import java.io.File;
import java.util.List;

public class DevelopmentEnvironmentTest {

    @Test
    public void testDevelopmentSetup() {
        // Combine base and override files
        DockerComposeContainer<?> devEnv = new DockerComposeContainer<>(
                List.of(
                    new File("docker-compose.yml"),           // Base configuration
                    new File("docker-compose.dev.yml")        // Dev overrides
                ))
            .withExposedService("app", 8080)
            .withExposedService("db", 5432)
            .withEnv("DEBUG", "true")
            .withEnv("HOT_RELOAD", "true")
            .withTailChildContainers(true);  // See all container logs

        devEnv.start();

        // Development environment is ready
        String appUrl = String.format("http://%s:%d",
            devEnv.getServiceHost("app", 8080),
            devEnv.getServicePort("app", 8080));

        // Test with hot reload enabled
        testWithCodeChanges(appUrl);

        devEnv.stop();
    }
}

Database Migration Testing

import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;

public class DatabaseMigrationTest {

    @Test
    public void testMigrations() throws Exception {
        // docker-compose.yml contains:
        // - postgres (database)
        // - flyway (migration tool)

        DockerComposeContainer<?> dbSetup = new DockerComposeContainer<>(
                new File("docker-compose.yml"))
            .withExposedService("postgres", 5432,
                Wait.forListeningPort())
            .withExposedService("flyway", 0,  // One-shot container
                Wait.forLogMessage(".*Successfully applied.*", 1))
            .withEnv("POSTGRES_DB", "testdb")
            .withEnv("POSTGRES_USER", "testuser")
            .withEnv("POSTGRES_PASSWORD", "testpass");

        dbSetup.start();

        // Wait for migrations to complete
        Thread.sleep(5000);

        // Connect to database
        String jdbcUrl = String.format("jdbc:postgresql://%s:%d/testdb",
            dbSetup.getServiceHost("postgres", 5432),
            dbSetup.getServicePort("postgres", 5432));

        try (Connection conn = DriverManager.getConnection(
                jdbcUrl, "testuser", "testpass")) {

            // Verify migrations
            ResultSet rs = conn.createStatement().executeQuery(
                "SELECT COUNT(*) FROM flyway_schema_history");
            rs.next();
            int migrationCount = rs.getInt(1);

            assertTrue(migrationCount > 0, "Migrations should have run");

            // Verify schema
            rs = conn.createStatement().executeQuery(
                "SELECT table_name FROM information_schema.tables " +
                "WHERE table_schema = 'public'");

            List<String> tables = new ArrayList<>();
            while (rs.next()) {
                tables.add(rs.getString(1));
            }

            assertTrue(tables.contains("users"), "Users table should exist");
            assertTrue(tables.contains("orders"), "Orders table should exist");
        }

        dbSetup.stop();
    }
}

Custom Docker Compose Options

import org.testcontainers.containers.DockerComposeContainer;
import java.io.File;

public class CustomComposeOptionsTest {

    @Test
    public void testWithCustomOptions() {
        DockerComposeContainer<?> custom = new DockerComposeContainer<>(
                new File("docker-compose.yml"))
            .withExposedService("app", 8080)
            // Custom docker-compose options
            .withOptions(
                "--compatibility",           // Enable compatibility mode
                "--renew-anon-volumes"      // Recreate anonymous volumes
            )
            .withPull(true)                 // Always pull latest images
            .withBuild(true)                // Build images before starting
            .withRemoveImages(DockerComposeContainer.RemoveImages.LOCAL);  // Clean up

        custom.start();

        // Test with custom configuration
        String appUrl = String.format("http://%s:%d",
            custom.getServiceHost("app", 8080),
            custom.getServicePort("app", 8080));

        // Run tests
        // ...

        custom.stop();
    }
}

Best Practices

Compose File Organization

# docker-compose.yml - Base configuration
version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080"
    depends_on:
      - db
      - redis
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
  redis:
    image: redis:7.0

# docker-compose.test.yml - Test overrides
version: '3.8'
services:
  app:
    environment:
      - SPRING_PROFILES_ACTIVE=test
      - LOG_LEVEL=DEBUG
    build:
      args:
        - ENV=test
  db:
    environment:
      - POSTGRES_PASSWORD=test

Wait Strategy Selection

// Choose appropriate wait strategies for each service type

// Databases - wait for port
.withExposedService("postgres", 5432,
    Wait.forListeningPort())

// HTTP services - wait for health endpoint
.withExposedService("api", 8080,
    Wait.forHttp("/health").forStatusCode(200))

// Services with specific startup messages
.withExposedService("kafka", 9092,
    Wait.forLogMessage(".*started.*", 1))

// Services with healthcheck
.withExposedService("app", 8080,
    Wait.forHealthcheck())

Resource Cleanup

// Always use try-with-resources
try (DockerComposeContainer<?> env = new DockerComposeContainer<>(...)) {
    env.start();
    // Use environment
} // Automatic cleanup

// Or manually manage lifecycle
DockerComposeContainer<?> env = new DockerComposeContainer<>(...);
try {
    env.start();
    // Use environment
} finally {
    env.stop();
}

// Configure image removal
.withRemoveImages(RemoveImages.LOCAL)  // Remove built images

Environment Isolation

// Use unique environment variables per test
DockerComposeContainer<?> test1 = new DockerComposeContainer<>(...)
    .withEnv("TEST_ID", "test1")
    .withEnv("DB_NAME", "test1_db");

DockerComposeContainer<?> test2 = new DockerComposeContainer<>(...)
    .withEnv("TEST_ID", "test2")
    .withEnv("DB_NAME", "test2_db");

// Ensures tests don't interfere with each other