← Back to blog

How DDEV Keeps My Client Projects From Fighting Each Other

1 Oct 2026 · 4 min read

I've got projects on PHP 8.1 that a client won't let me touch until next year, projects on 8.4 that use property hooks everywhere, and one horrible legacy Laravel 8 app running on PHP 7.4 that I keep meaning to hand back but haven't yet. If I tried to run all of those directly on my Mac, I'd spend half my week fighting version managers instead of writing code.

That's the whole reason I moved to DDEV a few years back and never looked for anything else. It's a Docker-based local dev tool, and every project gets its own isolated environment: its own PHP version, its own database engine and version, its own cache layer, all defined in a config file that lives in the project repo.

What the config actually looks like

Every DDEV project has a .ddev/config.yaml file. For a current client SaaS build running on Laravel 12, it looks roughly like this:

php_version: "8.4"
webserver_type: nginx-fpm
database:
  type: mysql
  version: "8.0"

For the legacy app I mentioned, the config in that project's folder says php_version: "7.4" and type: mariadb, version: "10.3". Both projects can be running at the same time, on the same laptop, and neither one knows the other exists. I've stopped needing Herd's PHP version switcher entirely for anything beyond quick script testing, because DDEV handles it per-project automatically the moment I cd into the folder and run ddev start.

Matching it to production, not guessing at it

This is the bit that actually matters for client work. Most of my SaaS clients are on Laravel Forge or Vapor, and I always pull the exact PHP and database version straight from the server settings before I touch the local config. If Forge says the server's running PHP 8.3.11 and MySQL 8.0.35, I set php_version: "8.3" locally and match the database engine too. It sounds obvious written down, but I've been caught out before by a client whose production database was MariaDB 10.6 while I'd been happily developing against MySQL 8 locally. A query that worked fine on my machine threw a syntax error on their server because of a subtle difference in how the two handle a particular JSON function. That was an annoying afternoon, and it's exactly the kind of thing DDEV's per-project config is meant to stop happening again.

The extras I add to nearly every project

Out of the box DDEV gives you the web server, PHP, and database. For SaaS apps I usually need more, so I add services through DDEV's add-on system or a docker-compose override in .ddev/docker-compose.redis.yaml. Redis for queues and cache, Mailpit for catching outgoing email during testing, sometimes Meilisearch if the project's using Scout. Running ddev get ddev/ddev-redis takes about ten seconds and the container's just there, sat alongside PHP and MySQL, no separate install needed.

Xdebug is a toggle rather than a permanent fixture. It slows things down noticeably when it's on, so I leave it off by default and flip it with ddev xdebug on only when I'm actually stepping through something tricky, then ddev xdebug off straight after. I forget to turn it off more often than I'd like to admit, and then wonder why a page that normally loads instantly is taking three seconds.

Where it's been a bit of a pain

I won't pretend it's been entirely smooth. On an older Intel MacBook I used to run, Docker Desktop would happily eat 6 or 7GB of RAM just sitting idle, and file syncing between the Mac filesystem and the containers was slow enough that a full page load sometimes took a couple of seconds longer than it should. Switching to Mutagen-based syncing, which DDEV supports natively now, fixed most of that. On my current M2 machine it's genuinely not an issue at all, projects start in five or six seconds and stay fast.

The other rough edge is ddev share, which tunnels your local project out to a public URL for showing a client something before it's deployed. It works well but occasionally needs a restart if the tunnel drops mid-demo, which is not a great moment when you're on a call with someone paying you by the hour.

Where I'd point someone starting out

If you're juggling more than two or three Laravel projects with different version requirements, it's worth the half day it takes to get DDEV set up properly. Start by copying your production PHP and database versions into the config rather than defaulting to whatever's newest. That one habit has saved me more debugging time than almost anything else in my workflow.