Fix OpenSearch port strategy when running inside Docker
diff --git a/localstack/services/opensearch/cluster.py b/localstack/services/opensearch/cluster.py
index 3c3e6a258756b..a8517b8f2e1e4 100644
--- a/localstack/services/opensearch/cluster.py
+++ b/localstack/services/opensearch/cluster.py
@@ -123,11 +123,10 @@ def build_cluster_run_command(cluster_bin: str, settings: CommandSettings) -> Li
 
 
 class OpensearchCluster(Server):
-    """Manages an OpenSearch cluster which is installed an operated by LocalStack."""
+    """Manages an OpenSearch cluster which is installed and operated by LocalStack."""
 
-    # TODO: legacy default port should be removed here
     def __init__(
-        self, port=4571, host="localhost", version: str = None, directories: Directories = None
+        self, port, host="localhost", version: str = None, directories: Directories = None
     ) -> None:
         super().__init__(port, host)
         self._version = version or self.default_version
@@ -201,6 +200,7 @@ def _base_settings(self, dirs) -> CommandSettings:
             "path.data": f'"{dirs.data}"',
             "path.repo": f'"{dirs.backup}"',
             "plugins.security.disabled": "true",
+            "discovery.type": "single-node",
         }
 
         if os.path.exists(os.path.join(dirs.mods, "x-pack-ml")):
@@ -358,6 +358,7 @@ def _base_settings(self, dirs) -> CommandSettings:
             "http.compression": "false",
             "path.data": f'"{dirs.data}"',
             "path.repo": f'"{dirs.backup}"',
+            "discovery.type": "single-node",
         }
 
         if os.path.exists(os.path.join(dirs.mods, "x-pack-ml")):
diff --git a/localstack/services/opensearch/cluster_manager.py b/localstack/services/opensearch/cluster_manager.py
index f6960952f2f1f..02f21d2733795 100644
--- a/localstack/services/opensearch/cluster_manager.py
+++ b/localstack/services/opensearch/cluster_manager.py
@@ -7,6 +7,7 @@
 
 from localstack import config
 from localstack.aws.api.opensearch import DomainEndpointOptions, EngineType
+from localstack.config import EDGE_BIND_HOST
 from localstack.constants import LOCALHOST, LOCALHOST_HOSTNAME
 from localstack.services.generic_proxy import EndpointProxy, FakeEndpointProxyServer
 from localstack.services.opensearch import versions
@@ -255,11 +256,11 @@ def _create_cluster(self, arn, url, version) -> Server:
                 # startup routine for the singleton cluster instance
                 if engine_type == EngineType.OpenSearch:
                     self.cluster = OpensearchCluster(
-                        port=get_free_tcp_port(), directories=resolve_directories(version, arn)
+                        get_free_tcp_port(), directories=resolve_directories(version, arn)
                     )
                 else:
                     self.cluster = ElasticsearchCluster(
-                        port=get_free_tcp_port(), directories=resolve_directories(version, arn)
+                        get_free_tcp_port(), directories=resolve_directories(version, arn)
                     )
 
                 def _start_async(*_):
