Every 'works on my machine' bug I have debugged in the last five years traces back to environment drift: different PHP versions, missing extensions, MySQL behaving differently on someone's laptop. Docker ends that conversation. Here is the Compose setup I roll out to teams.
The services
- app: PHP-FPM image with the exact extensions production uses, plus Composer and Xdebug (toggled by env)
- web: Nginx with the same vhost config as production
- mysql / pgsql: pinned to the production major version, data in a named volume
- redis: cache, sessions, queues — identical to prod topology
- mailpit: catches every outgoing email with a web UI, so no test email ever reaches a real inbox
- worker: the same app image running queue:work, so background jobs behave like production
Why mirror production so closely?
Because the environment is part of your application. When local Nginx, PHP version, and Redis match the servers, a whole category of deploy-day surprises disappears. Your Dockerfile becomes living documentation of what the app needs to run.
Performance tips that matter
On Mac and Windows, bind-mount performance is the pain point. Keep vendor/ and node_modules/ in named volumes instead of the bind mount, enable VirtioFS on Docker Desktop, and your artisan commands go from sluggish to native-feeling. On Linux none of this is needed — it is simply fast.
One command to onboard
The payoff: a new developer runs git clone, cp .env.example .env, docker compose up, and they are productive in ten minutes with the entire stack — database seeded, mail catcher running, queues processing. That onboarding speed alone justifies the setup cost.

