A named volume is managed by Docker and has no ownership relationship with the host, which removes the permission problem entirely for directories nobody edits.
services:
php:
volumes:
- .:/app # source: bind, editable
- vendor:/app/vendor # named: not editable
- node_modules:/app/node_modules
- storage:/app/storage/framework
volumes:
vendor: {}
node_modules: {}
storage: {}
Masking vendor and node_modules with named volumes is also a large performance win on macOS, where a bind mount of a hundred thousand small files is the slowest thing in the stack. The cost is that an editor on the host can no longer navigate into a dependency, which matters more than expected when debugging — a second, read-only bind mount at a different path is the usual compromise.