@@ -305,14 +306,14 @@ def _create_cluster(self, arn, url, version) -> Server:
             port = _get_port_from_url(url)
             if engine_type == EngineType.OpenSearch:
                 return OpensearchCluster(
-                    port=port,
-                    host=LOCALHOST,
+                    port,
+                    host=EDGE_BIND_HOST,
                     version=version,
                     directories=resolve_directories(version, arn),
                 )
             else:
                 return ElasticsearchCluster(
-                    port=port,
+                    port,
                     host=LOCALHOST,
                     version=version,
                     directories=resolve_directories(version, arn),
@@ -354,14 +355,14 @@ def _create_cluster(self, arn, url, version) -> Server:
             engine_type = versions.get_engine_type(version)
             if engine_type == EngineType.OpenSearch:
                 self.cluster = OpensearchCluster(
-                    port=port,
-                    host=LOCALHOST,
+                    port,
+                    host=EDGE_BIND_HOST,
                     version=version,
                     directories=resolve_directories(version, arn),
                 )
             else:
                 self.cluster = ElasticsearchCluster(
-                    port=port,
+                    port,
                     host=LOCALHOST,
                     version=version,
                     directories=resolve_directories(version, arn),
diff --git a/localstack/services/opensearch/provider.py b/localstack/services/opensearch/provider.py
index fcff9523c681d..1e35dca25e129 100644
--- a/localstack/services/opensearch/provider.py
+++ b/localstack/services/opensearch/provider.py
@@ -77,6 +77,7 @@
     VPCDerivedInfoStatus,
     VPCOptions,
 )
+from localstack.config import LOCALSTACK_HOSTNAME
 from localstack.constants import OPENSEARCH_DEFAULT_VERSION
 from localstack.services.generic_proxy import RegionBackend
 from localstack.services.opensearch import versions
@@ -146,7 +147,10 @@ def create_cluster(
     # FIXME: in AWS, the Endpoint is set once the cluster is running, not before (like here), but our tests and
     #  in particular cloudformation currently relies on the assumption that it is set when the domain is created.
     status = region.opensearch_domains[domain_key.domain_name]
-    status["Endpoint"] = cluster.url.split("://")[-1]
+    # Replacing only 0.0.0.0 here as usage of this bind address mostly means running in docker which is used locally
+    # If another bind address is used we want to keep it in the endpoint as this is a conscious user decision to
+    # access from another device on the network.
+    status["Endpoint"] = cluster.url.split("://")[-1].replace("0.0.0.0", LOCALSTACK_HOSTNAME)
     status["EngineVersion"] = engine_version
 
     if cluster.is_up():
diff --git a/tests/integration/test_opensearch.py b/tests/integration/test_opensearch.py
index 7c7fba78a7c79..ce95a1cb9688f 100644
--- a/tests/integration/test_opensearch.py
+++ b/tests/integration/test_opensearch.py
@@ -8,6 +8,7 @@
 
 from localstack import config
 from localstack.aws.accounts import get_aws_account_id
+from localstack.config import EDGE_BIND_HOST, LOCALSTACK_HOSTNAME
 from localstack.constants import OPENSEARCH_DEFAULT_VERSION, OPENSEARCH_PLUGIN_LIST
 from localstack.services.install import install_opensearch
 from localstack.services.opensearch.cluster import EdgeProxiedOpensearchCluster
@@ -413,7 +414,7 @@ def test_endpoint_strategy_port(self, monkeypatch, opensearch_create_domain, ope
         assert "Endpoint" in status
         endpoint = status["Endpoint"]
         parts = endpoint.split(":")
-        assert parts[0] == "localhost"
+        assert parts[0] in ("localhost", "127.0.0.1")
         assert int(parts[1]) in range(
             config.EXTERNAL_SERVICE_PORTS_START, config.EXTERNAL_SERVICE_PORTS_END
         )
@@ -585,7 +586,8 @@ def test_endpoint_strategy_port_singleton_cluster(self, monkeypatch):
 
         parts = cluster_0.url.split(":")
         assert parts[0] == "http"
-        assert parts[1] == "//localhost"
+        # either f"//{the bind host}" is used, or in the case of "//0.0.0.0" the localstack hostname instead
+        assert parts[1][2:] in [EDGE_BIND_HOST, LOCALSTACK_HOSTNAME]
         assert int(parts[2]) in range(
             config.EXTERNAL_SERVICE_PORTS_START, config.EXTERNAL_SERVICE_PORTS_END
         )

When running LocalStack (and therefore OpenSearch/Elasticsearch) in Docker, the search clusters were still bound to localhost in the container when using the port strategy. This caused them to be unreachable. This PR fixes that by addressing #6419 