Visitar URL original
No host port mapping after creating container · Issue #3 · testcontainers/testcontainers-php · GitHub
Skip to content

No host port mapping after creating container #3

Open

Description

@kursph

I noticed when creating a new MySQLContainer like so

protected function setUp(): void
{
      $this->container = MySQLContainer::make();
      $this->container->withMySQLDatabase('pimcore');
      $this->container->withMySQLUser('pimcore_user', 'pimcore');
      try {
          $this->container->run();
      } catch (JsonException $e) {
          $this->fail($e->getMessage());
      }
}

but when checking out the docker container with docker ps and docker inspect there is no port mapped to the host (localhost). And then when I try to connect to it like so

$this->pdo = new PDO(
      sprintf('mysql:host=%s;port=3306', $this->container->getAddress()),
      'pimcore_user',
      'pimcore',
);

host missing when inspecting the container

"Ports": {
      "3306/tcp": null,
      "33060/tcp": null
},

I get this timeout error

1) Tests\App\journey\ContactIntegrationTest::testGetAllAction
PDOException: SQLSTATE[HY000] [2002] Operation timed out

here the container in Docker Desktop
Bildschirm­foto 2023-12-21 um 11 05 05

Activity

  1. shyim commented on Dec 21, 2023

    @shyim
    Collaborator

    In which environment are you running PHPUnit? We use the internal IP of the container right now, so we don't have to do any port mapping :)

  2. hantsy commented on Jun 7, 2024

    @hantsy

    Testcontainer Java uses random ports if it is not set, this way can avoid port conflict. After container is started, we can get the real exposed port.

  3. shyim commented on Jun 7, 2024

    @shyim
    Collaborator

    Do you have an link to the implementation? What do they use as "IP", that would be interesting 🤔, hard-code 127.0.0.1 🤔

  4. hantsy commented on Jun 8, 2024

    @hantsy

    I used Testcontainers Java in projects for years, and by default when declaring a container, it will expose all ports, and map to random ports of clients(your machine), it has APIs to get the mapped ports, and first mapping port, etc.

    see: https://github.com/hantsy/spring-r2dbc-sample/blob/master/testcontainers/src/test/java/com/example/demo/PostRepositoryWithTestcontainersAnnotationsAndDynamicPropertySourceTest.java#L35

  5. MaxDaten commented on Feb 13, 2025

    @MaxDaten

    In which environment are you running PHPUnit? We use the internal IP of the container right now, so we don't have to do any port mapping :)

    Same problem here. I think it's macOS-specific because docker is running differently. If no exposed port maps to the host, you can't connect to the container from the host where the PHPUnit is running.

    Workaround, use withPort and localhost/127.0.0.1

    For example, should be easily adaptable to plain PDO:

    abstract class DatabaseTestCase extends TestCase
    {
    
        protected Zend_Db_Adapter_Abstract $db;
        protected MySQLContainer $container;
    
        /**
         * @throws \Zend_Db_Exception
         */
        protected function setUp(): void
        {
            $container = MySQLContainer::make('8.0');
            $container->withMySQLDatabase('foo');
            $container->withMySQLUser('bar', 'baz');
            $randomPort = rand(3300, 3400);
            $container->withPort($randomPort, 3306);
    
            $container->run();
    
            $params = [
                'host' => '127.0.0.1',
                'port' => $randomPort,
                'dbname' => 'foo',
                'username' => 'bar',
                'password' => 'baz',
                'driver_options' => [
                    PDO::ATTR_TIMEOUT => 2,
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ],
            ];
    
            $this->db = Zend_Db::factory(
                "pdo_mysql",
                $params
            );
            Zend_Db_Table_Abstract::setDefaultAdapter($this->db);
        }
    }
  6. thoro commented on Feb 3, 2026

    @thoro

    I just ran into the same issue, I believe multiple issues come together

    1. Dockerfile does not contain EXPOSE - if the same ports are exposed as the Dockerfile exposes all, works
    2. withExposedPorts does not automatically expose ports, because it only sets the hostConfig and not the containerConfig
    3. it's not possible to expose the ports in the containerConfig, because there's a bug in the normalizer that creates an array in the json instead of an object.

    so here is a fixed containers implementation that makes it work:

    class FixedGenericContainer extends GenericContainer
    {
        protected function createContainerConfig(): ContainersCreatePostBody
        {
            $config = parent::createContainerConfig();
    
            // Set ExposedPorts on the container config
            // This is required for images that don't have EXPOSE in their Dockerfile
            if ($this->exposedPorts !== []) {
                $exposedPortsConfig = [];
                foreach ($this->exposedPorts as $port) {
                    $item = new ContainerConfigExposedPortsItem();
                    // Workaround: Add dummy key so it serializes to {"_":true} instead of []
                    // Docker accepts and ignores the extra key
                    $item['_'] = true;
                    $exposedPortsConfig[$port] = $item;
                }
                $config->setExposedPorts($exposedPortsConfig);
            }
    
            return $config;
        }
    }
    

    The normalizer bug is here: https://github.com/beluga-php/docker-php-api/blob/a287d09bb7164da7cef5b1e652550a15cb739d58/src/Normalizer/ContainerConfigExposedPortsItemNormalizer.php#L57

    Here is also a Claude summary in case someone wants to read that:

      Bug: Images without EXPOSE don't get port bindings
    
      When using withExposedPorts() on images that don't have EXPOSE in their Dockerfile, port bindings don't work.
    
      Root cause: ContainerConfigExposedPortsItem (extends ArrayObject) serializes to [] when empty, but Docker expects {}. The ContainerConfigExposedPortsItemNormalizer::normalize() returns an empty array which
      JSON-encodes as [].
    
      Workaround: Set a dummy key in the item:
      $item = new ContainerConfigExposedPortsItem();
      $item['_'] = true;  // Forces serialization to {"_":true} instead of []
    
      Fix: The normalizer should return new \stdClass() for empty items to produce {} in JSON.
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions