Skip to content

Build/Test Tools: Retry the Docker environment setup - #12736

Closed
adimoldovan wants to merge 8 commits into
WordPress:trunkfrom
adimoldovan:ci-retry-docker-environment-setup
Closed

Build/Test Tools: Retry the Docker environment setup#12736
adimoldovan wants to merge 8 commits into
WordPress:trunkfrom
adimoldovan:ci-retry-docker-environment-setup

Conversation

@adimoldovan

@adimoldovan adimoldovan commented Jul 28, 2026

Copy link
Copy Markdown

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

Stacked on #12735 — that should land first. The retry only works once a failed up actually fails the step.

Testing instructions

  1. The retry is bounded and shows the real error. Three attempts, 10s and 20s apart, registry error each time, exit 1:

    LOCAL_PHP=this-tag-does-not-exist npm run env:pull
    LOCAL_PHP=this-tag-does-not-exist node ./tools/local-env/scripts/start.js
    

    Both take ~33s and end with docker compose <command> failed after 3 attempts.

  2. A command that is not retryable still runs once:

    npm run env:logs
    

    Ctrl-C ends it immediately and does not restart it.

  3. A cold start still works and is no slower:

    npm run env:stop && docker compose down -v
    npm run env:start && npm run env:install
    
  4. env:pull and env:start resolve the same tags. Delete .env, run npm run env:pull, confirm .env exists afterwards and that up does not pull a second database image.

Local Docker Environment, PHPUnit Tests and End-to-end Tests exercise this across large matrices.

Note for reviewers

An earlier version of this PR added retry loops to six reusable workflows. [63163] landed the same idea one layer down, so those are gone and only tools/ changes remain. This is backportable, which the workflow version was not.

composer runs are retried whether or not they are in CI, matching how env:pull already behaves. A genuine dependency conflict therefore costs 30s before it reports.

Branches 4.7 to 5.8 do not run npm run env:pull at all — composer install is the first command to pull the PHP image there, and it is covered.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Investigating the CI failures, writing the patch, and iterative code review. The cross-branch constraints above were checked against every calling branch, and the behaviour was verified locally.


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.

…commands.

`start.js` runs `docker compose up` through `spawnSync` and never inspects the
result, so a failed pull does not fail the script. Execution continues into
`composer update -W` with containers that may not exist, and the error surfaces
later and in the wrong place.

`docker.js` calls `process.exit( returns.status )`, and `status` is `null` when
Docker cannot be spawned. `process.exit( null )` exits 0, so `npm run env:pull`
reports success when the Docker CLI is missing.

Reproduce with:

    LOCAL_PHP=this-tag-does-not-exist npm run env:start; echo $?

See #65745.
Copilot AI review requested due to automatic review settings July 28, 2026 14:15
@adimoldovan adimoldovan self-assigned this Jul 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the local Docker environment setup used by WordPress core CI by ensuring .env exists before any Docker Compose command runs, and by adding bounded retries for image pulls and environment startup across reusable workflows to mitigate transient Docker Hub / Packagist failures.

Changes:

  • Add ensure_env_file() in tools/local-env/scripts/utils.js and call it from start.js, docker.js, and install.js so .env is created synchronously (when missing) before Compose/dotenv usage.
  • Propagate failures from docker compose up (and improve error reporting) in tools/local-env/scripts/start.js; improve error handling/exit behavior in tools/local-env/scripts/docker.js.
  • Add 3-attempt retry loops (10s/20s backoff) for npm run env:pull and npm run env:start in multiple reusable CI workflows.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tools/local-env/scripts/utils.js Adds ensure_env_file() to bootstrap .env from .env.example before any Compose usage.
tools/local-env/scripts/start.js Uses the new .env bootstrap and checks docker compose up exit status to fail fast.
tools/local-env/scripts/install.js Ensures .env exists before loading env vars during install flow.
tools/local-env/scripts/docker.js Ensures .env exists before running Compose; adjusts error/exit handling for Compose commands.
.github/workflows/reusable-test-local-docker-environment-v1.yml Adds retry loops for env:pull and env:start steps.
.github/workflows/reusable-phpunit-tests-v3.yml Adds retry loop for env:start (and contextual comments for retries).
.github/workflows/reusable-phpunit-tests-v2.yml Adds retry loops for env:pull and env:start.
.github/workflows/reusable-phpunit-tests-v1.yml Adds retry loops for env:pull and env:start.
.github/workflows/reusable-performance-test-v2.yml Adds retry loops for env:pull and env:start before building/running perf tests.
.github/workflows/reusable-end-to-end-tests.yml Adds retry loops for env:pull and env:start in E2E workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tools/local-env/scripts/docker.js Outdated
Copilot AI review requested due to automatic review settings July 28, 2026 14:19
@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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

