DocsAWS 101BlogServices

Testcontainers (Java)

Official Testcontainers module for MiniStack. One dependency, one MiniStackContainer, zero boilerplate — the container is pulled, started, health-checked, and handed to your test with an endpoint URL, credentials, and region.

Java 11+ org.ministack:testcontainers-ministack Testcontainers 1.21.0

Install

Maven:

<dependency>
  <groupId>org.ministack</groupId>
  <artifactId>testcontainers-ministack</artifactId>
  <version>0.1.4</version>
  <scope>test</scope>
</dependency>

Gradle:

testImplementation 'org.ministack:testcontainers-ministack:0.1.4'

The module pulls in Testcontainers core transitively; you don't need to add it separately. You bring your own AWS SDK (v2 recommended).

Quick start

import org.ministack.testcontainers.MiniStackContainer;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;

try (MiniStackContainer ms = new MiniStackContainer()) {
    ms.start();

    S3Client s3 = S3Client.builder()
        .endpointOverride(URI.create(ms.getEndpoint()))
        .region(Region.of(ms.getRegion()))
        .credentialsProvider(StaticCredentialsProvider.create(
            AwsBasicCredentials.create(ms.getAccessKey(), ms.getSecretKey())))
        .forcePathStyle(true)
        .build();

    s3.createBucket(CreateBucketRequest.builder().bucket("hello").build());
}

API reference

MemberReturnsPurpose
new MiniStackContainer()MiniStackContainerUses ministackorg/ministack:latest.
new MiniStackContainer(String tag)MiniStackContainerPins a specific release, e.g. "1.3.14".
new MiniStackContainer(DockerImageName)MiniStackContainerCustom image — useful behind a private registry mirror.
getEndpoint()Stringe.g. http://127.0.0.1:32789 — feed to every SDK client's endpointOverride.
getPort()intHost-side mapped port for container port 4566.
getAccessKey()StringDefault "test". Override with .withEnv("AWS_ACCESS_KEY_ID", "111111111111") to scope state to a specific account.
getSecretKey()StringDefault "test".
getRegion()StringDefault "us-east-1".
withRealInfrastructure()MiniStackContainerMounts the Docker socket so RDS/ElastiCache/ECS/EKS can spin up real sidecar containers.
stop()voidStops the container and reaps every sidecar tagged with ministack (works under Docker and Podman).

The class extends GenericContainer, so everything Testcontainers provides is available: withReuse(true), withEnv(key, val), withLabel(k, v), withCreateContainerCmdModifier(…), and the full wait-strategy API. The built-in wait strategy is an HTTP check on /_ministack/health.

SDK wiring

AWS SDK v2

S3Client s3 = S3Client.builder()
    .endpointOverride(URI.create(ms.getEndpoint()))
    .region(Region.of(ms.getRegion()))
    .credentialsProvider(StaticCredentialsProvider.create(
        AwsBasicCredentials.create(ms.getAccessKey(), ms.getSecretKey())))
    .forcePathStyle(true)       // required for S3
    .build();

AWS SDK v1

AmazonS3 s3 = AmazonS3ClientBuilder.standard()
    .withEndpointConfiguration(new EndpointConfiguration(ms.getEndpoint(), ms.getRegion()))
    .withCredentials(new AWSStaticCredentialsProvider(
        new BasicAWSCredentials(ms.getAccessKey(), ms.getSecretKey())))
    .withPathStyleAccessEnabled(true)
    .build();

Global services (IAM, Route53)

Use Region.AWS_GLOBAL for services that AWS treats as region-less. MiniStack accepts either AWS_GLOBAL or a specific region for these, but AWS_GLOBAL matches AWS SDK expectations:

IamClient iam = IamClient.builder()
    .endpointOverride(URI.create(ms.getEndpoint()))
    .region(Region.AWS_GLOBAL)
    .credentialsProvider(…)
    .build();

Real infrastructure

withRealInfrastructure() opts into sidecar mode: RDS CreateDBInstance, ElastiCache CreateCacheCluster, ECS RunTask, and EKS CreateCluster spin up genuine Docker containers on the host daemon. MiniStack talks to them over the host Docker socket, which the module mounts for you.

try (MiniStackContainer ms = new MiniStackContainer().withRealInfrastructure()) {
    ms.start();

    RdsClient rds = …;
    rds.createDBInstance(CreateDBInstanceRequest.builder()
        .dbInstanceIdentifier("mydb")
        .engine("postgres")
        .engineVersion("15")
        .masterUsername("admin").masterUserPassword("pw123456")
        .dbInstanceClass("db.t3.micro")
        .allocatedStorage(20)
        .build());

    // Await the real postgres container
    Awaitility.await().atMost(20, SECONDS)
        .until(() -> rds.describeDBInstances(…).dbInstances().get(0)
                        .dbInstanceStatus().equals("available"));

    // Connect with standard JDBC
    try (Connection conn = DriverManager.getConnection(
            "jdbc:postgresql://localhost:" + port + "/postgres", "admin", "pw123456")) {
        // …
    }

    // Cleanup — required until automatic teardown lands
    rds.deleteDBInstance(DeleteDBInstanceRequest.builder()
        .dbInstanceIdentifier("mydb")
        .skipFinalSnapshot(true)
        .build());
}
You own the lifecycle of child resources. Call deleteDBInstance / deleteCacheCluster explicitly before closing the container — the module's stop() reaps leaked containers as a safety net, but explicit cleanup is friendlier to shared dev machines.

Spring Boot

Use @ServiceConnection (Spring Boot 3.1+) to auto-configure an AwsAsyncClient-style bean pointing at the container:

@TestConfiguration(proxyBeanMethods = false)
class MiniStackTestConfig {
    @Bean
    @ServiceConnection
    MiniStackContainer miniStackContainer() {
        return new MiniStackContainer("1.3.14");
    }
}

Private registry

Air-gapped / registry-proxy environments: set Testcontainers' hub.image.name.prefix in ~/.testcontainers.properties or via TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX. The module forwards the prefix into MINISTACK_IMAGE_PREFIX inside the container, so every nested image (postgres, mysql, redis, k3s, Lambda runtimes) is also pulled through your mirror.

# ~/.testcontainers.properties
hub.image.name.prefix=proxy.corp.net/

Reuse across tests

Enable Testcontainers reuse so the container survives between suite runs (dramatic speed-up during local iteration):

// ~/.testcontainers.properties
testcontainers.reuse.enable=true

// Then in code:
new MiniStackContainer().withReuse(true);

When reuse is on, remember to POST /_ministack/reset at the start of your test suite to start from a clean state — otherwise prior runs leak into the new one.

// Before each test class
HttpClient.newHttpClient().send(
    HttpRequest.newBuilder(URI.create(ms.getEndpoint() + "/_ministack/reset"))
        .POST(HttpRequest.BodyPublishers.noBody()).build(),
    HttpResponse.BodyHandlers.discarding());

Gotchas

  • Version pinning. latest changes under you. Pin to the MiniStack release your CI validated against (the tag string matches a published image).
  • Docker socket on Podman. withRealInfrastructure() assumes a Docker-compatible socket. Podman works if you expose the user-scoped socket and set DOCKER_HOST before running tests.
  • Port mapping is random. Always read getEndpoint() / getPort() — never hardcode 4566 on the host.
  • Force path-style for S3. Without .forcePathStyle(true) the SDK virtual-hosts as bucket.localhost which Docker can't resolve.
Source: github.com/ministackorg/testcontainers-ministack — issues and PRs welcome.