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 pull
# 6. Open it.
cwp open
The first pull on an image-heavy site takes about two minutes, because uploads
are proxied rather than copied — a missing file below /wp-content/uploads/
is fetched from the remote on demand. cwp pull --uploads sync copies them
instead, and costs what copying them costs.
What just happened
Step 5 is the one worth reading. In order: the local database was snapshotted first, the remote database was exported and brought down, imported, rewritten from the remote URL to the local one, then scrubbed — and the mail guard was installed whether or not scrubbing was switched off.
That last clause is a rule rather than a default. A pulled production database contains real customer addresses and can be tricked into sending mail; the guard intercepts and logs outbound mail instead of sending it.
Check the setup
cwp doctor is read-only and every failure carries the
command that fixes it. On a project whose environment does not exist yet, it
fails — which is the state a reader meets first, so it is the state recorded
here:
$ cwp doctor
✓ Node ≥ 22 — Node 26.5.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 — dev
✓ Cloudron logged in — authenticated
✗ Remote app reachable — "example.com" not in cloudron list — fix: check the cloudron_app value in .cwp.yml
! Remote wp responds — skipped — provider did not pass
! PHP parity — skipped — provider did not pass
✗ WordPress core — not installed in "public" — fix: ddev wp core download
! DDEV project running — not started — fix: ddev start
✓ Uploads fallback — nginx snippet
! Uploads fallback serves a missing file — skipped — found no attachment whose file is absent locally — 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
! Bricks abilities (remote) — skipped — the environment is not reachable
! Custom post type drift — skipped — the local post types could not be listed — fix: ddev start
✗ 2 checks 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 — that is what
cwp db is for.
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.