Uploads strategies
shipped 1.0.0Set the default in cwp.yml under pull.uploads; override per run with
cwp media pull --uploads <mode>.
| Mode | Behaviour | When |
|---|---|---|
proxy | Default. No transfer. Anything below /wp-content/uploads/ that is missing locally is served from the remote. | Day to day. Turns a twenty-minute first setup into two minutes. |
sync | A full copy of the remote uploads directory. Correct, slow, large. | When you need every asset locally: offline work, media edits. |
skip | Nothing. Images are broken locally. | Database-only work where the media does not matter. |
How proxy works
cwp init writes .ddev/nginx/cwp-uploads-fallback.conf
and every cwp db pull keeps it pointing at the environment
you pulled from. nginx serves the local file when it exists and proxies to the
remote when it does not.
It is nginx configuration, so it needs ddev restart. cwp says so whenever
it writes or removes the file. The file belongs in git, not in .gitignore: a
colleague who clones the project and runs ddev start gets the same behaviour
without installing cwp.
Where nginx is not available, cwp falls back to a generated mu-plugin:
webserver_type: apache-fpm, or a project whose .ddev/ cwp did not scaffold.
A PHP filter can only rewrite URLs WordPress itself emits, so custom fonts,
generated CSS and background images stored as URL strings 404 under it (B-019).
In that case
cwp db pull and cwp doctor both say which mechanism is
running and what it does not cover. There is no silent downgrade to partial
coverage.
cwp doctor verifies the mechanism rather than counting files: it requests one
uploads path your database knows and your filesystem does not, and expects a 200.
Under proxy almost every file is missing locally by design, so “there are
attachments without files” says nothing. A fallback that exists but does not
work looks exactly like one that does.
What proxy means for a builder that checks the disk
The fallback serves files to a browser. Code that asks the filesystem still
finds nothing: is_file() is false for every proxied attachment, whatever the
browser gets.
Bricks 2.4 asks before it renders. Media_Browser_Health calls
get_attached_file() and tests it. An element whose file it cannot find draws a
placeholder, and on the front end that renders nothing at all. Measured on a
proxied install: a listing page showed 72 cards and 6 images, the six whose files
happened to be on disk. The site logo was gone. Nothing on the page said why.
So the pull writes down what the remote holds, into a generated mu-plugin beside the fallback. The builder’s own filter answers from that list:
✓ uploads: known 1,024 file(s) recorded, so the builder can tell a proxied
file from a missing one
It is a membership test, taken once: no request per attachment, and no cache to invalidate. The two answers stay apart:
| Case | What is true | What the builder is told |
|---|---|---|
| the remote has it, the fallback would serve it | the proxy works | unknown |
| the file is nowhere: not local, not remote | genuinely missing | missing, unchanged |
A file that is gone everywhere still draws its placeholder. That is a broken reference, and the check exists to surface it.
The list ages. An attachment uploaded on the remote after your last pull is
not in it and renders as missing until you pull again. cwp doctor says when the
pull took it and from where:
✓ Uploads the builder can see 1,024 file(s), recorded 2026-08-28 from dev —
an upload made there since is not in it
Without a list, the builder checks the disk as it always has. When cwp cannot read the far side it writes none, and says so. A list that guesses would hide exactly the failure the builder’s check is for.
What proxy means for a Bricks export
A tool that reads the disk finds nothing there either, and Bricks’ design-system export is one: it embeds custom font binaries by reading them off the local filesystem.
cwp pull handles the design system’s own binaries
itself. It fetches the few files the design system points at, then exports
again. A proxied workspace therefore does not need sync to produce a complete
tree. Where it cannot (no
environment stands out, or --no-assets), it names each font it could not carry
and cwp push refuses to send it.
To fetch media by hand, without the database:
cwp media pull dev --uploads sync
Privacy
proxy keeps the local copy coupled to production, and coupled widely:
everything below uploads/ is one request
away, not only the images WordPress renders. A scrubbed database plus a proxied
uploads directory is a local copy with the personal data removed from the
database and the production files still live.
sync and skip remove a fallback left over from an earlier pull rather
than leaving it active. Use one of them when you need full separation from
production.