Support dynamic port on Lambda remote debugging
diff --git a/localstack/utils/container_utils/container_client.py b/localstack/utils/container_utils/container_client.py
index 9cdb3d6c700a8..d0e5830444b88 100644
--- a/localstack/utils/container_utils/container_client.py
+++ b/localstack/utils/container_utils/container_client.py
@@ -166,7 +166,7 @@ def add(
                 else:
                     self.add(port[0] + i, mapped, protocol)
             return
-        if port is None or int(port) <= 0:
+        if port is None or int(port) < 0:
             raise Exception(f"Unable to add mapping for invalid port: {port}")
         if self.contains(port, protocol):
             return
@@ -242,6 +242,14 @@ def entry(k, v):
     def to_dict(self) -> Dict[str, Union[Tuple[str, Union[int, List[int]]], int]]:
         bind_address = self.bind_host or ""
 
+        def bind_port(bind_address, host_port):
+            if host_port == 0:
+                return None
+            elif bind_address:
+                return (bind_address, host_port)
+            else:
+                return host_port
+
         def entry(k, v):
             from_range, protocol = k
             to_range = v
@@ -258,7 +266,7 @@ def entry(k, v):
             return [
                 (
                     f"{container_port}{protocol_suffix}",
-                    (bind_address, host_port) if bind_address else host_port,
+                    bind_port(bind_address, host_port),
                 )
                 for container_port, host_port in zip(
                     range(to_range[0], to_range[1] + 1), range(from_range[0], from_range[1] + 1)
diff --git a/tests/integration/docker_utils/test_docker.py b/tests/integration/docker_utils/test_docker.py
index 064904a6ddfdf..eb9eb85a6f697 100644
--- a/tests/integration/docker_utils/test_docker.py
+++ b/tests/integration/docker_utils/test_docker.py
@@ -1415,6 +1415,23 @@ def test_run_with_additional_arguments_add_dns(
         result = docker_client.inspect_container(container_name)
         assert set(result["HostConfig"]["Dns"]) == {"1.2.3.4", "5.6.7.8"}
 
+    def test_run_with_additional_arguments_random_port(
+        self, docker_client: ContainerClient, create_container
+    ):
+        container = create_container(
+            "alpine",
+            command=["sh", "-c", "while true; do sleep 1; done"],
+            additional_flags="-p 0:80",
+        )
+        docker_client.start_container(container.container_id)
+        inspect_result = docker_client.inspect_container(
+            container_name_or_id=container.container_id
+        )
+        automatic_host_port = int(
+            inspect_result["NetworkSettings"]["Ports"]["80/tcp"][0]["HostPort"]
+        )
+        assert automatic_host_port > 0
+
 
 class TestDockerImages:
     def test_commit_creates_image_from_running_container(self, docker_client: ContainerClient):
diff --git a/tests/unit/test_dockerclient.py b/tests/unit/test_dockerclient.py
index 00df5a069d601..f7a4bb78fe16c 100644
--- a/tests/unit/test_dockerclient.py
+++ b/tests/unit/test_dockerclient.py
@@ -230,6 +230,13 @@ def test_windows_paths(self):
         flags = Util.parse_additional_flags(argument_string)
         assert flags.mounts == [("/var/test", "/var/task")]
 
+    def test_random_ports(self):
+        argument_string = r"-p 0:80"
+        ports = PortMappings()
+        Util.parse_additional_flags(argument_string, ports=ports)
+        assert ports.to_str() == "-p 0:80"
+        assert ports.to_dict() == {"80/tcp": None}
+
 
 def list_in(a, b):
     return len(a) <= len(b) and any(
diff --git a/tests/unit/test_misc.py b/tests/unit/test_misc.py
index 5ba65475afaa5..98ff9a7aceafa 100644
--- a/tests/unit/test_misc.py
+++ b/tests/unit/test_misc.py
@@ -63,6 +63,10 @@ def test_port_mappings(self):
         map.add([234, 237], [345, 348])
         self.assertEqual("-p 123-124:123-124 -p 234-237:345-348", map.to_str())
 
+        map = PortMappings()
+        map.add(0, 123)
+        self.assertEqual("-p 0:123", map.to_str())
+
     def test_port_mappings_single_protocol(self):
         map = PortMappings()
         map.add(port=53, protocol="udp")
@@ -110,6 +114,15 @@ def test_port_mappings_dict(self):
             map.to_dict(),
         )
 
+        map = PortMappings()
+        map.add(port=0, mapped=123, protocol="tcp")
+        self.assertEqual(
+            {
+                "123/tcp": None,
+            },
+            map.to_dict(),
+        )
+
     def test_port_mappings_list(self):
         map = PortMappings()
         map.add(port=[122, 124], protocol="tcp")
@@ -119,6 +132,10 @@ def test_port_mappings_list(self):
         map.add(port=[124, 126], protocol="udp")
         self.assertEqual(["-p", "122-125:122-125", "-p", "123-126:123-126/udp"], map.to_list())
 
+        map = PortMappings()
+        map.add(port=0, mapped=123, protocol="tcp")
+        self.assertEqual(["-p", "0:123"], map.to_list())
+
     def test_update_config_variable(self):
         config_listener.update_config_variable("foo", "bar")
         self.assertEqual("bar", config.foo)

<!-- Please refer to the contribution guidelines before raising a PR: https://github.com/localstack/localstack/blob/master/CONTRIBUTING.md -->

<!-- Why am I raising this PR? Add context such as related issues, PRs, or documentation. -->
## Motivation

This is solution for #9419 

The differences in behavior are as follows.

### before

1. Expose fixed port with `LAMBDA_DOCKER_FLAGS` setting.

```
LAMBDA_DOCKER_FLAGS=-e NODE_OPTIONS=--inspect=0.0.0.0:9229 -p 9229:9229
```

2. The first function execution succeeds and the container exposes the fixed port.

![image](https://github.com/localstack/localstack/assets/77612853/9ff41af3-8e88-4515-aae9-b9ceb25213b1)

3. The second function execution fails due to an error in the bind to the same port.

![image](https://github.com/localstack/localstack/assets/77612853/e9ef3b0c-122a-4c74-9914-824e17013b8a)


### after

1. Expose dynamic port with `LAMBDA_DOCKER_FLAGS` setting.

```
LAMBDA_DOCKER_FLAGS=-e NODE_OPTIONS=--inspect=0.0.0.0:9229 -p 0:9229
```

2. The first function execution succeeds and the container exposes the dynamic port.

![image](https://github.com/localstack/localstack/assets/77612853/bf37c588-8c2b-4ea2-af07-2ae3468cf20a)

3. The second function execution also succeeds and the container exposes another dynamic port.

![image](https://github.com/localstack/localstack/assets/77612853/7cd8e073-6c8d-4acb-8812-df00e7be78e1)


<!-- What notable changes does this PR make? -->
## Changes

It is possible to run multiple Lambda functions with remote debugging configured.

<!-- The following sections are optional, but can be useful! 

## Testing

Description of how to test the changes

## TODO

What's left to do:

- [ ] ...
- [ ] ...

-->

