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

startup-checks.mddocs/

Startup Check Strategies

Strategies for determining if a container has started correctly and is ready for use. Startup check strategies operate independently of wait strategies, providing an alternative or complementary mechanism for validating container startup success. While wait strategies focus on determining when a container is ready for testing, startup check strategies verify that the container started without errors.

Capabilities

StartupCheckStrategy Abstract Class

Base class for all startup check strategies, providing the framework for checking container startup status.

/**
 * Strategy for determining whether a container has started correctly.
 * Provides a mechanism to verify container startup success or failure
 * independent of wait strategies.
 */
public abstract class StartupCheckStrategy {

    /**
     * Enum representing possible startup states.
     */
    public enum StartupStatus {
        /** Startup status not yet determined */
        NOT_YET_KNOWN,
        /** Container started successfully */
        SUCCESSFUL,
        /** Container failed to start */
        FAILED
    }

    /**
     * Check the current startup state without blocking.
     * Must be implemented by subclasses.
     *
     * @param dockerClient the Docker client for querying container state
     * @param containerId the container ID to check
     * @return the current startup status
     */
    public abstract StartupStatus checkStartupState(DockerClient dockerClient, String containerId);

    /**
     * Wait until the container startup is successful or failed.
     * Blocks until a definitive status is determined within the configured timeout.
     *
     * @param dockerClient the Docker client
     * @param containerId the container ID
     * @return true if startup successful, false if failed
     */
    public boolean waitUntilStartupSuccessful(DockerClient dockerClient, String containerId);

    /**
     * Set the timeout for startup checks.
     * Default timeout is 30 seconds (GenericContainer.CONTAINER_RUNNING_TIMEOUT_SEC).
     *
     * @param timeout the timeout duration
     * @return this strategy with generic type for proper subclass chaining
     */
    public <SELF extends StartupCheckStrategy> SELF withTimeout(Duration timeout);

    /**
     * Get the current Docker container state information.
     * Protected utility method for subclasses.
     *
     * @param dockerClient the Docker client
     * @param containerId the container ID
     * @return the container state information
     */
    protected InspectContainerResponse.ContainerState getCurrentState(DockerClient dockerClient, String containerId);
}

Key Characteristics:

  • Abstract base class requiring subclasses to implement checkStartupState()
  • Uses rate-limited Docker client queries to avoid overwhelming the API (1 request per second)
  • Provides retry mechanism with exponential backoff via Unreliables.retryUntilTrue()
  • Configurable timeout with method chaining support
  • Operates at the container state level (running/stopped/exit codes)

IsRunningStartupCheckStrategy

The simplest and most common startup check strategy. Verifies that the container is running and hasn't exited with a failure code.

/**
 * Simplest possible startup check strategy.
 * Verifies the container has reached the running state and has not exited.
 * This is the default strategy for most containers.
 *
 * Success conditions:
 * - Container is currently running, OR
 * - Container has exited with exit code 0 (success)
 *
 * Failure conditions:
 * - Container has exited with non-zero exit code
 */
public class IsRunningStartupCheckStrategy extends StartupCheckStrategy {

    /**
     * Check if the container is running or successfully exited.
     *
     * @param dockerClient the Docker client
     * @param containerId the container ID
     * @return SUCCESSFUL if running or exited with code 0,
     *         FAILED if exited with non-zero code,
     *         NOT_YET_KNOWN if still starting
     */
    @Override
    public StartupStatus checkStartupState(DockerClient dockerClient, String containerId);
}

When to Use:

  • Standard long-running services (databases, web servers, message queues)
  • Any container that should remain running after startup
  • Default choice for most applications

Behavior:

  • Checks the container's running status
  • Accepts containers that are running as successful
  • Accepts containers that exited with code 0 as successful
  • Rejects containers that exited with non-zero codes
  • Polls with timeout to wait for definitive state

Limitations:

  • Port being open doesn't mean the application is ready
  • Container running doesn't mean it's actually serving requests
  • Pair with WaitStrategy for application-level readiness checks

