Local WordPress environments that behave like products, not chores.
Copy the folder. Rename it. Run one command. Get:
- a readable local hostname based on the folder name
- isolated containers, volumes, and ports
- WordPress installed automatically
- latest WordPress core on the configured PHP line
- optional package ZIP installs from
packages/ - a shared proxy for clean per-project URLs across many projects
Choose the mode first:
.\up.ps1Fast mode. Best performance.wp-contentstays volume-backed. Use this for smoke tests, imported client copies, QA, demos, and general non-editable runs..\up.ps1 -WithMountsDev mode. Editable bind mounts forplugins,mu-plugins, andthemes. Use this only when you need live file edits from the host.
If you are on Windows and want editable dev mode without a performance hit, keep the repo in WSL2 ext4 storage and open it through Remote WSL.
- one command bootstraps WordPress, DB, ports, hostname, and optional proxy
- copied folders become isolated stacks automatically
- fast mode avoids bind mounts in the web request path
- dev mode is explicit instead of being silently slower all the time
- package ZIPs can be dropped into
packages/and installed on boot
Most WordPress Docker setups are good at starting one site once.
They are bad at:
- spinning up many parallel client environments
- surviving folder copies and renames without manual cleanup
- staying understandable for normal developers
- keeping the repo publishable instead of turning it into a pile of local-only hacks
PressYard is designed for the copy-heavy reality of WordPress work:
- new client sandbox
- plugin smoke test
- staging clone
- persistent theme/plugin development
Open PowerShell as Administrator, then from the repo root run:
.\doctor.ps1
.\up.ps1That default path is fast mode:
- volume-backed
wp-content - no editable bind mounts in the web request path
- best local performance for smoke tests, snapshots, and disposable environments
Use dev mode only when you need live file editing from the host:
.\up.ps1 -WithMountsOr use one of the optional dev profiles:
.\up.ps1 -WithTools
.\up.ps1 -WithMail
.\up.ps1 -WithXdebug
.\up.ps1 -WithTools -WithMail -WithXdebug
.\up.ps1 -WithMounts -WithTools -WithMail -WithXdebugThat will:
- derive the stack name from the folder name
- generate
.env - choose free direct ports
- map
<project>.localhostinto your hosts file - start WordPress
- start the shared proxy
- print the full installation path and live URLs
If you skip the elevated terminal, use .\up.ps1 -WithProxy:$false and work on the printed direct port instead.
In that mode, .\open.ps1 also falls back to the direct URL automatically.
If your folder is named project1, PressYard will try to use:
COMPOSE_PROJECT_NAME=project1- containers like
project1-wordpress-1 - volumes like
project1_wp_data http://project1.localhosthttp://db-project1.localhostwith Adminer
If project1 is already owned by another live stack on your machine, PressYard falls back to:
project1-<hash>
That fallback applies to the internal Docker project namespace only when a live stack already owns the clean name. The public browser hostname stays tied to the folder name whenever that hostname is available.
.\up.ps1
.\up.ps1 -WithMounts
.\up.ps1 -WithTools
.\up.ps1 -WithMail
.\up.ps1 -WithXdebug
.\down.ps1
.\down.ps1 -Volumes
.\wp.ps1 plugin list
.\logs.ps1 wordpress -Follow
.\open.ps1
.\open.ps1 -Adminer
.\open.ps1 -Mailpit
.\reset.ps1 -WithTools
.\doctor.ps1
.\export-db.ps1
.\import-db.ps1 .\backups\snapshot.sqlPressYard ships with two explicit filesystem modes.
Default fast mode:
wp-contentlives in Docker volumes- repo-owned
wp-content/plugins,mu-plugins, andthemesare seeded into the runtime volume on boot uploadsstays volume-backed- best performance on Windows and Docker Desktop
Optional dev mode with .\up.ps1 -WithMounts:
- bind-mounts
wp-content/plugins - bind-mounts
wp-content/mu-plugins - bind-mounts
wp-content/themes - keeps
uploadsvolume-backed by default
Docker volumes are always used for:
- WordPress core/runtime
- MariaDB data
- uploads
- init/package state
PressYard also keeps WordPress runtime update directories writable so core, plugin, and theme updates from wp-admin do not fail on container permissions.
On Linux and macOS, PressYard maps the web runtime to the current host UID/GID by default.
That keeps wp-admin edits, plugin/theme updates, uploads, and generated assets writable without leaving files owned by root or www-data.
Related knobs in .env:
AUTO_RUNTIME_UID_GIDPRESSYARD_RUNTIME_UIDPRESSYARD_RUNTIME_GIDPRESSYARD_FILE_UMASK
That split keeps the default path fast while still allowing a deliberate editable mode when the task needs it.
Drop plugin or theme ZIPs into:
packages/
On first boot, PressYard will attempt:
- plugin install
- theme install
If the ZIP set has not changed, later boots skip reinstall.
packages/ is ignored by Git on purpose so personal/commercial plugin bundles stay local.
Optional profiles are intentionally off by default so the fastest path stays fast.
-WithToolsstarts Adminer-WithMailstarts Mailpit and routeswp_mail()into the local inbox automatically-WithXdebugswaps the WordPress web container to the Xdebug-enabled image-WithMountsenables editable bind mounts for theme/plugin development
You can also persist them in .env:
ENABLE_MAILPIT=trueENABLE_XDEBUG=true
By default, .\up.ps1 starts a shared Traefik instance on:
127.0.0.1:80- dashboard on
127.0.0.1:8089
Why port 80 by default:
- it gives the cleanest possible local URL
- subdomain routing keeps projects unique without extra ports
If you do not want the proxy:
.\up.ps1 -WithProxy:$falseYou still get the direct published WordPress port.
The proxy is global across all project copies on the machine.
Its shared Compose project name defaults to pressyard-proxy.
When Mailpit is enabled, it also gets a clean proxy URL:
http://mail-<project>.localhost
Subdomains of localhost are not resolved consistently across dev machines, especially on Windows.
PressYard handles that by managing explicit hosts-file entries for:
<project>.localhostdb-<project>.localhostwhen Adminer is enabled
If PowerShell is not running with permission to update the hosts file, .\up.ps1 will stop and tell the user to rerun it in an elevated terminal.
If you do not want to run an elevated terminal, use:
.\up.ps1 -WithProxy:$falseThat skips the proxy and still gives a working direct URL on http://localhost:<port>.
Primary target:
- Windows + PowerShell + Docker Desktop
Also supported in principle:
- macOS + PowerShell 7 + Docker Desktop
- Linux + PowerShell 7 + Docker Engine + Compose plugin
The repo intentionally uses PowerShell as the automation layer across platforms instead of maintaining parallel shell implementations.
For the best editable dev throughput on Windows, keep the repo in WSL2 ext4 storage and open it through Remote WSL.
Mailpit:
- direct URL:
http://127.0.0.1:<MAILPIT_PUBLISHED_PORT> - proxy URL:
http://mail-<project>.localhost - WordPress mail is routed there automatically through a must-use plugin
Xdebug:
- enabled with
.\up.ps1 -WithXdebugorENABLE_XDEBUG=true - uses
host.docker.internalby default - defaults:
- mode:
debug,develop - port:
9003 - IDE key:
VSCODE
- mode:
This repo is set up to be publishable:
MITlicensed- personal ZIP bundles ignored
- root ZIPs ignored
- line endings normalized through
.gitattributes - smoke workflow included in
.github/workflows/smoke.yml
- docker-compose.yml
- docker-compose.mounts.yml
- docker-compose.proxy.yml
- up.ps1
- doctor.ps1
- scripts/bootstrap-env.ps1
- scripts/init-wordpress.sh
- MANUAL.md
Before pushing:
- replace placeholder secrets in your local
.envgenerated from.env.example - confirm
packages/contains only local ignored ZIPs you actually want on your machine - decide your final GitHub repo name