For a long time, my local PHP setup was whatever I could get working that week. MAMP when I started out. Laravel Valet when I went all-in on macOS. A brief, regrettable period with a hand-assembled Docker Compose file I cobbled together from three Stack Overflow answers.
Each of those tools worked, until it didn't. MAMP conflicted with system PHP. Valet broke after an
brew upgrade. The Docker file worked great until a teammate couldn't reproduce it on
Windows. Every project had different quirks, and none of that friction was the actual work.
I've been building PHP projects for clients and on government contracts for a while now. Drupal for large-scale federal sites. WordPress for smaller public-facing builds. The environments are different enough that keeping a single clean local setup was never quite possible. Until ddev.
What ddev Actually Is
ddev is a
Docker-based local development environment with a CLI that knows about PHP projects. It handles PHP
version management, database setup, Nginx or Apache config, SSL certificates, and CLI tools like
Drush, WP-CLI, and Composer. You configure it once per project with a simple YAML file that lives in
.ddev/ inside your repo.
The key thing is isolation. Every project gets its own containers. There's no global PHP version to manage, no Valet park directories, no port collisions. You can run a Drupal 10 site on PHP 8.3 and a WordPress site on PHP 8.1 at the same time, and neither knows the other exists.
The Moment It Clicked
I found ddev while trying to hand off a WordPress project. The other developer was on Windows. My Valet setup was not going to help them at all. I'd heard of ddev but kept filing it under "maybe later."
That afternoon I ran three commands:
ddev config --project-type=wordpress
ddev start
ddev launch
That was it. Browser opened to a running site, trusted HTTPS certificate in place, no terminal
errors, no configuration research. The other developer cloned the repo, ran ddev start,
and had the same environment running in under two minutes.
I went back and set up every active project that same evening.
The Commands I Actually Use
Most of my day-to-day with ddev comes down to a short list:
ddev startandddev stop— bring the project up and down. Fast.ddev launch— opens the project in a browser. Small thing, saves a step.ddev describe— shows all the running services, ports, and URLs for the current project. I use this constantly when I forget which port something is on.ddev ssh— drops into the web container shell. Useful when you need to run something that isn't wrapped by ddev's built-in commands.ddev composer,ddev drush,ddev wp— these proxy directly into the container. Noddev sshneeded for routine tasks.
The proxied commands are what make day-to-day workflow clean. Running ddev drush cr
feels identical to running drush cr directly. Same with
ddev wp post list or ddev composer require.
HTTPS Without the Warnings
Every ddev project gets a trusted local HTTPS certificate automatically, wired up through
mkcert.
The first time you run ddev start, it installs a local certificate authority on
your machine. Every subsequent project just works, no browser security warnings, no clicking
through "your connection is not private."
I didn't realize how much cognitive overhead those certificate warnings had added up to until they were gone. Testing form submissions, checking mixed-content issues, reviewing HTTPS-only cookie behavior. All of that is now one less thing.
Mailpit Is Included
Every ddev project also comes with Mailpit, a local mail catcher. PHP mail() and any SMTP-configured
plugin or module sends to Mailpit instead of the real world. You open it at
https://[project].ddev.site:8026, or run ddev launch -m to go right to
it.
No more "did that email actually send?" guesses during development. No accidentally sending test emails to real addresses. It just works, zero configuration.
WordPress and Drupal, Same Workflow
The thing I appreciate most is that the workflow is genuinely identical for both. On a Drupal
project the config line changes, and ddev drush is there instead of
ddev wp, but the shape of the day is exactly the same.
# WordPress
ddev config --project-type=wordpress
ddev start
# Drupal
ddev config --project-type=drupal
ddev start
That consistency matters more than I expected. When you jump between project types regularly, not having to mentally context-switch your local tooling is a real reduction in friction.
Why I Stayed
The feature that sealed it for me was committing .ddev/ to the repo. The project's
local environment becomes part of the codebase. A new developer clones the repo, runs
ddev start, and has PHP version, database settings, and all necessary services exactly
as configured. No setup docs to follow. No "works on my machine."
For client projects and especially for government work where contractors rotate on and off projects, this is not a small thing. Onboarding time for the local environment goes from an afternoon of troubleshooting to a single command.
ddev also has a well-maintained set of add-ons for things like Solr, Redis, Elasticsearch, and Varnish. I haven't needed most of them yet, but it's the kind of thing that means you don't outgrow the tool as projects get more complex.
If you're spending time managing local PHP environments instead of building things, it's worth an afternoon. I haven't thought about my local setup since.