Usage Example:

GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:7.0"))
    .withExposedPorts(6379)
    .withStartupCheckStrategy(new IsRunningStartupCheckStrategy()
        .withTimeout(Duration.ofSeconds(60)));

redis.start();
// Container is running and startup check passed

OneShotStartupCheckStrategy

For containers that run a single command and exit. Considers the container successfully started when it has exited with code 0.

/**
 * Startup check strategy for one-shot/batch containers.
 * Designed for containers that run briefly and exit of their own accord.
 * Success is when the container has stopped with exit code 0.
 *
 * Success conditions:
 * - Container has exited with exit code 0
 *
 * Failure conditions:
 * - Container has exited with non-zero exit code
 *
 * Pending conditions:
 * - Container is still running
 */
public class OneShotStartupCheckStrategy extends StartupCheckStrategy {

    /**
     * Check if the one-shot container has completed successfully.
     *
     * @param dockerClient the Docker client
     * @param containerId the container ID
     * @return SUCCESSFUL when container exits with code 0,
     *         FAILED when container exits with non-zero code,
     *         NOT_YET_KNOWN while container is still running
     */
    @Override
    public StartupStatus checkStartupState(DockerClient dockerClient, String containerId);
}

When to Use:

  • Batch jobs or data processing containers
  • Initialization scripts or database setup containers
  • Container that must complete and exit to be considered "started"
  • Job runners (ETL processes, report generation, backups)

Behavior:

  • Waits for container to stop/exit
  • Expects exit code 0 for success
  • Any non-zero exit code is treated as failure
  • Polls container state until definitive outcome

Key Differences from IsRunningStartupCheckStrategy:

  • Expects container to exit (opposite of IsRunning)
  • Failure condition if container remains running
  • Exit code 0 is the success marker instead of running state

Usage Example:

// Data import container that processes files and exits
GenericContainer<?> dataImporter = new GenericContainer<>(
        DockerImageName.parse("myapp/data-importer:latest"))
    .withCommand("/bin/sh", "-c", "python /scripts/import_data.py")
    .withCopyToContainer(
        Transferable.of("import_data.py"),
        "/scripts/import_data.py"
    )
    .withStartupCheckStrategy(new OneShotStartupCheckStrategy()
        .withTimeout(Duration.ofMinutes(5)));

dataImporter.start();
// Wait for exit
String logs = dataImporter.getLogs();
if (dataImporter.getContainerInfo().getState().getExitCode() == 0) {
    System.out.println("Data import successful");
}

IndefiniteWaitOneShotStartupCheckStrategy

Extended one-shot strategy that waits indefinitely for the container to complete. Use when the execution time is unpredictable or can be very long.

/**
 * One-shot startup check strategy with indefinite wait.
 * Variant of OneShotStartupCheckStrategy that does not impose a timeout.
 * Intended for situations where long-running tasks form part of container startup.
 *
 * Behavior:
 * - Waits indefinitely for container to exit
 * - Expects exit code 0 for success
 * - No timeout constraints (use with care in CI/CD)
 *
 * Assumes the container will stop of its own accord,
 * either with success or failure exit code.
 */
public class IndefiniteWaitOneShotStartupCheckStrategy extends OneShotStartupCheckStrategy {

    /**
     * Wait indefinitely for the one-shot container to complete.
     * Polls the container state every 100ms without timeout.
     *
     * @param dockerClient the Docker client
     * @param containerId the container ID
     * @return true if container exits with code 0, false if non-zero exit code
     */
    @Override
    public boolean waitUntilStartupSuccessful(DockerClient dockerClient, String containerId);
}

When to Use:

  • Long-running batch jobs with unpredictable completion time
  • Database migrations that may take extended time
  • Machine learning training or data processing jobs
  • CI/CD pipelines where timeout is handled elsewhere
  • Load testing or stress testing scenarios

