Skip to content

Build/Test Tools: Make loopback requests work in the local Docker environment#12219

Open
westonruter wants to merge 7 commits into
WordPress:trunkfrom
westonruter:fix/local-env-loopback-requests
Open

Build/Test Tools: Make loopback requests work in the local Docker environment#12219
westonruter wants to merge 7 commits into
WordPress:trunkfrom
westonruter:fix/local-env-loopback-requests

Conversation

@westonruter

Copy link
Copy Markdown
Member

Problem

Loopback HTTP requests fail in the local Docker environment. The Site Health "loopback request" test reports a failure, and any in-PHP loopback fails too:

$ npm run env:cli -- eval "var_dump( wp_remote_get( home_url() ) );"
cURL error 7: Failed to connect to localhost port 8000 after 4 ms: Could not connect to server

The web server (nginx) runs in a separate container from php/cli. home_url() is http://localhost:8000, but inside those containers localhost is the container's own loopback, where nothing listens on the published port — so the request is refused.

The existing extra_hosts: localhost:host-gateway mapping (added in [a629d1c]) is meant to address this, but it has no effect when cURL resolves localhost via glibc's getaddrinfo(), which special-cases that name to loopback and never consults the /etc/hosts gateway entry. (The legacy gethostbyname() path does return the gateway, but the HTTP stack doesn't use it.) The host gateway itself is reachable — host.docker.internal:8000 returns 200 — so only name resolution of localhost is broken.

Fix

Add a development-only mu-plugin (tools/local-env/mu-plugins/fix-docker-loopback.php) that, for requests aimed at the site's own host, pins the cURL handle to the Docker host gateway via CURLOPT_RESOLVE (resolved at runtime through host.docker.internal). It is gated on wp_get_environment_type() === 'local' and no-ops where the gateway is unavailable.

The php container copies the shim into the (gitignored) wp-content/mu-plugins directory on startup, mirroring the existing object-cache.php drop-in pattern, so it covers Site Health, cron spawning, and wp_remote_get( home_url() ) from both php and cli.

The extra_hosts lines are left in place — they are the intended mechanism and remain effective on resolvers that honor them (e.g. a c-ares-based libcurl); this shim backs them up where they do not. composer.json is also updated to suggest ext-curl, which the shim relies on.

Testing

With the environment running:

  • wp_remote_get( home_url() )HTTP 200
  • WP_Site_Health::can_perform_loopback()status=good

PHPCS and PHPStan (level 10, via phpstan-diff) both pass on the new file.

Trac ticket: https://core.trac.wordpress.org/ticket/65484

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.8
Used for: Diagnosing the loopback failure, drafting the mu-plugin and docker-compose.yml changes, and verifying the fix end-to-end. All changes were reviewed and edited by me.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

westonruter and others added 6 commits June 18, 2026 11:39
…ironment.

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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
…ipt.

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) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

This comment was marked as outdated.

@westonruter westonruter requested a review from desrosj June 18, 2026 18:52
@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant