Setting Up DDEV for Craft CMS Local Development
I've tried every local development tool out there for Craft. MAMP, Valet, Homestead, Nitro (RIP), and plain Docker. DDEV is the one that stuck. It's fast to set up, it works the same on Mac, Windows, and Linux, and it hasn't broken on me during an OS update. That last point matters more than you'd think.
This is the guide I send to developers when they join one of my projects. Everything you need to go from zero to a running Craft site in about 15 minutes.
Why DDEV
DDEV is a Docker-based local development tool built specifically for PHP projects. It wraps Docker in a simple CLI so you don't have to write Docker Compose files or understand container networking. You run a few commands and you've got PHP, a database, and a web server all configured and talking to each other.
A few things I like about it for Craft specifically:
- It has a
craftcmsproject type that pre-configures everything Craft needs - Each project is fully isolated. Different PHP versions, different database versions, no conflicts
- Database imports and exports are one command
- HTTPS works locally out of the box (no more self-signed cert warnings)
- It's actively maintained and the community is responsive
Installing DDEV
First, you need Docker. If you're on Mac, install OrbStack or Docker Desktop. OrbStack is faster and uses less memory, so that's what I'd recommend. On Windows, Docker Desktop with WSL2 backend. On Linux, regular Docker.
Then install DDEV:
brew install ddev/ddev/ddev
choco install ddev
curl -fsSL https://ddev.com/install.sh | bash
Verify it's working:
ddev version
Setting Up a New Craft Project
Here's the fastest path from nothing to a running Craft site:
# Create project directory and move into it
mkdir my-craft-site && cd my-craft-site
# Configure DDEV for Craft
ddev config --project-type=craftcms --docroot=web --php-version=8.2
# Start the containers
ddev start
# Install Craft via Composer
ddev composer create craftcms/craft
# Run the Craft installer
ddev craft setup
That's it. After the setup wizard finishes, your site is running at https://my-craft-site.ddev.site. The control panel is at /admin.
Let's break down what each command does:
ddev configcreates a.ddev/config.yamlfile with your project settings. The--project-type=craftcmsflag tells DDEV to set Craft-appropriate defaults (like the Nginx config and PHP settings).ddev startpulls the Docker images and starts the containers. First run takes a minute or two. After that it's fast.ddev composer createrunscomposer create-projectinside the container, which downloads Craft and all its dependencies.ddev craft setupruns Craft's interactive installer, which creates the database tables and sets up your admin account.
.env for the database settings. DDEV sets the CRAFT_DB_SERVER, CRAFT_DB_USER, CRAFT_DB_PASSWORD, and CRAFT_DB_DATABASE environment variables inside the container.
Setting Up an Existing Project
If you're cloning an existing Craft project (which is the more common scenario), the process is slightly different:
# Clone the repo
git clone git@github.com:your-org/your-craft-site.git
cd your-craft-site
# Configure DDEV (if .ddev/config.yaml isn't in the repo)
ddev config --project-type=craftcms --docroot=web --php-version=8.2
# Start containers
ddev start
# Install PHP dependencies
ddev composer install
# Import the database
ddev import-db --file=path/to/database-dump.sql
# Copy the .env file
cp .env.example .env.ddev
# Run any pending migrations
ddev craft up
.ddev/config.yaml to the repo. That way, any developer who clones the project just runs ddev start and they've got an identical environment. No "it works on my machine" problems.
The .ddev/config.yaml File
Here's what a typical DDEV config looks like for a Craft project:
name: my-craft-site
type: craftcms
docroot: web
php_version: "8.2"
database:
type: mysql
version: "8.0"
webserver_type: nginx-fpm
# Expose Vite's dev server port
web_extra_exposed_ports:
- name: vite
container_port: 5173
http_port: 5172
https_port: 5173
# Node.js version for front-end builds
nodejs_version: "20"
# Additional PHP extensions if needed
# webimage_extra_packages: [php8.2-imagick]
The web_extra_exposed_ports section is important if you're using Vite (which I covered in a previous post). It exposes Vite's HMR port so your browser can connect to the dev server running inside the container.
Daily Commands You'll Actually Use
You don't need to memorize a huge list. These are the ones I run every day:
# Start your project (do this first thing in the morning)
ddev start
# Stop your project (end of day or switching projects)
ddev stop
# Run Craft CLI commands
ddev craft clear-caches/all
ddev craft project-config/apply
ddev craft up
# Run Composer commands
ddev composer require some/package
ddev composer update
# Run npm commands
ddev npm install
ddev npm run dev
ddev npm run build
# Open the site in your browser
ddev launch
# Open the Craft control panel
ddev launch /admin
# SSH into the container (for anything else)
ddev ssh
The pattern is simple: prefix any command with ddev and it runs inside the container. ddev composer, ddev npm, ddev craft. You can also run ddev ssh to get a shell inside the container and run commands directly.
Database Management
This is where DDEV really shines compared to managing databases manually.
# Export the database (creates a gzipped SQL file)
ddev export-db --file=db-backup.sql.gz
# Import a database dump
ddev import-db --file=db-backup.sql.gz
# Open a MySQL/Postgres CLI
ddev mysql
# or
ddev psql
# Use a GUI? Launch phpMyAdmin or Adminer
ddev launch -p
The import-db command handles both plain .sql files and gzipped .sql.gz files automatically. It also handles dropping and recreating the database, so you always get a clean import.
Integrating Vite with DDEV
If you're using Vite for your front-end build (and you should be), you'll want to run the Vite dev server inside DDEV so that hot module replacement works correctly.
First, make sure your .ddev/config.yaml exposes port 5173 (shown in the config above). Then start Vite inside the container:
# Install Node dependencies
ddev npm install
# Start Vite dev server
ddev npm run dev
Your Vite config needs to know about the DDEV hostname. Update vite.config.js:
export default defineConfig({
// ... other config
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
origin: 'https://my-craft-site.ddev.site:5173',
},
})
And update the Craft Vite plugin config to point to the DDEV hostname:
return [
'devServerPublic' => 'https://my-craft-site.ddev.site:5173',
'useDevServer' => App::env('CRAFT_ENVIRONMENT') === 'dev',
// ... rest of config
];
Setting Up Xdebug
DDEV includes Xdebug but has it disabled by default (because it slows PHP down). Toggle it on when you need it:
# Enable Xdebug
ddev xdebug on
# Disable when done
ddev xdebug off
# Check status
ddev xdebug status
In PhpStorm, DDEV's Xdebug is pre-configured to connect on port 9003. Just set a breakpoint, enable "Start Listening for PHP Debug Connections" in PhpStorm, reload your page, and you're debugging.
In VS Code, install the PHP Debug extension and add this launch configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
}
]
}
Working with Multiple Projects
DDEV handles multiple projects without conflict. Each project gets its own containers, its own database, and its own hostname. You can run as many projects simultaneously as your machine's memory allows.
# See all running DDEV projects
ddev list
# Start a specific project (run from that project's directory)
cd ~/Sites/client-a && ddev start
cd ~/Sites/client-b && ddev start
# Stop everything at once
ddev poweroff
I usually have 2-3 projects running at the same time. On a Mac with 16GB of RAM, that's comfortable. If you're running more than that or your machine is struggling, ddev stop on projects you're not actively working on.
Email Testing
DDEV includes Mailpit, a local email catcher. Any email that Craft sends (password resets, contact form notifications, etc.) gets caught by Mailpit instead of actually being sent.
# Open Mailpit in your browser
ddev launch -m
This opens a web UI where you can see all the emails your Craft site has sent. Super useful for testing email notifications without bothering real people or worrying about deliverability.
Custom DDEV Commands
You can create project-specific commands that any developer can use. I usually add a few to every Craft project:
#!/bin/bash
## Description: Pull production database and import locally
## Usage: pull-db
## Example: ddev pull-db
echo "Pulling production database..."
ssh forge@your-server.com "cd /home/forge/your-site.com && php craft db/backup --zip"
scp forge@your-server.com:/home/forge/your-site.com/storage/backups/latest.sql.gz /tmp/prod-db.sql.gz
ddev import-db --file=/tmp/prod-db.sql.gz
ddev craft clear-caches/all
echo "Done! Database imported and caches cleared."
# Make it executable
chmod +x .ddev/commands/web/pull-db
# Now any developer can run:
ddev pull-db
Custom commands live in .ddev/commands/ and get committed to Git. The whole team benefits from shared tooling without anyone having to remember long command sequences.
Troubleshooting
DDEV Won't Start
Usually a Docker issue. Try:
# Restart DDEV's internal services
ddev poweroff && ddev start
# If that doesn't work, restart Docker itself
# Then try ddev start again
Port Conflicts
If something else is using port 80 or 443 (like Apache or another local server), DDEV will fail to start. You can either stop the conflicting service or configure DDEV to use different ports in .ddev/config.yaml:
router_http_port: "8080"
router_https_port: "8443"
Slow on Mac
If Docker Desktop feels sluggish on Mac, switch to OrbStack. It uses less CPU and memory and file I/O is noticeably faster. DDEV works with either one out of the box.
DDEV has become such a natural part of my workflow that I barely think about it anymore. It starts fast, it doesn't break, and it keeps every project isolated. If you're still running MAMP or managing a local PHP installation by hand, give DDEV a try. The initial setup takes 15 minutes and it'll save you hours of environment debugging over the life of a project.
Running into DDEV issues on a Craft project, or need help setting up a team development workflow? I'm happy to help.