Behavior:

  • Waits indefinitely without timeout constraint
  • Polls container state every 100ms
  • Only completes when container exits (either successfully or with failure)
  • Non-interruptible polling (uses Uninterruptibles.sleepUninterruptibly)

Warnings:

  • Can hang indefinitely if container never exits
  • Requires careful resource management
  • Test locally before using in CI/CD pipelines
  • Consider wrapping in test timeout framework

Usage Example:

// Long-running machine learning training job
GenericContainer<?> mlTrainer = new GenericContainer<>(
        DockerImageName.parse("myapp/ml-trainer:latest"))
    .withCommand("/bin/sh", "-c", "python /scripts/train_model.py --epochs 100")
    .withCopyToContainer(
        Transferable.of("train_model.py"),
        "/scripts/train_model.py"
    )
    .withStartupCheckStrategy(new IndefiniteWaitOneShotStartupCheckStrategy());

mlTrainer.start();
// Will wait indefinitely for training to complete
String trainingOutput = mlTrainer.getLogs();

MinimumDurationRunningStartupCheckStrategy

Ensures the container stays running for a minimum duration before being considered successfully started. Useful for detecting containers that start but crash immediately.

/**
 * Startup check strategy that ensures the container runs for a minimum duration.
 * Useful for detecting containers that start successfully but then fail.
 * Prevents false positives where a container briefly starts then crashes.
 *
 * Success conditions:
 * - Container is running AND
 * - Container has been running for at least the specified minimum duration
 *
 * Failure conditions:
 * - Container has stopped with non-zero exit code
 * - Container exits before minimum duration is reached
 *
 * Pending conditions:
 * - Container is running but hasn't reached minimum duration yet
 */
public class MinimumDurationRunningStartupCheckStrategy extends StartupCheckStrategy {

    /**
     * Constructor setting the minimum duration the container must run.
     *
     * @param minimumRunningDuration the minimum duration (must not be null)
     * @throws NullPointerException if minimumRunningDuration is null
     */
    public MinimumDurationRunningStartupCheckStrategy(
        @NotNull Duration minimumRunningDuration
    );

    /**
     * Check if the container has been running for the minimum duration.
     *
     * @param dockerClient the Docker client
     * @param containerId the container ID
     * @return SUCCESSFUL if running for minimum duration,
     *         FAILED if container has stopped,
     *         NOT_YET_KNOWN if running but minimum duration not yet reached
     */
    @Override
    public StartupStatus checkStartupState(DockerClient dockerClient, String containerId);
}

When to Use:

  • Containers that crash immediately if there's a configuration problem
  • Services that need to prove stability before tests begin
  • Applications that have delayed initialization errors
  • Detecting startup race conditions or resource exhaustion issues

Behavior:

  • Requires container to be running for specified duration
  • Records container start time on first check
  • Ensures stability by preventing test against unstable startup
  • Useful for catching immediate failure scenarios

Key Advantages:

  • Catches crash-immediately bugs that quick-start strategies miss
  • Validates container stability, not just startup
  • Configurable minimum duration (10 seconds, 30 seconds, etc.)

Typical Duration Values:

// Brief stability check (good for fast-starting apps)
Duration.ofSeconds(5)

// Standard stability check
Duration.ofSeconds(10)

// Conservative stability check (for slower apps)
Duration.ofSeconds(30)

// Very conservative check
Duration.ofSeconds(60)

Usage Example:

// Microservice that should prove it can run stably
GenericContainer<?> microservice = new GenericContainer<>(
        DockerImageName.parse("mycompany/api-service:latest"))
    .withExposedPorts(8080)
    .withEnv("JAVA_OPTS", "-Xmx256m")
    .withStartupCheckStrategy(
        new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(10))
    );

microservice.start();
// Container must have been running successfully for 10 seconds

// Verify the service is actually responding
String response = HTTP_CLIENT.get(String.format("http://%s:%d/health",
    microservice.getHost(),
    microservice.getMappedPort(8080)));

