Quickstart
shipped 1.0.0From nothing to a working local copy of a production site.
# 1. An empty project directory
mkdir -p ~/Code/example/example.com
cd ~/Code/example/example.com
# 2. Scaffold. Interactive: it asks for the project
# name, the host and the site-plugin directory.
cwp init
# 3. Start the container and put WordPress core in
# place. Core is gitignored, never in the repo.
ddev start
ddev wp core download
# 4. Fetch the themes and plugins the site needs
# in order to render.
cwp fetch
# 5. Pull the production database, proxying the
# uploads rather than copying them. Runs
# search-replace, scrubs personal data, installs
# the mail guard, runs the post-import steps.
cwp db pull
# 6. Open it.
cwp open
The first pull on an image-heavy site takes about two minutes. cwp proxies
uploads rather than copying them: it fetches a missing file below
/wp-content/uploads/ from the remote on demand. cwp media pull --uploads sync
copies them instead, and costs what copying them costs.
What happened
Step 5 is the one worth reading. In order: cwp snapshotted the local database first. It exported the remote database and brought it down. It imported it, rewrote it from the remote URL to the local one, then scrubbed it. And it installed the mail guard whether or not you switched scrubbing off.
That last step is a rule rather than a default. A pulled production database contains real customer addresses and can end up sending mail. The guard intercepts and logs outbound mail instead of sending it.
Check the setup
cwp doctor only reads, and every failure carries the
command that fixes it. On a project whose environment does not exist yet, it
fails. A reader meets that state first, so the transcript below records it:
$ cwp doctor
✓ Comparisons against a far side — not run — "local" is this project's own site
✓ cwp build — 1.0.0 (32598ac-dirty, built 2026-08-29 09:01)
✓ Node ≥ 22 — Node 26.7.0
✓ Docker runtime — reachable
✓ DDEV installed — ddev version v1.25.3
✓ mkcert CA — installed
✓ Cloudron CLI installed — 8.2.6
✓ Machine config — valid
✓ Project config — valid
✓ Environment — local
✓ Project layout — cwp.yml, cwp/
✓ Taxonomies are carried or scrubbed, not both — no overlap
✓ the host filters what cwp writes, as it does for anyone
✓ No `access:` entry names a read-only environment — no overlap
✓ Target project present — ~/Code/example/example.com
✗ WordPress core — not installed in "public" — fix: ddev wp core download
! DDEV project running — status: unhealthy — fix: ddev start
! Outbound mail is intercepted — skipped — could not ask the local site — fix: ddev start, then re-run cwp doctor
✓ Uploads fallback — nginx snippet
! Uploads the builder can see — no record of what the remote holds. The builder cannot tell a proxied file from a missing one, and images may render as placeholders — fix: cwp media pull
! Uploads fallback serves a missing file — skipped — the local media library has no attachments to ask about — fix: nothing to fix: with every file present there is nothing for the fallback to serve
✓ Bricks child theme — none configured, none installed. Bricks runs as the active theme
! Bricks readiness (local) — skipped — WP-CLI did not answer on local
! Custom post type drift — skipped — the local post types could not be listed — fix: ddev start
✗ 1 check failed
→ apply the fixes shown against the ✗ checks above
Captured from cwp 1.0.0, exit 1. Not written by hand.
Then take a baseline
cwp db snapshot baseline
Restoring costs five seconds; re-pulling costs minutes. Make it a reflex before
any experiment that touches the database. cwp db
exists for that.
Then adopt the environment
You now have a working copy of the site. What you do not have is a tree: the committed files that describe it, plus the relationship that lets cwp move state between the two.
cwp adopt dev
It writes a marker on the site and a ref in your repository, so both sides agree which project manages this environment. It derives the scope from what the site registers. It reads the tree down for the first time. Nothing crosses between a tree and an environment nobody has adopted.
The steps above deliberately do not need it. cwp db pull, cwp media pull
and cwp fetch bring data and code down without a tree. The quickstart
therefore gets you a running site before asking you to commit to anything.
git diff
The tree exists for this: what the site holds, as files you can read.
Where to go next
- Environments and the two-layer config: what
cwp.ymlholds, and what it deliberately does not. - Asymmetric data flow: why
pullandpushare not mirror images. - The safety model: the six rules, and the tests that hold them.
- The build loop: the six steps you repeat once the tree exists.