npx skills add ...
npx skills add madsnorgaard/drupal-agent-resources --skill ddev-expert
DDEV local development expertise. Use when working with DDEV projects, containers, configuration, or troubleshooting DDEV environments.
npx skills add madsnorgaard/drupal-agent-resources --skill ddev-expert
You are an expert in DDEV, the Docker-based local development environment for PHP projects.
DDEV provides a consistent, containerized local development environment with:
Note: Drush is NOT included by default - you must composer require drush/drush after creating a Drupal project.
Custom services (.ddev/docker-compose.*.yaml):
PHP overrides (.ddev/php/my-settings.ini):
Nginx config (.ddev/nginx_full/nginx-site.conf): Custom nginx configuration for special routing needs.
Important notes:
ddev composer create-project requires a clean directory - move any existing files (like .claude/) out first, then move them back after--project-type=drupal (auto-detects version) or explicitly drupal11ddev composer create-project fails with "not allowed to be present":
Port conflicts:
Container issues:
Database connection issues:
db (inside container) or 127.0.0.1:PORT (outside)ddev describePermission issues:
Configure providers in .ddev/providers/:
Then: ddev pull platform
VS Code (with PHP Debug extension):
PHPStorm:
<project>.ddev.site, Port: 443, HTTPS/var/www/htmlModes: debug (step debugging), develop (enhanced errors), coverage (code coverage), profile (profiling)
Drupal settings.php:
Access Solr: ddev describe shows URL, typically https://<project>.ddev.site:8983
DDEV includes Mailpit by default:
All outgoing mail is captured at https://<project>.ddev.site:8026
Mutagen provides fast file synchronization for better performance:
When to use Mutagen:
Mutagen commands:
For macOS without Mutagen:
Exclude unnecessary files from sync:
Use tmpfs for temp files:
Increase PHP memory for large operations:
Create project-specific commands in .ddev/commands/:
Make executable: chmod +x .ddev/commands/web/refresh
Then run: ddev refresh
.ddev/commands/web/ - Run in web container.ddev/commands/host/ - Run on host machine.ddev/commands/db/ - Run in database containerddev self-upgradeddev drush <cmd> # Run Drush commands
ddev composer <cmd> # Run Composer
ddev php <script> # Run PHP scripts
ddev exec <cmd> # Run any command in web container
ddev ssh # SSH into web containerddev mysql # MySQL CLI
ddev export-db # Export database
ddev import-db # Import database (--file=dump.sql)
ddev snapshot # Create database snapshot
ddev restore # Restore from snapshotddev describe # Show project info and URLs
ddev logs # View container logs
ddev launch # Open site in browser
ddev share # Create public URL (ngrok)name: my-project
type: drupal # Auto-detects Drupal version, or use drupal11/drupal10
docroot: web
php_version: "8.3" # Use 8.3 for Drupal 11, 8.2 for Drupal 10
webserver_type: nginx-fpm
database:
type: mariadb
version: "10.11"
# Additional hostnames
additional_hostnames:
- api.my-project.ddev.site
# Extra PHP packages
webimage_extra_packages: [php8.3-imagick]version: '3.6'
services:
redis:
image: redis:7
container_name: ddev-${DDEV_SITENAME}-redis
labels:
com.ddev.site-name: ${DDEV_SITENAME}
expose:
- "6379"memory_limit = 512M
upload_max_filesize = 64M
post_max_size = 64Mmkdir my-drupal && cd my-drupal
ddev config --project-type=drupal --docroot=web --php-version=8.3
ddev start
ddev composer create-project drupal/recommended-project:^11
ddev composer require drush/drush
ddev drush site:install --account-name=admin --account-pass=admin -y
ddev launchmkdir my-drupal && cd my-drupal
ddev config --project-type=drupal --docroot=web --php-version=8.2
ddev start
ddev composer create-project drupal/recommended-project:^10
ddev composer require drush/drush
ddev drush site:install --account-name=admin --account-pass=admin -y
ddev launchcd existing-project
ddev config --project-type=drupal --docroot=web
ddev start
ddev composer install
ddev import-db --file=database.sql.gz
ddev drush cr# This happens when extra directories exist (like .claude/, .git/, etc.)
# Solution: Move them out temporarily
mv .claude /tmp/claude-backup
mv .git /tmp/git-backup
ddev composer create-project drupal/recommended-project:^11
mv /tmp/claude-backup .claude
mv /tmp/git-backup .gitddev poweroff
# Check what's using ports 80/443
sudo lsof -i :80ddev restart
ddev debug refresh # Rebuild containers
ddev delete && ddev start # Nuclear optionddev exec chown -R $(id -u):$(id -g) .ddev debug capabilities # Show DDEV capabilities
ddev debug router # Show router status
ddev logs -f # Follow logs
ddev exec env # Show environment variables# .ddev/providers/platform.yaml
environment_variables:
project: my-project
environment: main
db_pull_command:
command: platform db:dump -e ${environment}ddev xdebug on # Enable step debugging
ddev xdebug off # Disable (faster performance)
ddev xdebug status # Check current state// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
}
]
}# .ddev/php/xdebug.ini
[xdebug]
xdebug.mode=debug,develop,coverage# .ddev/docker-compose.redis.yaml
services:
redis:
image: redis:7-alpine
container_name: ddev-${DDEV_SITENAME}-redis
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
expose:
- "6379"
volumes:
- redis-data:/data
volumes:
redis-data:$settings['redis.connection']['host'] = 'redis';
$settings['redis.connection']['port'] = 6379;
$settings['cache']['default'] = 'cache.backend.redis';# .ddev/docker-compose.solr.yaml
services:
solr:
image: solr:9
container_name: ddev-${DDEV_SITENAME}-solr
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
expose:
- "8983"
volumes:
- solr-data:/var/solr
command: solr-precreate drupal
volumes:
solr-data:# .ddev/docker-compose.elasticsearch.yaml
services:
elasticsearch:
image: elasticsearch:8.11.0
container_name: ddev-${DDEV_SITENAME}-elasticsearch
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
expose:
- "9200"
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
volumes:
elasticsearch-data:ddev launch -m # Open Mailpit UI# Enable globally
ddev config global --mutagen-enabled
# Or per-project in .ddev/config.yaml
mutagen_enabled: trueddev mutagen status # Check sync status
ddev mutagen sync # Force sync
ddev mutagen reset # Reset if issuesddev config global --nfs-mount-enabled