Startup Checks vs Wait Strategies

Key differences and complementary usage:

AspectStartup CheckWait Strategy
PurposeVerify container started correctlyVerify container is ready for testing
TimingRuns immediately after container.start()Runs after startup check passes
LevelContainer state (running/stopped)Application level (port, HTTP, logs)
TimeoutContainer startup timeout (default 30s)Configurable per strategy
Example"Container is running""Application responds to HTTP requests"
Used forCatching startup failuresEnsuring application readiness

Complementary Usage:

GenericContainer<?> app = new GenericContainer<>(DockerImageName.parse("webapp:latest"))
    .withExposedPorts(8080)
    // Startup check: ensure container stays running for 5 seconds
    .withStartupCheckStrategy(
        new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(5))
    )
    // Wait strategy: ensure application is responding to requests
    .waitingFor(Wait.forHttp("/health")
        .forStatusCode(200)
        .withStartupTimeout(Duration.ofSeconds(60)));

app.start();
// Both checks have passed: container is stable AND application is responding

Integration with GenericContainer

Using startup check strategies in containers:

/**
 * Set the startup check strategy for the container.
 * Determines how the container startup success is verified.
 *
 * @param strategy the startup check strategy to use
 * @return this container for method chaining
 */
public SELF withStartupCheckStrategy(StartupCheckStrategy strategy);

/**
 * Set the minimum running duration for container startup.
 * Convenience method that creates a MinimumDurationRunningStartupCheckStrategy.
 *
 * @param minimumRunningDuration the minimum duration the container must run
 * @return this container for method chaining
 */
public SELF withMinimumRunningDuration(Duration minimumRunningDuration);

/**
 * Set the number of startup attempts before giving up.
 * Container will be restarted this many times if startup fails.
 *
 * @param attempts the number of startup attempts
 * @return this container for method chaining
 */
public SELF withStartupAttempts(int attempts);

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

Usage Examples:

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.IsRunningStartupCheckStrategy;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
import org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;

// Using IsRunningStartupCheckStrategy
GenericContainer<?> database = new GenericContainer<>(DockerImageName.parse("postgres:15"))
    .withExposedPorts(5432)
    .withEnv("POSTGRES_PASSWORD", "secret")
    .withStartupCheckStrategy(new IsRunningStartupCheckStrategy()
        .withTimeout(Duration.ofSeconds(60)));

// Using OneShotStartupCheckStrategy
GenericContainer<?> batchJob = new GenericContainer<>(DockerImageName.parse("myapp/job:latest"))
    .withCommand("sh", "-c", "python process.py")
    .withStartupCheckStrategy(new OneShotStartupCheckStrategy()
        .withTimeout(Duration.ofMinutes(5)));

// Using MinimumDurationRunningStartupCheckStrategy
GenericContainer<?> stableService = new GenericContainer<>(
        DockerImageName.parse("myapp/service:latest"))
    .withExposedPorts(8080)
    .withStartupCheckStrategy(
        new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(10))
    );

// Using convenience method
GenericContainer<?> app = new GenericContainer<>(DockerImageName.parse("app:latest"))
    .withExposedPorts(8080)
    .withMinimumRunningDuration(Duration.ofSeconds(10));

// Using with startup attempts
GenericContainer<?> flaky = new GenericContainer<>(DockerImageName.parse("flaky-app:latest"))
    .withStartupCheckStrategy(new IsRunningStartupCheckStrategy()
        .withTimeout(Duration.ofSeconds(30)))
    .withStartupAttempts(3);  // Retry up to 3 times if startup fails

flaky.start();

Decision Guide: Choosing the Right Startup Check Strategy

Quick Selection Table

