Skip to content

Repair wp/5.5 branch Actions: Migrate wp-env to Docker Compose v2 - #289

Closed
desrosj wants to merge 2 commits into
fix-actions-5.5/add-workflow-concurrencyfrom
fix-actions-5.5/fix-docker-compose-v2
Closed

desrosj wants to merge 2 commits into
fix-actions-5.5/add-workflow-concurrencyfrom
fix-actions-5.5/fix-docker-compose-v2

Conversation

@desrosj

@desrosj desrosj commented Sep 11, 2026 •

Copy link
Copy Markdown
Owner

What?

Migrates wp-env (packages/env) from Docker Compose v1 (a standalone docker-compose binary) to Docker Compose v2 (the docker compose subcommand provided by the docker CLI plugin), pinning the docker-compose npm dependency to the exact 0.24.0 release rather than a ^0.24.3-style range.

Why?

packages/env shells out to a literal docker-compose binary via the docker-compose npm package's default (non-.v2) export, and run.js also spawns that binary directly for interactive commands. GitHub's current runner images no longer ship the standalone Docker Compose v1 binary — only the docker compose v2 subcommand (via the docker CLI plugin) is present now.

As a result, wp-env start (and every other wp-env command) fails immediately with Error: spawn docker-compose ENOENT. This is the root cause of essentially all e2e and PHP-unit-test CI failures on this branch, since every one of those jobs depends on wp-env to stand up a WordPress environment before anything else can run.

This branch has no Playwright e2e at all (only Puppeteer, via end2end-test.yml, confirmed via package.json), so there is no separate Noble-runner browser-install issue to also account for here.

The .v2 export this fix needs only exists in docker-compose npm package versions >=0.23.x. However, this branch also pins Node 12 (.nvmrc), and every docker-compose release from 0.24.1 onward depends on yaml@^2.2.2, which ships its compiled output using optional chaining (?.) syntax — a syntax V8 didn't support before Node 14. Pulling in yaml@2.x breaks wp-env start itself (and every Puppeteer e2e job that depends on it) with SyntaxError: Unexpected token '.', thrown by plain Node loading yaml/dist/compose/composer.js directly, independent of and in addition to the Docker Compose migration itself. This was confirmed live on an earlier version of this PR that bumped to ^0.24.3 (see Verification below for the full before/after).

Checking every 0.23.x/0.24.x patch release's own declared dependency (npm view docker-compose@<version> dependencies) found a clean, exact boundary: 0.23.0–0.23.6 have no dependencies at all; 0.23.7 through 0.24.0 depend on yaml@^1.10.2 (plain CommonJS, no optional chaining); 0.24.1 onward depend on yaml@^2.2.2. Pinning the exact 0.24.0 release gets the .v2 export this fix needs while resolving to yaml@1.x instead — sidestepping the Node 12 incompatibility entirely, rather than needing a Node bump or a Jest-only workaround that wouldn't actually fix a real wp-env start.

How?