tools/local-env/scripts/docker.js:49

  • docker.js exits with code 0 for any signal-terminated Docker Compose process (returns.signal ? 0 : …). This hides abnormal termination (e.g. SIGTERM/SIGKILL) and can incorrectly report success for non-interactive commands like env:pull if the runner kills the process. If the intent is only to avoid an npm error block on Ctrl+C, special-case SIGINT and keep non-zero exit behavior for other signals.
// `status` is null when Docker could not be spawned at all, or was killed by a signal. Ctrl+C on
// a long-running command such as `env:logs` is not a failure worth an npm error block.
process.exit( returns.signal ? 0 : ( returns.status ?? 1 ) );

Copilot AI review requested due to automatic review settings July 28, 2026 14:49
…nment command.

`docker.js` exited 0 for any signal, so a command killed by SIGTERM or SIGKILL reported success. That reintroduced the false success this ticket set out to remove: the retry loop in `reusable-phpunit-tests-v3.yml` branches on the status of `npm run env:pull` and would treat a killed pull as a completed one.

Restrict the exemption to SIGINT, which is how a long-running command such as `env:logs` is normally ended. Report every other signal and exit non-zero.

`start.js` exempts no signal, because `env:start` runs `composer update -W` next and that must not run against containers that never came up. Say so in the comment, so the difference between the two files is deliberate.

Also fold the unreachable `up.error` branch in `start.js` into the failure message. The `docker info` check above it already throws when the Docker CLI is missing or the daemon is down.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

tools/local-env/scripts/docker.js:49

  • returns.signal ? 0 : … exits successfully for any signal (e.g. SIGTERM), but the comment (and the linked PR context) indicate only SIGINT should be treated as a non-failure. Exiting 0 on SIGTERM can mask real failures/cancellations and cause the surrounding npm step to report success incorrectly.
// `status` is null when Docker could not be spawned at all, or was killed by a signal. Ctrl+C on
// a long-running command such as `env:logs` is not a failure worth an npm error block.
process.exit( returns.signal ? 0 : ( returns.status ?? 1 ) );

@adimoldovan
adimoldovan marked this pull request as ready for review July 28, 2026 15:32
@adimoldovan
adimoldovan requested a review from lancewillett July 28, 2026 15:32
@github-actions

github-actions Bot commented Jul 28, 2026

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 adrianmoldovanwp, lancewillett.

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

`Start Docker environment` failed in 85 runs across 69 branches over 30 days.
Of 92 sampled failures, 63 were Docker Hub and 20 were repo.packagist.org.

`env:pull` was already retried in `reusable-phpunit-tests-v3.yml`. Extend that
to the five remaining workflows, and retry `npm run env:start` as well, since it
also runs `composer update -W`.

The retry stays in the workflows rather than the npm scripts. Released branches
call these reusable workflows at `@trunk` but check out their own
`tools/local-env/` and `package.json`, so a retry in the scripts would only
protect trunk. Keeping it in CI also means a contributor working offline gets an
immediate error instead of waiting through the backoff.

Add `ensure_env_file()` so that every Compose command resolves the same image
tags. The pull step runs before `start.js` created the `.env` file, which made
`env:pull` fall back to the Compose defaults. The synchronous copy also removes
a race: the previous `copyFile` was asynchronous, so `dotenv.config()` could
read a `.env` that was not written yet.

See #65745.
…etries.

- Retry `env:restart` in the local Docker environment workflow. It ends in
  `env:start`, so it has the same registry and Packagist exposure as the
  steps that were already retried.
- Correct the retry comments. They claimed a bad tag would not hold a
  runner, but the loop retries every failure regardless of its cause.
- Resolve `.env` and `.env.example` against the repository root instead of
  the current directory, so running the scripts from a subdirectory no
  longer creates a stray `.env` there.
@adimoldovan
adimoldovan force-pushed the ci-retry-docker-environment-setup branch from e0681ac to 656fc29 Compare July 28, 2026 16:05
Copilot AI review requested due to automatic review settings July 28, 2026 16:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (9)

.github/workflows/reusable-test-local-docker-environment-v1.yml:161

  • This retry loop uses Bash syntax (for ...; do, $(( ... ))). Since runs-on is parameterized via inputs.os, this step can end up running under PowerShell on Windows runners unless shell is set explicitly, causing a hard failure. Add shell: bash here to ensure consistent execution.
      - name: Start Docker environment (with retry)
        run: |

.github/workflows/reusable-test-local-docker-environment-v1.yml:192

  • Same portability issue as the other retry steps: this uses Bash-only constructs, but the workflow accepts inputs.os (which could be Windows => PowerShell default). Add shell: bash so retries work reliably regardless of runner OS.
      - name: Restart Docker environment (with retry)
        run: |

.github/workflows/reusable-phpunit-tests-v3.yml:230

  • The retry logic here is written for Bash, but runs-on is controlled by inputs.os, so this can run under a different default shell (notably PowerShell on Windows). Please set shell: bash for this step so the loop works consistently.
      - name: Start Docker environment (with retry)
        run: |

