Local development

A real copy of production, minus the parts you should not have.

The local database is disposable and the tool rests on that. What it must never be is a second, unsupervised copy of your customers' data — or an 8 GB download.

Full media, without downloading it

Uploads stay where they are. A missing file falls through to the remote at the web-server level, which is the only layer that sees every request — the attachment API is not the only thing that asks for a file.

  1. The browser asks the local site

    GET /wp-content/uploads/2026/portrait.jpg — a plain request to the DDEV container.

  2. nginx looks on disk first

    location ^~ /wp-content/uploads/ with try_files $uri @cwp-uploads-remote. If the file is there, it is served and nothing else happens.

  3. Otherwise it falls through to the remote

    The named location proxies to the environment's URL, carrying the remote Host header — and no cookies.

  4. The response comes back without its session

    Any Set-Cookie header is dropped. The bytes arrive; the session does not.

It fetches files. It does not share a session.

^~ is load-bearing. A regex location beats a plain prefix match, and DDEV's WordPress configuration matches static assets by extension — so without it the fallback would apply to everything except the image and font requests it exists for.

Not a mu-plugin, which was the original design and was wrong. A PHP filter can only rewrite URLs WordPress itself emits — the attachment API. A .woff2 requested from an @font-face rule, or a background image stored as a URL string inside an element's settings, never passes through it.

The config file is committed, not gitignored: a clone has to serve uploads the same way without cwp installed. It takes effect after ddev restart.

Production data, without personal data

Every pull can scrub, and a pull installs the mail guard whether it scrubs or not. The two are separate on purpose: one protects the people in the database, the other protects them from your local site.

wp_users, as pulled

user_loginuser_emaildisplay_name
a.hartmanna.hartmann@example.comAnke Hartmann
editor_02redaktion@example.comRedaktion
shop_kunde_881m.***@example.comM. B.

after cwp scrub

user_loginuser_emaildisplay_name
a.hartmanna.hartmann@example.invalidUser 1
editor_02editor-02@example.invalidUser 2
shop_kunde_881user-881@example.invalidUser 3

Illustrative rows. Nothing on this page comes from anyone's database — which is rather the point of the feature.