chore: parallel local dev — isolated WP instances per worktree - #129
Draft
dmytrobez wants to merge 3 commits into
Draft
chore: parallel local dev — isolated WP instances per worktree#129dmytrobez wants to merge 3 commits into
dmytrobez wants to merge 3 commits into
Conversation
dmytrobez
force-pushed
the
chore/parallel-local-dev
branch
2 times, most recently
from
August 3, 2026 15:34
195fbee to
42868aa
Compare
Infrastructure to run several isolated local WordPress instances in parallel (one per git worktree), so multiple people/agents can work side by side without port, database or container-name conflicts. - docker-compose.yml parameterized via env (PROJECT_CODE, WP_PORT, DB_PORT, WORDPRESS_URL, NEXT_URL); WP_HOME/WP_SITEURL follow the instance URL so a seeded DB works on any port. All vars are defaulted, so the default single-instance workflow is unchanged (:80, :3306, spck). - scripts/create-worktree.sh — detached worktree at ../<repo>-wtN plus a copy-on-write clone of the heavy gitignored dirs, then per-instance setup. - scripts/setup-instance.sh — generates per-instance wordpress/.env, wp-cli.local.yml and next/.env with free-port detection, and reports when a busy port pushes an instance off its nominal slot. - wordpress/scripts/db-snapshot.sh / db-seed.sh — copy DB + uploads from another checkout, rewriting the source URL. Both refuse to touch a container owned by a different checkout, since PROJECT_CODE falls back to "spck" and every superstack-derived project shares that default. db-seed.sh resets next_url outright: a bare "http://localhost" source URL is a prefix of "http://localhost:3000", so search-replace cannot rewrite it without mangling the port. - wordpress/server/mu-plugins/dev-autologin.php — local-only auto-login, gated on DEV_AUTOLOGIN_SECRET (only set by local compose, so it is inert on staging/production). wordpress/.env is no longer tracked: setup-instance.sh writes it per instance, so keeping it in git meant every worktree carried a modified file holding a random auto-login secret. It is replaced by wordpress/.env.example, matching how next/.env is already handled, and docker-compose.yml now defaults THEME_NAME so compose still resolves without an .env file. start-wp.sh loads wordpress/.env when present; the existing compose environment already carries the vars provision.sh reads, so provisioning is unchanged for the default workflow.
dmytrobez
force-pushed
the
chore/parallel-local-dev
branch
from
August 3, 2026 15:35
42868aa to
1a486f6
Compare
find_free_port only saw ports with a live listener. Instances are configured first and booted later, so a sibling that was set up but not yet started looked free and its port got handed out a second time — the second instance then failed to bind. Ports already written to a sibling wordpress/.env now count as taken.
… machinery Safety: - start-wp.sh had no ownership check. Two checkouts defaulting to PROJECT_CODE=spck share a compose project identity, so `docker compose up` in the second recreates the first's running containers instead of clashing. The guard existed but was wired only into the database scripts. - The guard failed open when the compose ownership label was absent, and compared against `pwd` rather than `pwd -P` while Docker stores the symlink-resolved path. - db-seed.sh replaced the WordPress URL before the Next.js one. A bare "http://localhost" source URL is a prefix of "http://localhost:3000", so every stored frontend URL was left with a dangling port. Only next_url was repaired afterwards; nav items, redirects and ACF fields were not. - db-seed.sh resolved a relative source path against its own directory, and swallowed search-replace failures before printing unqualified success. - create-worktree.sh invoked setup-instance.sh from the checked-out ref, which does not exist on any ref predating this tooling — the documented `create-worktree.sh 2 origin/main` copied gigabytes and then died. It now fails before the copy and removes the worktree it created. Simplification: - Ports are derived from the instance number instead of searched for. The search could hand the same port to two instances configured before either booted, its sibling scan missed worktrees not adjacent to the repo root while reading unrelated projects' .env files, and a missing lsof read as "free". Instance N is now always 808N/3306N/310N, or setup fails. - db-snapshot.sh is gone; db-seed.sh exports from the source directly. The intermediate .data/snapshot.sql only existed to be consumed, and being truncated on a failed export could destroy a good dump. - Dropped theme/static from the copy list: shipping one branch's compiled assets into another branch's worktree renders a site that looks correct and is not. Dropped theme/vendors, which does not exist. - provision.sh set next_url twice; neither block fired locally before this branch passed NEXT_URL through. - Trimmed the guide and linked it from the README.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Infrastructure to run several isolated local WordPress instances in parallel — one per git worktree — so multiple people (or agents) can work side by side without port / database / container-name conflicts.
wordpress/docker-compose.yml— parameterised via env (PROJECT_CODE,WP_PORT,DB_PORT,WORDPRESS_URL,NEXT_URL,DEV_AUTOLOGIN_SECRET). Each instance gets its own compose project, ports, database and containers;WP_HOME/WP_SITEURLfollow the instance URL so a seeded database works on any port. Every variable is defaulted — with no.env, behaviour is identical to today (:80,:3306, projectspck).scripts/create-worktree.sh <n> [ref]— creates a detached worktree../<repo>-wt<n>, copy-on-write clones the heavy gitignored dependency directories, then runs setup.scripts/setup-instance.sh <n>— generateswordpress/.env,wordpress/wp-cli.local.ymlandnext/.env. Instance N gets808N/3306N/310N, or fails if one is occupied.wordpress/scripts/db-seed.sh <source-checkout>— copies another instance's database and uploads into this one, rewriting stored URLs to this instance's ports.wordpress/scripts/instance-lib.sh— shared env loading and the container-ownership guard.wordpress/server/mu-plugins/dev-autologin.php— local-only auto-login helper.Guide:
docs/parallel-local-dev.md, linked from the README.Usage
Why it's safe on staging/production
DEV_AUTOLOGIN_SECRETis set — which only the localdocker-compose.ymldoes.Dockerfilecopies onlyserver/custom.iniandserver/wp-su.sh, and.github/actions/theme-deployrsyncs onlywordpress/theme/.docker-compose.yml/start-wp.shchanges are local-dev only and fully backward-compatible (defaulted env), so the standard single-instance workflow is untouched.Review
Three independent adversarial reviews (safety, simplification, shell/POSIX rigour) were run against the diff. All findings that survived verification are fixed:
Safety
start-wp.shhad no ownership check. Two checkouts defaulting toPROJECT_CODE=spckshare a compose project identity, sodocker compose upin the second recreates the first's running containers rather than clashing with them. The guard existed but was wired only into the database scripts. Verified against a real cross-project collision on a dev machine.pwdwhile Docker stores the symlink-resolved path (pwd -P).db-seed.shreplaced the WordPress URL before the Next.js one. A barehttp://localhostsource URL is a prefix ofhttp://localhost:3000, so every stored frontend URL was left with a dangling port (http://localhost:8083:3000). Onlynext_urlwas repaired afterwards — nav items, redirects and ACF fields were not.db-seed.shresolved a relative source path against its own directory, and swallowed search-replace failures behind|| truebefore printing unqualified success.create-worktree.shinvokedsetup-instance.shfrom the checked-out ref, which does not exist on any ref predating this branch — the documentedcreate-worktree.sh 2 origin/maincopied ~1.6 GB and then died, leaving a half-configured worktree. It now fails before the copy and removes what it created.Simplification
.envfiles; and a missinglsofwas indistinguishable from "port free". Instance N is now always808N/3306N/310N, or setup fails with the occupied port named.db-snapshot.shdeleted.db-seed.shexports from the source directly. The intermediate.data/snapshot.sqlexisted only to be consumed, and>truncated it before the export ran — a failed export destroyed a good dump.theme/staticno longer copied. It is a build output; one branch's compiled assets in another branch's worktree render a site that looks correct and is not.theme/vendorsdoesn't exist.provision.shsetnext_urltwice; neither block fired locally before this branch passedNEXT_URLthrough.Cleared on inspection: shellcheck clean under
-s sh(no bashisms;dashandshboth parse), the mu-plugin's production isolation traced through the Dockerfile and deploy workflow, guard ordering before every destructive operation, and.gitignorenegation for.env.example.Verified
Two instances side by side on a machine already running an unrelated
spckproject — three isolated stacks at once. Containers, ports and databases fully isolated; per-instance auto-login;wp @localresolving to the right container; each instance's headless redirect targeting its own Next.js port; a seed round-trip transferring content with URLs rewritten to the target and the source untouched.The ownership guard, the deterministic-port refusal, the
create-worktreeref guard and thedb-seederror paths were each re-verified against live state after the review fixes.Status
Draft. Two spots worth a reviewer's eye: the
.data/uploadsseeding path, and whether the auto-login mu-plugin belongs in the starter as-is.