diff --git a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleFreeContainer.java b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleFreeContainer.java
new file mode 100644
index 00000000000..9ed6f6d38f7
--- /dev/null
+++ b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleFreeContainer.java
@@ -0,0 +1,221 @@
+package org.testcontainers.containers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.testcontainers.containers.JdbcDatabaseContainer;
+import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
+import org.testcontainers.utility.DockerImageName;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+/**
+ * Testcontainers implementation for Oracle.
+ *
+ * Supported image: {@code container-registry.oracle.com/database/free}
+ *
+ * Exposed ports: 1521
+ *
+ * MIT-licensesd by Bernd Eckenfels, based on OracleContainer from oracle-xe.
+ */
+public class OracleFreeContainer extends JdbcDatabaseContainer {
+
+ public static final String NAME = "oracle";
+
+ private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("container-registry.oracle.com/database/free");
+
+ // https://container-registry.oracle.com/ords/f?p=113:4:16246212162327:::RP,4:P4_REPOSITORY,AI_REPOSITORY,P4_REPOSITORY_NAME,AI_REPOSITORY_NAME:1863,1863,Oracle%20Database%20Free,Oracle%20Database%20Free&cs=3gQONqcVhKMn1mUNRMJjdHgofFIzDG97WTyRx3tHBfkK8axlWmSmJRNF_YnrAizYZZj5kyAnyS9O_nudXM3Xw6w
+ static final String DEFAULT_TAG = "23.3.0.0";
+
+ static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();
+
+ static final int ORACLE_PORT = 1521;
+
+ private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 240;
+
+ private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 120;
+
+ // Container defaults
+ static final String DEFAULT_DATABASE_NAME = "freepdb1";
+
+ static final String DEFAULT_SID = "free";
+
+ static final String DEFAULT_SYSTEM_USER = "system";
+
+ static final String DEFAULT_SYS_USER = "sys";
+
+ // Test container defaults
+ static final String APP_USER = "test";
+
+ static final String APP_USER_PASSWORD = "test";
+
+ // Restricted user and database names
+ private static final List ORACLE_SYSTEM_USERS = Arrays.asList(DEFAULT_SYSTEM_USER, DEFAULT_SYS_USER);
+
+ private String databaseName = DEFAULT_DATABASE_NAME;
+
+ private String username = APP_USER;
+
+ private String password = APP_USER_PASSWORD;
+
+ private boolean usingSid = false;
+
+ /**
+ * @deprecated use {@link #OracleContainer(DockerImageName)} instead
+ */
+ @Deprecated
+ public OracleFreeContainer() {
+ this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));
+ }
+
+ public OracleFreeContainer(String dockerImageName) {
+ this(DockerImageName.parse(dockerImageName));
+ }
+
+ public OracleFreeContainer(final DockerImageName dockerImageName) {
+ super(dockerImageName);
+ dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
+ preconfigure();
+ }
+
+ public OracleFreeContainer(Future dockerImageName) {
+ super(dockerImageName);
+ preconfigure();
+ }
+
+ private void preconfigure() {
+ this.waitStrategy =
+ new LogMessageWaitStrategy()
+ .withRegEx(".*DATABASE IS READY TO USE!.*\\s")
+ .withTimes(1)
+ .withStartupTimeout(Duration.of(DEFAULT_STARTUP_TIMEOUT_SECONDS, ChronoUnit.SECONDS));
+
+ withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS);
+ addExposedPorts(ORACLE_PORT);
+ }
+
+ @Override
+ protected void waitUntilContainerStarted() {
+ getWaitStrategy().waitUntilReady(this);
+ }
+
+ @NotNull
+ @Override
+ public Set getLivenessCheckPortNumbers() {
+ return Collections.singleton(getMappedPort(ORACLE_PORT));
+ }
+
+ @Override
+ public String getDriverClassName() {
+ return "oracle.jdbc.driver.OracleDriver";
+ }
+
+ @Override
+ public String getJdbcUrl() {
+ return isUsingSid()
+ ? "jdbc:oracle:thin:" + "@" + getHost() + ":" + getOraclePort() + ":" + getSid()
+ : "jdbc:oracle:thin:" + "@" + getHost() + ":" + getOraclePort() + "/" + getDatabaseName();
+ }
+
+ @Override
+ public String getUsername() {
+ // An application user is tied to the database, and therefore not authenticated to connect to SID.
+ return isUsingSid() ? DEFAULT_SYSTEM_USER : username;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ protected boolean isUsingSid() {
+ return usingSid;
+ }
+
+ @Override
+ public OracleFreeContainer withUsername(String username) {
+ if (StringUtils.isEmpty(username)) {
+ throw new IllegalArgumentException("Username cannot be null or empty");
+ }
+ if (ORACLE_SYSTEM_USERS.contains(username.toLowerCase())) {
+ throw new IllegalArgumentException("Username cannot be one of " + ORACLE_SYSTEM_USERS);
+ }
+ this.username = username;
+ return self();
+ }
+
+ @Override
+ public OracleFreeContainer withPassword(String password) {
+ if (StringUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("Password cannot be null or empty");
+ }
+ this.password = password;
+ return self();
+ }
+
+ @Override
+ public OracleFreeContainer withDatabaseName(String databaseName) {
+ if (StringUtils.isEmpty(databaseName)) {
+ throw new IllegalArgumentException("Database name cannot be null or empty");
+ }
+
+ if (DEFAULT_DATABASE_NAME.equals(databaseName.toLowerCase())) {
+ throw new IllegalArgumentException("Database name cannot be set to " + DEFAULT_DATABASE_NAME);
+ }
+
+ this.databaseName = databaseName;
+ return self();
+ }
+
+ public OracleFreeContainer usingSid() {
+ this.usingSid = true;
+ return self();
+ }
+
+ @Override
+ public OracleFreeContainer withUrlParam(String paramName, String paramValue) {
+ throw new UnsupportedOperationException("The Oracle Database driver does not support this");
+ }
+
+ @SuppressWarnings("SameReturnValue")
+ public String getSid() {
+ return DEFAULT_SID;
+ }
+
+ public Integer getOraclePort() {
+ return getMappedPort(ORACLE_PORT);
+ }
+
+ @SuppressWarnings("unused")
+ public Integer getWebPort() {
+ retur null;
+ }
+
+ @Override
+ public String getTestQueryString() {
+ return "SELECT 1 FROM DUAL";
+ }
+
+ @Override
+ protected void configure() {
+ withEnv("ORACLE_PASSWORD", password);
+
+ // Only set ORACLE_DATABASE if different than the default.
+ if (databaseName != DEFAULT_DATABASE_NAME) {
+ withEnv("ORACLE_DATABASE", databaseName);
+ }
+
+ withEnv("APP_USER", username);
+ withEnv("APP_USER_PASSWORD", password);
+ }
+}