Container TypeStartup CheckRationale
Long-running services (API, database, cache)IsRunningStartupCheckStrategyShould remain running indefinitely
Batch jobs, data import, ETLOneShotStartupCheckStrategyRuns once and exits
Long-running batch job (unpredictable duration)IndefiniteWaitOneShotStartupCheckStrategyNo timeout needed
Service with crash-immediately bugsMinimumDurationRunningStartupCheckStrategyNeeds to prove stability
Most applicationsIsRunningStartupCheckStrategy (default)Safe default choice

Decision Flowchart Logic

Is the container expected to stay running after startup?
├─ YES → IsRunningStartupCheckStrategy
│   └─ Does it crash immediately on config errors?
│       └─ YES → MinimumDurationRunningStartupCheckStrategy (with stability check)
└─ NO → Does it run for a predictable time?
    ├─ YES → OneShotStartupCheckStrategy
    └─ NO → IndefiniteWaitOneShotStartupCheckStrategy

Practical Scenarios

Scenario 1: Standard Web Application

GenericContainer<?> webApp = new GenericContainer<>(
        DockerImageName.parse("mycompany/api:latest"))
    .withExposedPorts(8080)
    .withEnv("ENVIRONMENT", "test")
    .withStartupCheckStrategy(new IsRunningStartupCheckStrategy()
        .withTimeout(Duration.ofSeconds(45)))
    .waitingFor(Wait.forHttp("/health")
        .forStatusCode(200)
        .withStartupTimeout(Duration.ofSeconds(30)));

webApp.start();
// Startup check: container is running
// Wait strategy: HTTP /health endpoint responds with 200

Scenario 2: Database Migration on Startup

GenericContainer<?> appWithMigration = new GenericContainer<>(
        DockerImageName.parse("myapp/migrator:latest"))
    .withExposedPorts(8080)
    .withEnv("DB_HOST", "db.example.com")
    // Allow extra time for database migration
    .withStartupCheckStrategy(new IsRunningStartupCheckStrategy()
        .withTimeout(Duration.ofMinutes(2)))
    .waitingFor(Wait.forLogMessage(".*Migration completed.*", 1)
        .withStartupTimeout(Duration.ofMinutes(2)));

appWithMigration.start();

Scenario 3: Data Processing Job

GenericContainer<?> dataProcessor = new GenericContainer<>(
        DockerImageName.parse("myapp/processor:latest"))
    .withCommand("sh", "-c", "python process_data.py input.csv output.csv")
    .withCopyToContainer(Transferable.of(csvData), "/data/input.csv")
    .withStartupCheckStrategy(new OneShotStartupCheckStrategy()
        .withTimeout(Duration.ofMinutes(10)));

dataProcessor.start();

String output = dataProcessor.getLogs();
String processedData = dataProcessor.copyFileFromContainer("/data/output.csv");

Scenario 4: Crash Detection

GenericContainer<?> service = new GenericContainer<>(
        DockerImageName.parse("myapp/service:latest"))
    .withExposedPorts(8080)
    .withEnv("CONFIGURATION_REQUIRED", "true")  // Will crash if not set
    .withStartupCheckStrategy(
        new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(15))
    );

try {
    service.start();
    // If we reach here, container has been running for 15+ seconds
    // indicating configuration was valid
} catch (ContainerLaunchException e) {
    // Configuration error caused immediate crash
    System.err.println("Service configuration invalid:");
    System.err.println(service.getLogs());
}

Scenario 5: Machine Learning Model Training

GenericContainer<?> trainer = new GenericContainer<>(
        DockerImageName.parse("myapp/ml-trainer:latest"))
    .withCommand("python", "/app/train.py", "--epochs", "100")
    .withEnv("CUDA_VISIBLE_DEVICES", "0")
    .withStartupCheckStrategy(
        new IndefiniteWaitOneShotStartupCheckStrategy()
    );

System.out.println("Starting model training (may take hours)...");
trainer.start();

String trainingLogs = trainer.getLogs();
System.out.println("Training completed:");
System.out.println(trainingLogs);

// Extract trained model
trainer.copyFileFromContainer("/models/trained.pkl", "./trained_model.pkl");

Common Patterns

