diff --git a/docs/modules/ceph.md b/docs/modules/ceph.md
new file mode 100644
index 00000000000..0300339cfb5
--- /dev/null
+++ b/docs/modules/ceph.md
@@ -0,0 +1,46 @@
+# Ceph Module
+
+!!! note
+ This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy.
+
+Testcontainers module for [Ceph](https://ceph.io/).
+
+## Ceph vs Localstack for simulating S3
+
+One possible usage for the Ceph module is to take advantage of its S3 compatibility.
+
+For simulating S3 you may wish to also consider the [Localstack](./localstack.md) module.
+Ceph and Localstack provide good compatibility with the S3 API, and we encourage you to evaluate both.
+
+Localstack is likely to be a better choice if you require simulation of other AWS services along with S3.
+
+## Usage example
+
+Creating a Ceph container:
+
+
+[Creating a Ceph container](../../modules/ceph/src/test/java/org/testcontainers/containers/CephContainerTest.java) inside_block:creating_container
+
+
+Configuring an S3 Client to use Ceph:
+
+
+[Configuring an S3 Client to use Ceph](../../modules/ceph/src/test/java/org/testcontainers/containers/CephContainerTest.java) inside_block:setting_up_s3_client
+
+
+## Adding this module to your project dependencies
+
+Add the following dependency to your `pom.xml`/`build.gradle` file:
+
+```groovy tab='Gradle'
+testCompile "org.testcontainers:ceph:{{latest_version}}"
+```
+
+```xml tab='Maven'
+
+ org.testcontainers
+ ceph
+ {{latest_version}}
+ test
+
+```
diff --git a/docs/modules/localstack.md b/docs/modules/localstack.md
index 4ff46233b05..b4cd3c5e7dd 100644
--- a/docs/modules/localstack.md
+++ b/docs/modules/localstack.md
@@ -2,6 +2,15 @@
Testcontainers module for the Atlassian's [LocalStack](https://github.com/localstack/localstack), 'a fully functional local AWS cloud stack'.
+## Ceph vs Localstack for simulating S3
+
+One possible usage for the Localstack module is to take advantage of its S3 compatibility.
+
+For simulating S3 you may wish to also consider the [Ceph](./ceph.md) module.
+Ceph and Localstack provide good compatibility with the S3 API, and we encourage you to evaluate both.
+
+Localstack is likely to be a better choice if you require simulation of other AWS services along with S3.
+
## Usage example
Running LocalStack as a stand-in for AWS S3 during a test:
diff --git a/mkdocs.yml b/mkdocs.yml
index e1b21d80811..5c113bd4fde 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -58,6 +58,7 @@ nav:
- modules/databases/orientdb.md
- modules/databases/postgres.md
- modules/databases/presto.md
+ - modules/ceph.md
- modules/docker_compose.md
- modules/elasticsearch.md
- modules/kafka.md
diff --git a/modules/ceph/build.gradle b/modules/ceph/build.gradle
new file mode 100644
index 00000000000..75a6a98c444
--- /dev/null
+++ b/modules/ceph/build.gradle
@@ -0,0 +1,7 @@
+description = "Testcontainers :: Ceph"
+
+dependencies {
+ compile project(':testcontainers')
+
+ provided 'com.amazonaws:aws-java-sdk-s3:1.11.495'
+}
diff --git a/modules/ceph/src/main/java/org/testcontainers/containers/CephContainer.java b/modules/ceph/src/main/java/org/testcontainers/containers/CephContainer.java
new file mode 100644
index 00000000000..013350ebdcc
--- /dev/null
+++ b/modules/ceph/src/main/java/org/testcontainers/containers/CephContainer.java
@@ -0,0 +1,117 @@
+package org.testcontainers.containers;
+
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.client.builder.AwsClientBuilder;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
+
+import java.time.Duration;
+
+@Slf4j
+@Getter
+public class CephContainer extends GenericContainer {
+
+ public static final String IMAGE = "ceph/daemon";
+ public static final String DEFAULT_TAG = "v3.2.13-stable-3.2-mimic-centos-7";
+
+ public static final int S3_PORT = 8080;
+ public static final int REST_API_PORT = 5000;
+
+ private String awsAccessKey = "ceph";
+
+ private String awsSecretKey = "ceph";
+
+ private String bucketName = "CEPH";
+
+ private String rgwName = "localhost";
+
+ private String demoUid = "ceph";
+
+ private NetworkAutoDetectMode networkAutoDetectMode = NetworkAutoDetectMode.IPV4_ONLY;
+
+ public CephContainer() {
+ super(IMAGE + ":" + DEFAULT_TAG);
+ }
+
+ public CephContainer(String dockerImageName) {
+ super(dockerImageName);
+ }
+
+ @Override
+ protected void configure() {
+ withEnv("RGW_NAME", rgwName);
+ withEnv("NETWORK_AUTO_DETECT", networkAutoDetectMode.value);
+ withEnv("CEPH_DAEMON", "demo");
+ withEnv("CEPH_DEMO_UID", demoUid);
+ withEnv("CEPH_DEMO_ACCESS_KEY", awsAccessKey);
+ withEnv("CEPH_DEMO_SECRET_KEY", awsSecretKey);
+ withEnv("CEPH_DEMO_BUCKET", bucketName);
+ withExposedPorts(REST_API_PORT, S3_PORT);
+ waitingFor(
+ new HttpWaitStrategy()
+ .forPath("/")
+ .forPort(REST_API_PORT)
+ .forStatusCode(200)
+ .withStartupTimeout(Duration.ofMinutes(5))
+ );
+ withLogConsumer(new Slf4jLogConsumer(log));
+ }
+
+ public AWSCredentialsProvider getAWSCredentialsProvider() {
+ return new AWSStaticCredentialsProvider(
+ new BasicAWSCredentials(awsAccessKey, awsSecretKey)
+ );
+ }
+
+ public AwsClientBuilder.EndpointConfiguration getAWSEndpointConfiguration() {
+ return new AwsClientBuilder.EndpointConfiguration(
+ getContainerIpAddress() + ":" + getMappedPort(S3_PORT),
+ "us-east-1"
+ );
+ }
+
+ public CephContainer withAwsAccessKey(String awsAccessKey) {
+ this.awsAccessKey = awsAccessKey;
+ return self();
+ }
+
+ public CephContainer withAwsSecretKey(String awsSecretKey) {
+ this.awsSecretKey = awsSecretKey;
+ return self();
+ }
+
+ public CephContainer withBucketName(String bucketName) {
+ //because s3cmd transforming bucket name to uppercase
+ this.bucketName = bucketName.toUpperCase();
+ return self();
+ }
+
+ public CephContainer withRgwName(String rgwName) {
+ this.rgwName = rgwName;
+ return self();
+ }
+
+ public CephContainer withDemoUid(String demoUid) {
+ this.demoUid = demoUid;
+ return self();
+ }
+
+ public CephContainer withNetworkAutoDetectMode(NetworkAutoDetectMode networkAutoDetectMode) {
+ this.networkAutoDetectMode = networkAutoDetectMode;
+ return self();
+ }
+
+ @AllArgsConstructor
+ public enum NetworkAutoDetectMode {
+ IPV6_OR_IPV4("1"),
+ IPV4_ONLY("4"),
+ IPV6_ONLY("6");
+
+ private final String value;
+ }
+}
diff --git a/modules/ceph/src/test/java/org/testcontainers/containers/CephContainerTest.java b/modules/ceph/src/test/java/org/testcontainers/containers/CephContainerTest.java
new file mode 100644
index 00000000000..620250bb3be
--- /dev/null
+++ b/modules/ceph/src/test/java/org/testcontainers/containers/CephContainerTest.java
@@ -0,0 +1,112 @@
+package org.testcontainers.containers;
+
+import com.amazonaws.ClientConfiguration;
+import com.amazonaws.Protocol;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.AmazonS3ClientBuilder;
+import com.amazonaws.services.s3.model.Bucket;
+import com.amazonaws.services.s3.model.S3Object;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class CephContainerTest {
+ @Test
+ public void testS3Bucket() {
+ try (
+ // creating_container {
+ CephContainer cephContainer = new CephContainer()
+ .withAwsAccessKey("test")
+ .withAwsSecretKey("test")
+ .withBucketName("test");
+ // }
+ ) {
+ cephContainer.start();
+
+ final AmazonS3 amazonS3 = buildS3Client(cephContainer);
+
+ String bucketName = "test2";
+
+ //check current bucket
+ assertTrue(amazonS3.doesBucketExistV2(cephContainer.getBucketName()));
+
+ //create another bucket
+ amazonS3.createBucket(bucketName);
+ assertTrue(amazonS3.doesBucketExistV2(bucketName));
+ List buckets = amazonS3.listBuckets();
+ assertEquals(2, buckets.size());
+ assertTrue(buckets.stream().anyMatch(
+ bucket -> bucket.getName().equals(bucketName)
+ ));
+
+ //remove bucket
+ amazonS3.deleteBucket(bucketName);
+ assertFalse(amazonS3.doesBucketExistV2(bucketName));
+ buckets = amazonS3.listBuckets();
+ assertEquals(1, buckets.size());
+ assertFalse(buckets.stream().anyMatch(
+ bucket -> bucket.getName().equals(bucketName)
+ ));
+ }
+ }
+
+ @Test
+ public void testS3Object() throws IOException {
+ try (
+ CephContainer cephContainer = new CephContainer()
+ .withAwsAccessKey("test")
+ .withAwsSecretKey("test")
+ .withBucketName("test");
+ ) {
+ cephContainer.start();
+
+ final AmazonS3 amazonS3 = buildS3Client(cephContainer);
+ String objectId = "test";
+ String testData = "This is test data";
+
+ //put object
+ amazonS3.putObject(cephContainer.getBucketName(), objectId, testData);
+ assertEquals(1, amazonS3.listObjects(cephContainer.getBucketName()).getObjectSummaries().size());
+ S3Object object = amazonS3.getObject(cephContainer.getBucketName(), objectId);
+ assertEquals(testData, IOUtils.toString(object.getObjectContent(), StandardCharsets.UTF_8));
+
+ //generate presigned url and download file
+ URL url = amazonS3.generatePresignedUrl(
+ cephContainer.getBucketName(),
+ objectId,
+ Date.from(Instant.now().plusSeconds(Duration.ofMinutes(5).toMillis())));
+
+ try (InputStream inputStream = url.openStream()) {
+ assertEquals(testData, IOUtils.toString(inputStream, StandardCharsets.UTF_8));
+ }
+ }
+ }
+
+ private AmazonS3 buildS3Client(final CephContainer cephContainer) {
+ // setting_up_s3_client {
+ final AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
+ .withCredentials(cephContainer.getAWSCredentialsProvider())
+ .withEndpointConfiguration(cephContainer.getAWSEndpointConfiguration())
+ .withPathStyleAccessEnabled(true)
+ .withClientConfiguration(
+ new ClientConfiguration()
+ .withProtocol(Protocol.HTTP)
+ .withSignerOverride("S3SignerType")
+ )
+ .build();
+ // }
+ return amazonS3;
+ }
+}