Before a build starts, the whole context directory is sent to the daemon. Without a .dockerignore that includes .git, vendor and node_modules, that is several hundred megabytes copied before the first instruction runs.
.git
.gitignore
node_modules
vendor
storage/logs
*.sql
*.log
.env
docker-compose*.yml
Dockerfile*
Besides the time there is a correctness reason: COPY . /app bakes .env and the git history into the image, and both then travel wherever that image goes. The syntax resembles .gitignore and is not identical — negation with ! behaves differently and there is no per-directory file. Watch the “Sending build context” line; if it is large, this is why.