.github/workflows/reusable-phpunit-tests-v2.yml:177

  • This step's retry loop is Bash-specific, but the workflow's runs-on is parameterized (inputs.os), so it may execute under PowerShell on Windows. Setting shell: bash avoids a cross-OS parse failure.
      - name: Start Docker environment (with retry)
        run: |

.github/workflows/reusable-phpunit-tests-v1.yml:164

  • This retry loop depends on Bash syntax, but runs-on is driven by inputs.os, so the default shell may not be Bash on some runners (e.g. Windows). Add shell: bash to avoid breaking the workflow when inputs.os changes.
      - name: Start Docker environment (with retry)
        run: |

.github/workflows/reusable-test-local-docker-environment-v1.yml:143

  • These retry loops rely on Bash features (POSIX for + arithmetic expansion $(( ... ))). Because this reusable workflow allows inputs.os, the default shell may be PowerShell on Windows runners, which would make this step fail to parse. Consider explicitly setting shell: bash for this step (and other retry steps) to keep the workflow portable across runner OS values.

This issue also appears in the following locations of the same file:

  • line 160
  • line 191
      - name: Pull Docker images (with retry)
        run: |

.github/workflows/reusable-phpunit-tests-v3.yml:212

  • This reusable workflow allows inputs.os, so the default shell can be non-Bash (e.g. PowerShell on Windows). The retry loop uses Bash syntax (for ...; do, $(( ... ))) and will fail to parse in that case. Set shell: bash for this step to keep it OS-agnostic.

This issue also appears on line 229 of the same file.

      - name: Pull Docker images (with retry)
        run: |

.github/workflows/reusable-phpunit-tests-v2.yml:134

  • Because this workflow accepts inputs.os, the default shell isn't guaranteed to be Bash. This new retry loop uses Bash-only syntax and would break on Windows runners (PowerShell default). Add shell: bash here to ensure the step runs consistently.

This issue also appears on line 176 of the same file.

      - name: Pull Docker images (with retry)
        run: |

.github/workflows/reusable-phpunit-tests-v1.yml:135

  • The workflow supports an inputs.os override, but this retry loop is written for Bash and will fail under shells like PowerShell (Windows default). Please set shell: bash for this step to keep it portable.

This issue also appears on line 163 of the same file.

      - name: Pull Docker images (with retry)
        run: |

@lancewillett lancewillett left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested with head e0681ac

Two blockers:

  1. The branch lacks PR 12735’s latest signal fix and restores exit code 0 for SIGTERM/SIGKILL
  2. Reusable workflows check out the caller branch. On 6.8–7.0, env:pull lacks the new .env bootstrap. A controlled 6.8 test confirmed .env remained missing.

Trunk replaced the asynchronous `.env` copy in `start.js` with a synchronous
one. This branch had already moved that bootstrap into `ensure_env_file()` in
`utils.js`, so that `docker.js` and `install.js` create `.env` too. Keep the
shared helper rather than the inline copy.
Copilot AI review requested due to automatic review settings July 30, 2026 09:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

…vironment-setup

# Conflicts:
#	.github/workflows/reusable-phpunit-tests-v3.yml
Follows the approach in [63163], which moved the `env:pull` retry out of the
PHPUnit workflow and into `docker.js`. The same loop now covers the two other
commands that reach a network during `env:start`:

- `docker compose up` in `start.js`, which pulls any missing image.
- `composer install` and `composer update`, which reach repo.packagist.org.

The loop moves to `utils.js` so both scripts share it. Retrying only the pull
left the Packagist failures uncovered: of 92 sampled `Start Docker environment`
failures, 63 were Docker Hub and 20 were repo.packagist.org.

`ensure_env_file()` makes every Compose command resolve image tags from the same
`.env`, and replaces an asynchronous `copyFile` that raced `dotenv.config()` on
a fresh clone.

See #65745.
@adimoldovan adimoldovan changed the title Build/Test Tools: Retry the Docker environment setup in CI Build/Test Tools: Retry the Docker environment setup Aug 13, 2026
adimoldovan added a commit to adimoldovan/wordpress-develop that referenced this pull request Aug 13, 2026
…egistry.

Moves the retry loop from [63163] into `utils.js` so `start.js` can use it too, and
adds `ensure_env_file()` so every script creates `.env` before Compose reads it.

`docker.js` retries `pull` and any command containing `composer`. `start.js` retries
`docker compose up`.

Folds in the changes from PR WordPress#12736.

See #65745.
@adimoldovan

Copy link
Copy Markdown
Author

#12937 partially changed the scope so I decided to fold the changes into #12735 instead for easier landing. Both changes touch only tools/ and share Trac ticket 65745, so they are now on one branch.

Closing this one.

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.

3 participants