This is the same fix WordPress/gutenberg landed on trunk as commit a0943f2fba4a5df0e5ee051bfc567b7f6f7f1942 ("wp-env: Migrate to Compose V2", #51339, fixes #51249), adapted to this branch's own file layout, dependency versions, and Node 12 constraint:

File Change
packages/env/lib/cli.js Reworded the docker-compose-error handling comment/message to "docker compose" and updated the referenced upstream source link.
packages/env/lib/commands/clean.js require( 'docker-compose' ) → { v2: dockerCompose } = require( 'docker-compose' )
packages/env/lib/commands/destroy.js Same as above.
packages/env/lib/commands/logs.js Same as above.
packages/env/lib/commands/start.js Same as above.
packages/env/lib/commands/stop.js Same as above.
packages/env/lib/wordpress.js Same as above.
packages/env/lib/commands/run.js Prepends 'compose' to the manually-built compose argument list, and spawns docker instead of docker-compose directly (this file bypasses the npm package for interactive commands like bash, since the package's underlying implementation always passes -T).
packages/env/test/cli.js Updated a unit test that asserted the exact old 'Error while running docker-compose command.' wording, to match cli.js's new message.
packages/env/package.json Bumped docker-compose from ^0.22.2 to exactly 0.24.0 (not a caret range — a range would let a future un-pinned install resolve up into 0.24.1+, reintroducing yaml@2.x).
package-lock.json Added docker-compose@0.24.0 and its yaml@1.10.3 dependency, nested under @wordpress/env's own dependency tree since the version needed conflicts with the yaml@1.7.2 already pinned at the top level for unrelated packages. version/resolved/integrity for both taken directly from the npm registry and cross-checked against a real npm install docker-compose@0.24.0's own generated lockfile output.

No test/unit/jest.config.js change is needed: yaml@1.x is plain CommonJS (confirmed via its package.json's "type": "commonjs"), so Jest's unmodified default transform handling already loads it — unlike yaml@2.x, which ships as an ES module and would otherwise need a transformIgnorePatterns allow-list (as landed on sibling branches in this project that couldn't avoid the yaml@2.x bump).

Verification performed

An earlier version of this PR bumped docker-compose to ^0.24.3 and added a jest.config.js fix for yaml@2.x's ESM output. Real CI on that version confirmed:

  • The JavaScript job's SyntaxError: Unexpected token '.' (from Jest loading yaml/dist/compose/composer.js) was fixed by the transformIgnorePatterns allow-list.
  • But the PHP job's Install WordPress step (npm run wp-env start) and all four Admin (Puppeteer e2e) jobs still failed with the identical SyntaxError: Unexpected token '.' — this time thrown by plain Node itself (not Jest) loading the same file, at if (prelude[i + 1]?.[0] !== '#'), since this branch's pinned Node 12 predates optional-chaining support. This meant the Docker Compose v2 migration could not actually complete a real wp-env start on this branch as written.

After re-pinning to docker-compose@0.24.0 (this PR's current state) and pushing:

  • Real Node 12 verification: installed docker-compose@0.24.0 under an actual Node 12.22.12 binary (via fnm install 12) in an isolated scratch directory. require('docker-compose').v2 resolves cleanly to an object exposing every method this codebase calls (upAll, down, stop, exec, run, etc.) as real functions. Grepped the resolved yaml@1.10.3 package for ?. — zero matches, and its package.json declares "type": "commonjs".
  • Real CI, triggered by the force-push and watched to completion: grepping every PHP, Admin - 1 through Admin - 4, and JavaScript job's raw log for SyntaxError/Unexpected token/yaml found zero matches across all of them — the target bug is gone. Install WordPress actually executes real docker compose down/up/run invocations to completion, with both the mysql and wordpress containers reported Running by docker compose itself.
  • CI is not yet fully green, but for reasons unrelated to this PR's target bug: JavaScript still fails on the same pre-existing, already-documented caniuse-lite/ignore-browserslist-old-data gap noted in this project's tracking (nothing to do with this fix). PHP and all four Admin jobs now get past Docker Compose entirely, but hit a new, different, previously-unreached failure: wp-env's own database-wait retry loop times out with a MariaDB authentication error (mariadb-check: Got error: 1045: Access denied for user 'example username'@'...' (using password: YES)) — a genuinely new finding, not yet root-caused, out of scope for this PR (which targets the Docker Compose/yaml/Node-12 issue specifically), and a good candidate for a follow-up task now that CI gets far enough to expose it. Check (Static Analysis) fails for the same pre-existing, unrelated check-local-changes reason already confirmed on this PR's own base branch.
  • package-lock.json was validated as well-formed JSON after editing, and its edited region diffs down to exactly the version/resolved/integrity fields for docker-compose and yaml — nothing else in the file touched.

Use of AI Tools

This PR was created by Claude Code under my supervision. All code should be treated as AI-produced and not yet reviewed by a human until this PR is marked Ready for Review.

@desrosj
desrosj force-pushed the fix-actions-5.5/fix-docker-compose-v2 branch from 6e58a0e to e79ca86 Compare September 11, 2026 21:25
@desrosj
desrosj force-pushed the fix-actions-5.5/fix-docker-compose-v2 branch from e79ca86 to 50a48ca Compare September 12, 2026 12:46
`packages/env` shells out to a standalone `docker-compose` binary (Docker
Compose v1) via the `docker-compose` npm package's default export.
GitHub's current runner images no longer ship that binary — only the
`docker compose` v2 subcommand, provided by the `docker` CLI plugin, is
present now. As a result, `wp-env start` (and every other `wp-env`
command) fails immediately with `Error: spawn docker-compose ENOENT`,
which is the root cause of effectively all e2e and PHP-unit-test CI
failures on this branch.

This switches every command module (`clean.js`, `destroy.js`, `logs.js`,
`start.js`, `stop.js`, `wordpress.js`) from the package's default export
to its `v2` export, which shells out to `docker compose` instead of the
removed `docker-compose` binary. `run.js`'s manual `spawn()` call (used
so interactive commands like `bash` work, which the npm package can't
support) is updated the same way: it now spawns `docker` with `compose`
prepended to its argument list instead of spawning `docker-compose`
directly. `cli.js`'s error-handling comments/message are reworded to
match, and its corresponding unit test in `test/cli.js` (which asserted
the exact old wording) is updated to match the new message.

The `v2` export only exists in `docker-compose` npm package versions
`>=0.23.x`; this branch was pinned to `^0.22.2`, so `packages/env/package.json`
and `package-lock.json` are updated to bump the dependency. Unlike a
plain `^0.24.3` bump, this pins the exact `0.24.0` release rather than a
caret range: `docker-compose`'s own dependency on `yaml` changed from
`^1.10.2` to `^2.2.2` starting with `0.24.1` (checked every `0.23.x` and
`0.24.x` patch release's declared `yaml` dependency directly via `npm
view docker-compose@<version> dependencies` — `0.23.7`–`0.24.0` all
depend on `yaml@^1.10.2`, `0.24.1` onward depend on `yaml@^2.2.2`;
`0.23.0`–`0.23.6` have no dependencies at all). `0.24.0` is the newest
release that still resolves to a `yaml@1.x`, and it carries none of the
API changes `0.24.1`–`0.24.3` added (a stray `console.log` removal, `ps`
JSON-format support) that this codebase's own call sites don't use, so
it's a safe, minimal choice rather than a downgrade in practice. This
branch's `.nvmrc` pins Node 12, and `yaml@2.x`'s compiled output uses
optional-chaining (`?.`) syntax that V8 didn't support before Node 14 —
confirmed via a real Node 12 `require('docker-compose').v2` on this
exact pin resolving cleanly, and via a direct search of the installed
`yaml@1.10.3` package for `?.` (as well as its `package.json` declaring
plain `"type": "commonjs"`) turning up nothing, whereas the equivalent
check against `yaml@2.x` does. Avoiding `yaml@2.x` entirely means this
branch's already-limited Node 12 runtime never has to load that syntax
in the first place, and (unlike the `yaml@2.x`-based fix that would
otherwise be needed here) no `test/unit/jest.config.js`
`transformIgnorePatterns` allow-list is required either: `yaml@1.x` is
plain CommonJS, so Jest's unmodified default already loads it.

This is the same fix WordPress/gutenberg landed on trunk as commit
`a0943f2fba4a5df0e5ee051bfc567b7f6f7f1942` ("wp-env: Migrate to Compose
V2 (WordPress#51339)"), adapted to this branch's own file layout, dependency
versions, and Node 12 constraint.
…vice that talks to the database.

Real CI on this PR (after the Docker Compose v2 fix let `wp-env start`
get this far for the first time) showed `wp-env start` hanging for ~30
retries and then failing at the `wp db check` step with:

```
mariadb-check: Got error: 1045: Access denied for user 'example username'@'172.18.0.5' (using password: YES) when trying to connect
```

The literal, space-containing username is not a real credential anyone
configured — it is the hardcoded placeholder default baked into the
official `wordpress` Docker image's `wp-config-docker.php` template:

```php
define( 'DB_USER', getenv_docker('WORDPRESS_DB_USER', 'example username') );
define( 'DB_PASSWORD', getenv_docker('WORDPRESS_DB_PASSWORD', 'example password') );
```

`getenv_docker()` re-evaluates these environment variables every time
`wp-config.php` is loaded, in whatever process loads it — it does not
bake a value in once and share it. `wp-config.php` is generated once by
the `wordpress`/`tests-wordpress` apache containers and then read from
the same shared volume by `cli`/`tests-cli` (which is what actually
runs `wp db check`/`wp core install`) and by `phpunit` (via the
`phpunit-wp-config.php` copy `sed` produces from it). Each of those
containers evaluates `getenv_docker()` using *its own* environment, so
setting `WORDPRESS_DB_USER`/`WORDPRESS_DB_PASSWORD` only on
`wordpress`/`tests-wordpress` is not enough — confirmed directly with a
local reproduction: doing only that still failed with the identical
error, because `cli`'s own environment (which is what `wp db check`
actually runs in) still had neither variable set.

This branch's `wordpress`/`tests-wordpress`/`cli`/`tests-cli`/`phpunit`
services only ever set `WORDPRESS_DB_NAME` (or nothing at all, for
`cli`/`tests-cli`/`phpunit`) — never `WORDPRESS_DB_USER` or
`WORDPRESS_DB_PASSWORD` — so every one of them falls back to the
placeholder default. The `mysql` service, meanwhile, is configured with
`MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'` and no `MYSQL_USER`/`MYSQL_PASSWORD`
of its own, so the only account that actually exists in the database is
`root` with an empty password — never a match for the placeholder.

This is not a Docker Compose v1-vs-v2 regression: the environment
variables a `docker-compose.yml` `environment:` block passes into a
container work identically under both `docker-compose` and
`docker compose`, and this branch's own `build-docker-compose-config.js`
never set these two variables anywhere, in either case. The bug was
simply unreachable until now, because `wp-env start` always failed
earlier, at the `docker-compose` v1 binary's `ENOENT`.

Setting `WORDPRESS_DB_USER: 'root'` and `WORDPRESS_DB_PASSWORD: ''`
explicitly on every service that ever reads `wp-config.php` (matching
the `mysql` service's actual, empty-password `root` account) makes each
of them connect as a user that genuinely exists, instead of silently
falling back to the Docker image's documentation placeholder.
@desrosj

desrosj commented Sep 13, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #313, which applies this same fix as a properly stacked layer on the real wp/5.5 PR chain.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant