The image that was 40 MB smaller after one COPY was moved

A COPY . . before the dependency install, which had been putting the entire source tree into a layer that the vendor directory then sat on top of.

# before
COPY . /app
RUN composer install --no-dev

# after
COPY composer.json composer.lock /app/
RUN composer install --no-dev --no-scripts --no-autoloader
COPY . /app
RUN composer dump-autoload --classmap-authoritative --no-dev

The size difference is the .git directory and the test fixtures, which were being copied and then not removed because a later rm does not shrink an earlier layer. The build time difference is larger and is the usual reason to do this — a source change no longer reinstalls dependencies — and the forty megabytes was the part that made somebody look.