Let's Connect

Stacked shipping containers representing Docker containers

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.