From 0722557d0ac0dbd78fa7b09304130f6509bff66e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 18 Jun 2026 11:23:36 -0700 Subject: [PATCH 1/6] Build/Test Tools: Make loopback requests work in the local Docker environment. Inside the php/cli containers `localhost` is the container's own loopback, where nothing listens on the published web-server port, so WordPress loopback requests (Site Health, cron, `wp_remote_get( home_url() )`) fail with "cURL error 7: Could not connect to server". The `extra_hosts: localhost:host-gateway` mapping cannot fix this because Docker always writes `127.0.0.1 localhost` first in `/etc/hosts`, shadowing the gateway entry. Add a development-only mu-plugin that pins loopback requests to the Docker host gateway (via `host.docker.internal`) using `CURLOPT_RESOLVE`, and copy it into the gitignored mu-plugins directory from the php container on startup, mirroring the existing object-cache.php drop-in pattern. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.yml | 5 +- .../mu-plugins/fix-docker-loopback.php | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 tools/local-env/mu-plugins/fix-docker-loopback.php diff --git a/docker-compose.yml b/docker-compose.yml index cc2ed8d94975e..18fedab5bb19a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,8 +51,9 @@ services: - ./tools/local-env/php-config.ini:/usr/local/etc/php/conf.d/php-config.ini - ./:/var/www - # Copy or delete the Memcached dropin plugin file as appropriate. - command: /bin/sh -c "if [ $LOCAL_PHP_MEMCACHED = true ]; then cp -n /var/www/tests/phpunit/includes/object-cache.php /var/www/src/wp-content/object-cache.php; else rm -f /var/www/src/wp-content/object-cache.php; fi && exec php-fpm" + # Install the loopback-request shim (see tools/local-env/mu-plugins/fix-docker-loopback.php), then + # copy or delete the Memcached dropin plugin file as appropriate. + command: /bin/sh -c "mkdir -p /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins && cp -f /var/www/tools/local-env/mu-plugins/fix-docker-loopback.php /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins/fix-docker-loopback.php; if [ $LOCAL_PHP_MEMCACHED = true ]; then cp -n /var/www/tests/phpunit/includes/object-cache.php /var/www/src/wp-content/object-cache.php; else rm -f /var/www/src/wp-content/object-cache.php; fi && exec php-fpm" # The init directive ensures the command runs with a PID > 1, so Ctrl+C works correctly. init: true diff --git a/tools/local-env/mu-plugins/fix-docker-loopback.php b/tools/local-env/mu-plugins/fix-docker-loopback.php new file mode 100644 index 0000000000000..8b5b6315e3811 --- /dev/null +++ b/tools/local-env/mu-plugins/fix-docker-loopback.php @@ -0,0 +1,51 @@ + $args The HTTP request arguments. + * @param string $url The request URL. + */ +function resolve_loopback_to_host_gateway( $handle, array $args, string $url ): void { + $host = wp_parse_url( $url, PHP_URL_HOST ); + if ( ! is_string( $host ) ) { + return; + } + + // Only rewrite requests aimed at this site (loopback), not arbitrary outbound requests. + $home_host = wp_parse_url( home_url(), PHP_URL_HOST ); + if ( $host !== $home_host ) { + return; + } + + // host.docker.internal resolves to the host gateway, which reaches the published web-server port. + $gateway = gethostbyname( 'host.docker.internal' ); + if ( 'host.docker.internal' === $gateway ) { + return; // Not running under Docker Desktop / gateway unavailable. + } + + $port = wp_parse_url( $url, PHP_URL_PORT ); + if ( ! $port ) { + $port = ( 'https' === wp_parse_url( $url, PHP_URL_SCHEME ) ) ? 443 : 80; + } + + curl_setopt( $handle, CURLOPT_RESOLVE, array( "{$host}:{$port}:{$gateway}" ) ); +} From 74194c5020b1cc97020541b8294543c14bec273e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 18 Jun 2026 11:24:10 -0700 Subject: [PATCH 2/6] Suggest ext-curl in composer.json --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 5c016d37316c1..21848148ad132 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "php": ">=7.4" }, "suggest": { + "ext-curl": "*", "ext-dom": "*" }, "require-dev": { From e8dfeac93de5ff13258dbafa2f14684535b3aaad Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 18 Jun 2026 11:35:32 -0700 Subject: [PATCH 3/6] Add comment explaining how to edit the mu-plugin --- tools/local-env/mu-plugins/fix-docker-loopback.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/local-env/mu-plugins/fix-docker-loopback.php b/tools/local-env/mu-plugins/fix-docker-loopback.php index 8b5b6315e3811..f918c5c1b4e7e 100644 --- a/tools/local-env/mu-plugins/fix-docker-loopback.php +++ b/tools/local-env/mu-plugins/fix-docker-loopback.php @@ -5,6 +5,9 @@ * * This is a development-environment-only shim and should never ship to production. * + * This file is copied from tools/local-env/mu-plugins/fix-docker-loopback.php, so ensure any changes are made there. + * Restart the environment to apply the changes, as that file will be re-copied when the container starts. + * * @package WordPress\Develop */ From 959fab2a96525bfd20b3d59645c43206ee2f358b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 18 Jun 2026 11:37:17 -0700 Subject: [PATCH 4/6] Build/Test Tools: Clarify when the loopback shim is needed. Update the mu-plugin description to explain that the docker-compose `extra_hosts: localhost:host-gateway` mapping has no effect when cURL resolves "localhost" via glibc's getaddrinfo() (which special-cases the name to loopback), and that the shim is therefore not always necessary -- resolvers that honor the mapping (e.g. a c-ares-based libcurl) already complete loopback requests. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/local-env/mu-plugins/fix-docker-loopback.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/local-env/mu-plugins/fix-docker-loopback.php b/tools/local-env/mu-plugins/fix-docker-loopback.php index f918c5c1b4e7e..6c8dbaac03707 100644 --- a/tools/local-env/mu-plugins/fix-docker-loopback.php +++ b/tools/local-env/mu-plugins/fix-docker-loopback.php @@ -1,7 +1,7 @@ Date: Thu, 18 Jun 2026 11:41:52 -0700 Subject: [PATCH 5/6] Build/Test Tools: Wrap the php container command for readability. Convert the php service `command` to a YAML folded block scalar so the mu-plugin install and Memcached dropin steps each read on their own line. The folded newlines collapse back into single spaces, so the resolved command is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 18fedab5bb19a..4c1ac797d6bac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,14 @@ services: # Install the loopback-request shim (see tools/local-env/mu-plugins/fix-docker-loopback.php), then # copy or delete the Memcached dropin plugin file as appropriate. - command: /bin/sh -c "mkdir -p /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins && cp -f /var/www/tools/local-env/mu-plugins/fix-docker-loopback.php /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins/fix-docker-loopback.php; if [ $LOCAL_PHP_MEMCACHED = true ]; then cp -n /var/www/tests/phpunit/includes/object-cache.php /var/www/src/wp-content/object-cache.php; else rm -f /var/www/src/wp-content/object-cache.php; fi && exec php-fpm" + # Note: this is a YAML folded block scalar, so each line break below collapses into a single space. + command: >- + /bin/sh -c "mkdir -p /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins + && cp -f /var/www/tools/local-env/mu-plugins/fix-docker-loopback.php /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins/fix-docker-loopback.php; + if [ $LOCAL_PHP_MEMCACHED = true ]; + then cp -n /var/www/tests/phpunit/includes/object-cache.php /var/www/src/wp-content/object-cache.php; + else rm -f /var/www/src/wp-content/object-cache.php; + fi && exec php-fpm" # The init directive ensures the command runs with a PID > 1, so Ctrl+C works correctly. init: true From 4a4c575ffb8a9c0c6754572a663b0ae9448d91bd Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 18 Jun 2026 11:47:53 -0700 Subject: [PATCH 6/6] Build/Test Tools: Format the php container command as an indented script. Lay the folded `command` out as an indented multi-line shell script and drop the note about YAML folding mechanics. The resolved command is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4c1ac797d6bac..1082bd1bf3553 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,14 +53,15 @@ services: # Install the loopback-request shim (see tools/local-env/mu-plugins/fix-docker-loopback.php), then # copy or delete the Memcached dropin plugin file as appropriate. - # Note: this is a YAML folded block scalar, so each line break below collapses into a single space. command: >- - /bin/sh -c "mkdir -p /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins - && cp -f /var/www/tools/local-env/mu-plugins/fix-docker-loopback.php /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins/fix-docker-loopback.php; - if [ $LOCAL_PHP_MEMCACHED = true ]; - then cp -n /var/www/tests/phpunit/includes/object-cache.php /var/www/src/wp-content/object-cache.php; - else rm -f /var/www/src/wp-content/object-cache.php; - fi && exec php-fpm" + /bin/sh -c " + mkdir -p /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins && cp -f /var/www/tools/local-env/mu-plugins/fix-docker-loopback.php /var/www/${LOCAL_DIR-src}/wp-content/mu-plugins/fix-docker-loopback.php; + if [ $LOCAL_PHP_MEMCACHED = true ]; then + cp -n /var/www/tests/phpunit/includes/object-cache.php /var/www/src/wp-content/object-cache.php; + else + rm -f /var/www/src/wp-content/object-cache.php; + fi && exec php-fpm + " # The init directive ensures the command runs with a PID > 1, so Ctrl+C works correctly. init: true