A named volume is not a bind mount, and the permissions prove it

A named volume is initialised from the image, so it inherits ownership from whatever the image had. A bind mount is the host’s directory with the host’s ownership, and the container gets no say.

services:
  php:
    volumes:
      - ./src:/app/src           # bind mount: host uid, host permissions
      - vendor:/app/vendor       # named volume: seeded from the image
      - /app/storage/framework   # anonymous: fast, and discarded

volumes:
  vendor:

The named volume for vendor is the standard trick on macOS and Windows, where bind-mounted directories with tens of thousands of small files are slow enough to make the application unusable. It has a real cost: the volume is seeded once, so a dependency change requires removing it, and forgetting that produces a container running last week’s packages. Documenting the removal command next to the compose file saves the same twenty minutes repeatedly.