Pattern 1: Startup Check + Wait Strategy Combination

GenericContainer<?> complexApp = new GenericContainer<>(
        DockerImageName.parse("complex-app:latest"))
    .withExposedPorts(8080, 9090)
    .withStartupCheckStrategy(
        new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(10))
    )
    .waitingFor(new WaitAllStrategy()
        .withStrategy(Wait.forListeningPort())
        .withStrategy(Wait.forHttp("/ready").forStatusCode(200))
        .withStrategy(Wait.forLogMessage(".*Initialization complete.*", 1))
        .withStartupTimeout(Duration.ofSeconds(60)));

complexApp.start();

Pattern 2: Startup Retry on Failure

GenericContainer<?> unreliableService = new GenericContainer<>(
        DockerImageName.parse("unreliable-service:latest"))
    .withStartupCheckStrategy(new IsRunningStartupCheckStrategy()
        .withTimeout(Duration.ofSeconds(30)))
    .withStartupAttempts(3);  // Retry up to 3 times

try {
    unreliableService.start();
} catch (ContainerLaunchException e) {
    System.err.println("Service failed to start after 3 attempts");
}

Pattern 3: Conditional Startup Check Based on Environment

StartupCheckStrategy startupCheck = System.getenv("QUICK_START") != null
    ? new IsRunningStartupCheckStrategy().withTimeout(Duration.ofSeconds(10))
    : new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(30));

GenericContainer<?> conditionalApp = new GenericContainer<>(
        DockerImageName.parse("app:latest"))
    .withExposedPorts(8080)
    .withStartupCheckStrategy(startupCheck);

Troubleshooting

Issue: Container Startup Timeout

Symptoms:

org.testcontainers.containers.ContainerLaunchException:
  Container startup failed

Common Causes:

  1. Timeout too short for application startup

    // Solution: Increase timeout
    .withStartupCheckStrategy(new IsRunningStartupCheckStrategy()
        .withTimeout(Duration.ofSeconds(120)))
  2. Container crashes immediately (with OneShotStartupCheckStrategy)

    // Check logs to diagnose crash
    System.err.println(container.getLogs());
  3. Wrong strategy for container type

    // Problem: Using OneShotStartupCheckStrategy for long-running service
    // Solution: Use IsRunningStartupCheckStrategy instead
    .withStartupCheckStrategy(new IsRunningStartupCheckStrategy())

Issue: False Positive Crashes (Crash-immediately Problem)

Symptoms: Container appears to start but crashes after a few seconds

Solution: Use MinimumDurationRunningStartupCheckStrategy

GenericContainer<?> app = new GenericContainer<>(DockerImageName.parse("app:latest"))
    .withStartupCheckStrategy(
        new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(20))
    );

app.start();
// If we reach here, container has been running stably for 20+ seconds

Issue: OneShotStartupCheckStrategy Timeout on Long Jobs

Symptoms: Data processing job times out before completion

Solution: Use IndefiniteWaitOneShotStartupCheckStrategy

// Problem: Job takes 2 hours, timeout at 5 minutes
.withStartupCheckStrategy(new OneShotStartupCheckStrategy()
    .withTimeout(Duration.ofMinutes(5)))

// Solution: Wait indefinitely
.withStartupCheckStrategy(new IndefiniteWaitOneShotStartupCheckStrategy())

Issue: Debuggable Startup Failures

Best Practice: Capture logs on startup failure

try {
    container.start();
} catch (ContainerLaunchException e) {
    System.err.println("Container startup failed");
    System.err.println("Startup check strategy: " +
        container.getStartupCheckStrategy().getClass().getSimpleName());
    System.err.println("Container logs:");
    System.err.println(container.getLogs());
    System.err.println("Container status:");
    System.err.println(container.getCurrentContainerInfo().getState());
    throw e;
}

Complete Example: Complex Multi-Component System

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.startupcheck.IsRunningStartupCheckStrategy;
import org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;

public class CompleteStartupCheckExample {

    public void testComplexSystem() {
        Network network = Network.newNetwork();

        // Database with startup check
        GenericContainer<?> database = new GenericContainer<>(
                DockerImageName.parse("postgres:15"))
            .withNetwork(network)
            .withNetworkAliases("database")
            .withExposedPorts(5432)
            .withEnv("POSTGRES_PASSWORD", "testpass")
            .withStartupCheckStrategy(
                new IsRunningStartupCheckStrategy()
                    .withTimeout(Duration.ofSeconds(60))
            )
            .waitingFor(Wait.forHealthcheck()
                .withStartupTimeout(Duration.ofSeconds(60)));

        // Cache service with minimum duration check
        GenericContainer<?> cache = new GenericContainer<>(
                DockerImageName.parse("redis:7.0"))
            .withNetwork(network)
            .withNetworkAliases("cache")
            .withExposedPorts(6379)
            .withStartupCheckStrategy(
                new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(5))
            )
            .waitingFor(Wait.forListeningPort());

        // Main application with comprehensive checks
        WaitAllStrategy appWaitStrategy = new WaitAllStrategy()
            .withStrategy(Wait.forListeningPort())
            .withStrategy(Wait.forHttp("/health")
                .forStatusCode(200)
                .forResponsePredicate(r -> r.contains("\"status\":\"UP\"")))
            .withStartupTimeout(Duration.ofSeconds(60));

        GenericContainer<?> app = new GenericContainer<>(
                DockerImageName.parse("myapp/service:latest"))
            .withNetwork(network)
            .withExposedPorts(8080)
            .withEnv("DB_HOST", "database")
            .withEnv("CACHE_HOST", "cache")
            .dependsOn(database, cache)
            .withStartupCheckStrategy(
                new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(10))
            )
            .waitingFor(appWaitStrategy);

        // Start all containers
        try {
            database.start();
            System.out.println("Database started successfully");

            cache.start();
            System.out.println("Cache started successfully");

            app.start();
            System.out.println("Application started successfully");

            // All checks have passed - system is ready
            String appUrl = String.format("http://%s:%d",
                app.getHost(),
                app.getMappedPort(8080));
            System.out.println("System ready at: " + appUrl);

            // Run tests...

        } catch (Exception e) {
            System.err.println("System startup failed:");
            System.err.println("Database logs: " + database.getLogs());
            System.err.println("Cache logs: " + cache.getLogs());
            System.err.println("App logs: " + app.getLogs());
            throw new RuntimeException("Failed to start test system", e);
        }
    }
}

Performance Considerations

Startup Check Performance

StrategyOverheadBest For
IsRunningStartupCheckStrategyMinimal (~100ms)Fast-starting services
OneShotStartupCheckStrategyVariable (depends on job)Batch operations
IndefiniteWaitOneShotStartupCheckStrategyVariable (no timeout)Long-running jobs
MinimumDurationRunningStartupCheckStrategyDepends on duration (adds ~duration overhead)Stability-critical services

Optimization Tips

  1. Use IsRunningStartupCheckStrategy as default

    • Fastest startup check
    • Suitable for most services
  2. Set appropriate timeouts

    // Too long - wastes time
    .withTimeout(Duration.ofMinutes(5))
    
    // Appropriate for typical service
    .withTimeout(Duration.ofSeconds(30))
  3. Combine with appropriate wait strategy

    // Startup check verifies container stability
    .withStartupCheckStrategy(new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(5)))
    // Wait strategy verifies application readiness
    .waitingFor(Wait.forHttp("/ready").forStatusCode(200))
  4. Consider container reuse in development

    container.withReuse(true);  // Requires testcontainers.reuse.enable=true

Related Documentation

  • Wait Strategies (wait-strategies.md) - Application-level readiness checking
  • Container Lifecycle (container-lifecycle.md) - Container configuration and lifecycle management
  • Network Configuration (network-configuration.md) - Multi-container networking