# DDEV Docs > Do local website development on your computer or in the cloud with DDEV. # Getting Started # Get Started with DDEV [DDEV]() is an open source tool for launching local web development environments in minutes. It supports PHP and Node.js. These environments can be extended, version controlled, and shared, so you can take advantage of a Docker workflow without Docker experience or bespoke configuration. Projects can be changed, powered down, or removed as easily as they’re started. ## System Requirements ### macOS Runs natively on ARM64 (Apple Silicon) and AMD64 machines. - macOS Sonoma (14) or higher. This is primarily driven by the available Docker providers. - RAM: 8GB - Storage: 256GB - [OrbStack]() or [Lima]() or [Docker Desktop]() or [Rancher Desktop]() or [Colima]() **Next steps:** *You’ll need a Docker provider on your system before you can install DDEV.* 1. Install Docker with [recommended settings](). 2. Install [DDEV for macOS](). 3. Launch your [first project]() and start developing. 🚀 ### Windows WSL2 - RAM: 8GB - Storage: 256GB - [Docker CE]() inside WSL2 or [Docker Desktop]() on the Windows side. - Ubuntu or an Ubuntu-derived distro is recommended, though others may work fine **Next steps:** *You’ll need a Docker provider on your system before you can install DDEV.* 1. Install [DDEV for Windows](). 2. Launch your [first project]() and start developing. 🚀 **WSL2 in Mirrored Mode** If you’re using Windows WSL2 with [“Mirrored” networking mode](), enable the experimental `hostAddressLoopback=true` setting. You can do this using the “WSL Settings” app: - Networking \> Networking mode: set to *Mirrored* - Networking \> Host Address Loopback: turn *On*. Or by creating/editing the file at `C:\Users\\.wslconfig`: ``` [wsl2] networkingMode=Mirrored [experimental] hostAddressLoopback=true ``` **WSL2 in VirtioProxy Mode (Experimental)** If you’re using Windows WSL2 with [“VirtioProxy” networking mode](), DDEV supports this mode experimentally. Set your `C:\Users\\.wslconfig`: ``` [wsl2] networkingMode=VirtioProxy ``` Then restart WSL with `wsl --shutdown`. > [!WARNING] > > **VirtioProxy mode is experimental with significant limitations** > > VirtioProxy mode has known limitations and is poorly documented by Microsoft. On some machines it works well; on others, the WSL2 distro has no internet access at all, making tools like Composer and npm non-functional. > > When it does work: the WSL2 distro and Docker containers cannot make outgoing connections to the Windows host, so a Windows-side IDE cannot receive Xdebug connections. Run your IDE inside WSL2 via WSLg instead: > > ``` > ddev config global --xdebug-ide-location=wsl2 > ``` > > Accessing DDEV sites from a Windows browser works normally. Run `ddev utility xdebug-diagnose` to check your Xdebug configuration. > > If VirtioProxy is required due to a corporate VPN (such as Netskope), see [Special Network Configurations](). Users who can choose their setup may prefer the [Traditional Windows](<#traditional-windows>) approach instead. ### Traditional Windows - Any recent edition of Windows Home or Windows Pro. - RAM: 8GB - Storage: 256GB - [Docker Desktop]() using the WSL2 backend **Next steps:** *You’ll need a Docker provider on your system before you can install DDEV.* 1. Install Docker with [recommended settings](). 2. Install [DDEV for Windows](). 3. Launch your [first project]() and start developing. 🚀 ### Linux Most distros and most versions work fine, on both AMD64 and ARM64 architectures. - RAM: 8GB - Storage: 256GB **Next steps:** *You’ll need a Docker provider on your system before you can install DDEV.* 1. Install Docker with [recommended settings](). 2. Install [DDEV for Linux](). 3. Launch your [first project]() and start developing. 🚀 ### GitHub Codespaces With [GitHub Codespaces]() you don’t install anything; you only need a browser and an internet connection. **Next steps:** 1. Install DDEV within [GitHub Codespaces](). 2. Launch your [first project]() and start developing. 🚀 # Configuring Browsers for DDEV projects **Most DDEV users can ignore this page. The standard instructions in DDEV Installation do everything that is needed. These instructions are for unusual browsers or OS environments.** ## Firefox on Windows Modern Firefox on Windows supports using the Windows system certificate store automatically. If you use Firefox on Windows and see certificate errors on DDEV sites, go to Firefox Settings → Privacy & Security and check **“Allow Firefox to automatically trust third-party root certificates you install”**. This is often all that is needed. If that setting is already enabled and Firefox still shows errors, import the CA manually (see below). DDEV generates SSL certificates to enable local projects to use the HTTPS protocol. It uses a custom root Certificate Authority (CA) to generate SSL certificates for `*.ddev.site` domains. However, since this is a custom CA, web browsers display an ERR\_CERT\_AUTHORITY\_INVALID warning when trying to access a local DDEV site over HTTPS. To eliminate this warning, you can install the custom root CA into your browser. For default browsers, this works automatically using the [mkcert]() utility. For custom browsers (such as Firefox Developer Edition, Vivaldi, etc.), manual steps may be required. > [!TIP] > > **Want to learn more about HTTPS in DDEV?** > > See [Hostnames and Wildcards and DDEV, Oh My!]() for more information on DDEV hostname resolution. ## Adding the DDEV Root Certificate Authority to Browsers The steps to install the root CA depend on the browser, but they generally follow this process: 1. Use `mkcert -CAROOT` to locate the directory with the generated root certificate. Inside, you should find a `rootCA.pem` file. If it’s missing, run `mkcert -install` command. 2. Open your browser and navigate to the preferences or settings. 3. Find the Certificate Manager, typically located in the “Security” section of the preferences. 4. Click the “Manage Certificates” button (labeled “View Certificates” in older Firefox versions). 5. Go to the “Authorities” tab. 6. Click the “Import” button to add a custom authority certificate. 7. Import the `rootCA.pem` file to install the root certificate authority. > [!NOTE] > > **Still having issues?** > > Check out [this specific mkcert thread]() for additional troubleshooting. ## Xcode iOS Simulator Testing any DDEV site via Safari through the iOS simulator will lead to TLS errors due to the mkcert CA certificate not being trusted. You will need to manually install the CA certificate into the simulator device. 1. Run `mkcert -CAROOT` and open that directory in Finder. On macOS, the default location is typically `~/Library/Application Support/mkcert`. 2. Drag the `rootCA.pem` file from that directory onto the iOS simulator session. 3. In the iOS simulator, open **Settings** and install the downloaded certificate profile. Depending on the iOS version, use **Profile Downloaded** or go to **General \> VPN & Device Management** (or **Profiles**) and install the mkcert/root CA profile. 4. After the profile is installed, go to **Settings \> General \> About \> Certificate Trust Settings** and enable full trust for the mkcert certificate. This should now allow valid TLS connections to any DDEV domain/site. # DDEV Installation Once you’ve [installed a Docker provider](), you’re ready to install DDEV! ## macOS ### Homebrew [Homebrew]() is the easiest and most reliable way to install and [upgrade]() DDEV: ``` # Install DDEV brew install ddev/ddev/ddev # One-time initialization of mkcert mkcert -install ``` For unusual browsers and situations that don’t automatically support the `mkcert` certificate authority, [configure your browser](). ### Install Script The [install script]() is another option. It downloads, verifies, and sets up the `ddev` executable: ``` # Download and run the install script curl -fsSL https://ddev.com/install.sh | bash ``` For unusual browsers and situations that don’t automatically support the `mkcert` certificate authority, [configure your browser](). **Do you still have an old version after installing or upgrading?** If `ddev --version` still shows an older version than you installed or upgraded to, use `which -a ddev` to find out where another version of the `ddev` executable must be installed. See the [“Why Do I Have An Old DDEV” FAQ](). **Need a specific version?** Use the `-s` argument to specify a specific stable or prerelease version: ``` # Download and run the script to install DDEV v1.24.10 curl -fsSL https://ddev.com/install.sh | bash -s v1.24.10 ``` ## Linux ### Debian/Ubuntu DDEV’s Debian and RPM packages work with `apt` and `yum` repositories and most variants that use them, including Windows WSL2: ``` # Ensure sudo credentials are cached for copy/paste of this block sudo true # Add DDEV’s GPG key to your keyring sudo apt-get update && sudo apt-get install -y curl sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://packages.ddev.com/public/gpg.key | sudo tee /etc/apt/keyrings/ddev.asc > /dev/null sudo chmod a+r /etc/apt/keyrings/ddev.asc # Remove old repository files if present sudo rm -f /etc/apt/keyrings/ddev.gpg /etc/apt/sources.list.d/ddev.list # Add DDEV releases to your package repository printf "Types: deb\nURIs: https://packages.ddev.com/public/deb/ubuntu\nSuites: stable\nComponents: main\nSigned-By: /etc/apt/keyrings/ddev.asc\n" | sudo tee /etc/apt/sources.list.d/ddev.sources >/dev/null # Update package information and install DDEV sudo apt-get update && sudo apt-get install -y ddev # One-time initialization of mkcert mkcert -install ``` > [!TIP] > > **Prefer to run as a script?** > > Create a script file, then run it: > > ``` > cat > /tmp/install-ddev.sh << 'SCRIPT' > #!/usr/bin/env bash > set -euo pipefail > sudo true > sudo apt-get update && sudo apt-get install -y curl > sudo install -m 0755 -d /etc/apt/keyrings > curl -fsSL https://packages.ddev.com/public/gpg.key | sudo tee /etc/apt/keyrings/ddev.asc > /dev/null > sudo chmod a+r /etc/apt/keyrings/ddev.asc > sudo rm -f /etc/apt/keyrings/ddev.gpg /etc/apt/sources.list.d/ddev.list > printf "Types: deb\nURIs: https://packages.ddev.com/public/deb/ubuntu\nSuites: stable\nComponents: main\nSigned-By: /etc/apt/keyrings/ddev.asc\n" | sudo tee /etc/apt/sources.list.d/ddev.sources >/dev/null > sudo apt-get update && sudo apt-get install -y ddev > mkcert -install > SCRIPT > ``` > > Review the script, then run it: `bash /tmp/install-ddev.sh` For unusual browsers and situations that don’t automatically support the `mkcert` certificate authority, [configure your browser](). **Do you still have an old version after installing or upgrading?** If `ddev --version` still shows an older version than you installed or upgraded to, use `which -a ddev` to find out where another version of the `ddev` executable must be installed. See the [“Why Do I Have An Old DDEV” FAQ](). **Need to remove a previously-installed variant?** If you previously used DDEV’s [install script](<#install-script>), you can remove that version: ``` sudo rm -f /usr/local/bin/ddev /usr/local/bin/mkcert ``` If you previously [installed DDEV with Homebrew](<#homebrew>), you can run `brew unlink ddev` to get rid of the Homebrew version. ### Fedora, Red Hat, etc. ``` # Ensure sudo credentials are cached for copy/paste of this block sudo true # Add DDEV releases to your package repository echo '[ddev] name=ddev baseurl=https://packages.ddev.com/public/rpm/any-distro/any-version/$basearch gpgkey=https://packages.ddev.com/public/gpg.key gpgcheck=1 repo_gpgcheck=1 enabled=1' | sed 's/^ \+//' | sudo tee /etc/yum.repos.d/ddev.repo >/dev/null # Install DDEV sudo dnf install -y --refresh ddev # One-time initialization of mkcert mkcert -install ``` > [!TIP] > > **Prefer to run as a script?** > > Create a script file, then run it: > > ``` > cat > /tmp/install-ddev.sh << 'SCRIPT' > #!/usr/bin/env bash > set -euo pipefail > sudo true > printf "[ddev]\nname=ddev\nbaseurl=https://packages.ddev.com/public/rpm/any-distro/any-version/\$basearch\ngpgkey=https://packages.ddev.com/public/gpg.key\ngpgcheck=1\nrepo_gpgcheck=1\nenabled=1\n" | sudo tee /etc/yum.repos.d/ddev.repo >/dev/null > sudo dnf install -y --refresh ddev > mkcert -install > SCRIPT > ``` > > Review the script, then run it: `bash /tmp/install-ddev.sh` For unusual browsers and situations that don’t automatically support the `mkcert` certificate authority, [configure your browser](). ### Arch Linux We maintain the [ddev-bin]() package in AUR for Arch-based systems including Arch Linux, EndeavourOS and Manjaro. Install with `yay` or your AUR tool of choice. ``` # Install DDEV yay -S ddev-bin # One-time initialization of mkcert mkcert -install ``` For unusual browsers and situations that don’t automatically support the `mkcert` certificate authority, [configure your browser](). ### Homebrew ``` # Install DDEV using Homebrew brew install ddev/ddev/ddev # One-time initialization of mkcert mkcert -install ``` For unusual browsers and situations that don’t automatically support the `mkcert` certificate authority, [configure your browser](). ### Install Script The [install script]() is another option. It downloads, verifies, and sets up the `ddev` executable: ``` # Download and run the install script curl -fsSL https://ddev.com/install.sh | bash ``` For unusual browsers and situations that don’t automatically support the `mkcert` certificate authority, [configure your browser](). **Need a specific version?** Use the `-s` argument to specify a specific stable or prerelease version: ``` # Download and run the script to install DDEV v1.24.10 curl -fsSL https://ddev.com/install.sh | bash -s v1.24.10 ``` **Do you still have an old version after installing or upgrading?** If `ddev --version` still shows an older version than you installed or upgraded to, use `which -a ddev` to find out where another version of the `ddev` executable must be installed. See the [“Why Do I Have An Old DDEV” FAQ](). ## Windows DDEV on Windows uses an installer that supports three configurations — pick the one that fits you: | Mode | Best for | Requires | | --- | --- | --- | | **Docker CE inside WSL2** (recommended) | Most users, best performance | WSL2 | | **Docker Desktop / Rancher Desktop with WSL2** | Already have Docker Desktop or Rancher Desktop installed | WSL2 + Docker Desktop or Rancher Desktop | | **Traditional Windows** | PowerShell/Git Bash workflow | Docker Desktop or Rancher Desktop (no separate WSL2 distro needed) | ### Step 1: Install WSL2 In PowerShell, run: ``` # Install WSL2; reboot if prompted, then continue: wsl --install --no-distribution # Update WSL2 if previously installed: wsl --update ``` Create an Ubuntu or other Debian-based WSL2 distro (skip if you’re using Traditional Windows). Ubuntu is recommended: ``` # You'll be asked to set a username and password for the distro: wsl --install Ubuntu-26.04 --name DDEV ``` > [!TIP] > > **"DDEV" is just a suggested name — use any name you like.** > [!TIP] > > **Other Debian-based distros also work** > > The DDEV installer supports Ubuntu and Debian and has been tested with Kali Linux and eLxr. If you prefer one of those, substitute its name, for example `wsl --install Debian --name DDEV`. Verify the “DDEV” distro is set as default: ``` > wsl -l -v NAME STATE VERSION * DDEV Stopped 2 ``` ### Step 2: Install DDEV Download the installer — make sure to pick the right architecture: - **AMD64 (x86-64)**: Most traditional Windows PCs (Intel/AMD processors) - **ARM64**: Windows on ARM devices like Microsoft Surface Pro X, Surface Pro 9 (5G), or other ARM-based Windows devices > [!TIP] > > **Check your system architecture** > > Not sure which architecture you have? Open PowerShell and run: `$env:PROCESSOR_ARCHITECTURE`. It will show `AMD64` or `ARM64`. Alternatively, in WSL2/Ubuntu run `uname -m` which shows `x86_64` for AMD64 or `aarch64` for ARM64. Run the installer and select your mode. For WSL2 modes, the installer will display all detected Debian-based WSL2 distros (Ubuntu, Debian, Kali, eLxr, etc.) and prompt you to select one. The installer will automatically configure DDEV for your chosen Docker provider. If you run the wrong installer for your architecture, it will detect the mismatch and direct you to download the correct one. > [!TIP] > > **Install using WinGet** > > [WinGet]() can download and launch the installer interactively: > > ``` > winget install --interactive DDEV > ``` > > For silent/automated installation with specific options, use `--override` to pass installer flags: > > ``` > # WSL2 with Docker CE and specified distro, silently > winget install DDEV --silent --override '/docker-ce /distro=DDEV' > ``` > > If WinGet picks up an old version (which can happen when DDEV was previously installed with the Windows installer), uninstall all versions first: > > ``` > winget uninstall DDEV --all-versions > ``` > [!TIP] > > **Install using Chocolatey (Traditional Windows AMD64 only)** > > [Chocolatey]() has had DDEV support for years and remains an option for Traditional Windows on AMD64: > > ``` > choco install ddev > ``` > [!TIP] > > **Installer in Silent Mode** > > The Windows installer supports silent mode for automated installations and testing: > > ``` > # WSL2 with Docker CE and specified distro > ./ddev_windows_amd64_installer.exe /S /docker-ce /distro=DDEV > > # WSL2 with Docker Desktop and specified distro > ./ddev_windows_amd64_installer.exe /S /docker-desktop /distro=Ubuntu > > # Traditional Windows (requires Docker Desktop/Rancher Desktop) > ./ddev_windows_installer.exe /S /traditional > > # Get help with all options > ./ddev_windows_amd64_installer.exe /help > ``` > > The `/S` flag makes the installation completely silent. Use `/distro=` to specify your WSL2 distribution name (required for WSL2 options). **Manual Installation** ### Windows Manual Installation If you prefer to install DDEV manually on WSL2, these techniques can be used to install on a non-Ubuntu distro, and can be adapted for Yum-based or ArchLinux-based distros as well: 1. In PowerShell, run `mkcert.exe -install` and follow the prompt to install the Certificate Authority. 2. In PowerShell, run `$env:CAROOT="$(mkcert -CAROOT)"; setx CAROOT $env:CAROOT; If ($Env:WSLENV -notlike "*CAROOT/up:*") { $env:WSLENV="CAROOT/up:$env:WSLENV"; setx WSLENV $Env:WSLENV }`. This will set WSL2 to use the Certificate Authority installed on the Windows side, so your Windows-side browser can trust it. 3. In PowerShell, run `wsl --update` to make sure you have a current WSL version. 4. In PowerShell, run `wsl --install --name DDEV`, for example `wsl --install Debian --name DDEV`. This will install the WSL2 distro for you. (The name “DDEV” is just a suggestion; you can use any name you like.) 5. **Docker CE:** Follow the instructions in the [Linux install section here]() to install Docker CE and DDEV. In addition, `sudo apt-get install -y docker-ce ddev ddev-wsl2` (or the equivalent `dnf` or other package manager commands). 6. **Docker Desktop for Windows (if used, this is not preferred):** - Download and install the installer from [docker.com](). Choose the WSL2-based configuration. Start Docker Desktop. It may prompt you to log out and log in again, or reboot. - Go to Docker Desktop’s *Settings* → *Resources* → *WSL integration* → *enable integration for your distro*. Now `docker` commands will be available from within your WSL2 distro. 7. Double-check your distro: `echo $CAROOT` should show something like `/mnt/c/Users//AppData/Local/mkcert` 8. Check that Docker is working inside Ubuntu (or your distro) by running `docker ps` in the distro. 9. Open the WSL2 terminal, for example `Debian` from the Windows start menu. 10. In WSL2, run `mkcert -install`. 11. Note that the older manual [PowerShell script]() may be instructional if you’re setting up a non-Ubuntu distro. It has been replaced by the Windows installer, but could be adapted for other distros. Pass `-Distro ` when running it. > [!NOTE] > > **Path to certificates** > > If you get the prompt `Installing to the system store is not yet supported on this Linux`, you may need to add `/usr/sbin` to the `$PATH` so that `/usr/sbin/update-ca-certificates` can be found. ### Step 3: Start working in WSL2 *(Skip if using Traditional Windows.)* Open your distro from the Start menu (or run `wsl` in PowerShell) to get a Linux terminal. Run all `ddev` commands here — never in PowerShell or Git Bash. **Store your [projects]() inside WSL2** (e.g. `/home//projects`), not on `C:\` drive (`/mnt/c` in WSL2) — faster and avoids permission issues. From Windows Explorer: **Linux → DDEV → home → \[username\]**. > [!NOTE] > > **Custom hostnames** > > Custom hostnames with a non-default [TLD]() (non `ddev.site`) are managed via the Windows hosts file at `C:\Windows\System32\drivers\etc\hosts`, not within WSL2. **What if my browser (especially Firefox) doesn’t trust `https` URLs?** DDEV uses [mkcert]() to generate a local certificate authority (CA), and `mkcert -install` registers it with most browsers automatically. Firefox and some other browsers manage their own certificate stores and require a manual import. To diagnose certificate trust issues, run `ddev utility tls-diagnose`. To manually import the CA: run `mkcert.exe -CAROOT` to find the directory containing `rootCA.pem`, then import that file via your browser’s certificate manager (usually under Security settings → View Certificates → Authorities → Import). See [Configuring Browsers]() for full details. ## GitHub Codespaces You can use DDEV in remote [GitHub Codespaces]() without having to run Docker locally; you only need a browser and an internet connection. Start by creating a `.devcontainer/devcontainer.json` file in your GitHub repository: ``` { "image": "mcr.microsoft.com/devcontainers/base:debian-12", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": { "version": "latest" }, "ghcr.io/ddev/ddev/install-ddev:latest": {} } } ``` The `install-ddev` feature automatically configures the environment and sets up DDEV for use in Codespaces. Launch your repository in Codespaces: ![Screenshot of codespace create dialog in a repository on GitHub]() ![Screenshot of codespace create dialog in a repository on GitHub]() DDEV is now available within your new codespace instance: ![]() Run `ddev config` to [start a new blank project]() - or [install a CMS](). Run `ddev start` if there is already a configured DDEV project in your repository. **Troubleshooting**: If there are errors after restarting a codespace, use `ddev restart` or `ddev poweroff`. You can also use the commands - “Codespaces: Rebuild container” - “Codespaces: Full rebuild container” (Beware: database will be deleted) via the [Visual Studio Code Command Palette](): - ⌘ + SHIFT + P on a Mac - CTRL + SHIFT + P on Windows/Linux - from the Application Menu, click View \> Command Palette (Firefox) If you need DDEV-specific assistance or have further questions, see [support](). Your updated `devcontainer.json` file may differ depending on your project, but you should have `install-ddev` in the `features` section. > [!NOTE] > > **Normal Linux installation also works** > > You can also install DDEV as if it were on any normal [Linux installation](<#linux>). ### Docker integration DDEV in Codespaces relies on [`docker-in-docker`](), which must be added manually to the features when using `"mcr.microsoft.com/devcontainers/base:debian-12"`. See [`devcontainers/features`]() for general support and issues regarding Docker-support. ### DDEV’s router is not used Since Codespaces handles all the routing, the internal DDEV router will not be used on Codespaces. Therefore config settings like [`web_extra_exposed_ports`]() will have no effect. You can expose ports via the `ports` setting, which is usually not recommended if you work locally due to port conflicts. But you can load these additional Docker compose files only when Codespaces is detected. See [Defining Additional Services]() for more information. ``` services: web: ports: - "5174:5174" ``` ### Default environment variables Codespace instances already provide some [default environment values](). You can inherit and inject them in your `.ddev/config.yaml`: ``` web_environment: - CODESPACES - CODESPACE_NAME - GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN ``` ### Advanced usage via devcontainer.json A lot more customization is possible via the [`devcontainer.json`-configuration](). You can install Visual Studio Code extensions by default or run commands automatically. #### postCreateCommand The [`postCreateCommand`]() lets you run commands automatically when a new codespace is launched. DDEV commands are available here. The event is triggered on: fresh creation, rebuilds and full rebuilds. `ddev poweroff` is used in this example to avoid errors on rebuilds since some Docker containers are kept. You usually want to use a separate bash script to do this, as docker [might not yet be available when the command starts to run](). ``` { "image": "mcr.microsoft.com/devcontainers/base:debian-12", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": { "version": "latest" }, "ghcr.io/ddev/ddev/install-ddev:latest": {} }, "portsAttributes": { "3306": { "label": "database" }, "8027": { "label": "mailpit" }, "8080": { "label": "web http" }, "8443": { "label": "web https" } }, "postCreateCommand": "bash .devcontainer/setup_project.sh" } ``` The `install-ddev` feature provides its own `postCreateCommand` to verify DDEV installation. User-provided commands run after the feature’s commands. ``` #!/usr/bin/env bash set -ex wait_for_docker() { while true; do docker ps > /dev/null 2>&1 && break sleep 1 done echo "Docker is ready." } wait_for_docker # download images beforehand, optional ddev utility download-images # avoid errors on rebuilds ddev poweroff # start ddev project automatically ddev start -y # further automated install / setup steps, e.g. ddev composer install ``` To check for errors during the `postCreateCommand` action, use the command - “Codespaces: View creation log” via the [Visual Studio Code Command Palette](): - ⌘ + SHIFT + P on a Mac - CTRL + SHIFT + P on Windows/Linux - from the Application Menu, click View \> Command Palette (Firefox) ![]() ## Manual DDEV is a single executable, so installation on any OS is a matter of copying the `ddev` executable for your architecture into the appropriate system path on your machine. - Download and extract the latest [DDEV release]() for your architecture. - Move `ddev` to `/usr/local/bin` with `mv ddev /usr/local/bin/` (may require `sudo`), or another directory in your `$PATH` as preferred. - Run `ddev` to test your installation. You should see DDEV’s command usage output. - As a one-time initialization, run `mkcert -install`, which may require your `sudo` password. If you don’t have `mkcert` installed, download the [latest release]() for your architecture and `sudo mv /usr/local/bin/mkcert && sudo chmod +x /usr/local/bin/mkcert`. - For unusual browsers and situations that don’t automatically support the `mkcert` certificate authority, [configure your browser](). # Upgrading DDEV Installing and upgrading DDEV are nearly the same thing, because you’re upgrading the `ddev` binary that talks with Docker. You can update this file like other software on your system, whether it’s with a package manager or traditional installer. > [!TIP] > > **`ddev --version` shows an old version** > > If you have installed or upgraded DDEV to the latest version, but when you check the actual version with `ddev --version`, it shows an older version, please refer to [Why do I have an old DDEV?]() ## macOS ### Homebrew (Most Common) ``` # Upgrade DDEV to the latest version brew update && brew upgrade ddev/ddev/ddev ``` ### Install Script (Unusual) ``` # Download and run the script to replace the DDEV binary curl -fsSL https://ddev.com/install.sh | bash ``` ### Verify New Version Use `ddev --version` to find out the version of the `ddev` binary in your `$PATH`. If `ddev --version` still shows an older version than you installed or upgraded to, use `which -a ddev` to find out where another version of the `ddev` binary must be installed. See the [“Why Do I Have An Old DDEV” FAQ](). **Need a specific version?** Use the `-s` argument to specify a specific stable or prerelease version: ``` # Download and run the script to update to DDEV v1.24.10 curl -fsSL https://ddev.com/install.sh | bash -s v1.24.10 ``` ## Linux ### Debian/Ubuntu (including WSL2) ``` # Update package information and ddev package sudo apt-get update && sudo apt-get install --only-upgrade ddev # If you are using WSL2, also upgrade the ddev-wsl2 package sudo apt-get update && sudo apt-get install --only-upgrade ddev ddev-wsl2 ``` ### Fedora, Red Hat, etc. ``` # Upgrade the DDEV package sudo dnf upgrade --refresh ddev ``` ### Arch Linux ``` # Upgrade the DDEV package yay -Syu ddev-bin ``` ### Verify New Version Use `ddev --version` to find out the version of the `ddev` binary in your `$PATH`. If `ddev --version` still shows an older version than you installed or upgraded to, use `which -a ddev` to find out where another version of the `ddev` binary must be installed. See the [“Why Do I Have An Old DDEV” FAQ](). ## Windows ### WSL2 + Docker If you’re using WSL2, the upgrade process is the same regardless of how you installed DDEV. Open the WSL2 terminal, for example “Ubuntu” from the Windows start menu, and run the following: ``` # Update package information and ddev packages sudo apt-get update && sudo apt-get install --only-upgrade ddev ddev-wsl2 ``` You can also download and run the DDEV Windows Installer again (see [Traditional Windows](<#traditional-windows>) below), and it will do the upgrade for you. Make sure to choose the type of installation you have (Docker CE or Docker/Rancher Desktop). ### Verify New Version Use `ddev --version` to find out the version of the `ddev` binary in your `$PATH`. If `ddev --version` still shows an older version than you installed or upgraded to, use `which -a ddev` to find out where another version of the `ddev` binary must be installed. See the [“Why Do I Have An Old DDEV” FAQ](). ### Traditional Windows Download the installer — make sure to pick the right architecture: - **AMD64 (x86-64)**: Most traditional Windows PCs (Intel/AMD processors) - **ARM64**: Windows on ARM devices like Microsoft Surface Pro X, Surface Pro 9 (5G), or other ARM-based Windows devices > [!TIP] > > **Check your system architecture** > > Not sure which architecture you have? Open PowerShell and run: `$env:PROCESSOR_ARCHITECTURE`. It will show `AMD64` or `ARM64`. Alternatively, in WSL2/Ubuntu run `uname -m` which shows `x86_64` for AMD64 or `aarch64` for ARM64. Run the installer and follow the prompts to complete the upgrade. > [!TIP] > > **Upgrade using WinGet** > > ``` > winget upgrade --interactive DDEV > ``` > [!TIP] > > **Upgrade using Chocolatey (Traditional Windows AMD64 only)** > > ``` > choco upgrade ddev > ``` ## GitHub Codespaces ``` # Update package information and ddev package sudo apt-get update && sudo apt-get install --only-upgrade ddev ``` ## Manual Upgrade using the exact same [manual install]() process: - Download and extract the latest [DDEV release]() for your architecture. - Move `ddev` to `/usr/local/bin` with `mv ddev /usr/local/bin/` (may require `sudo`), or another directory in your `$PATH` as preferred. - Run `ddev --version` to confirm you’re running the expected version. # Docker Installation You’ll need a Docker provider on your system before you can [install DDEV](). ## macOS Choose a container runtime: - **Docker** (recommended) - The best-tested option. Install OrbStack, Lima, Docker Desktop, Rancher Desktop, or Colima. - **Podman rootless** - Rootless by default; a good fit where Docker is forbidden. Can’t use the default ports 80/443, so DDEV must be configured to use unprivileged ports. Install one of the supported Docker providers: - [OrbStack](<#orbstack>): Recommended, easiest to install, most performant, commercial, not open-source. - [Lima](<#lima>): Free, open-source. - [Docker Desktop](<#docker-desktop-for-mac>): Familiar, popular, not open-source, may require license, may be unstable. - [Rancher Desktop](<#rancher-desktop>): Free, open-source, simple installation, slower startup. - [Colima](<#colima>): Free, open-source. Depends on separate Lima installation (managed by Homebrew). ### OrbStack [OrbStack]() is a newer Docker provider that is very popular with DDEV users because it’s fast, lightweight, and easy to install. It’s a good choice for most users. It is *not* open-source, and it is not free for professional use. 1. Install OrbStack with `brew install orbstack` or [download it directly](). 2. Run the OrbStack app (from Applications) to finish setup, choosing “Docker” as the option. Answer any prompts to allow OrbStack access. ### Lima [Lima]() is a free and open-source project supported by the [Cloud Native Computing Foundation](). 1. Install Lima with `brew install lima`. 2. If you don’t have the `docker` client (if `docker help` fails) then install it with `brew install docker`. 3. Create a 100GB VM in Lima with 4 CPUs, 6GB memory, and Cloudflare DNS. Adjust to your own needs: ``` limactl create --name=default --vm-type=vz --mount-type=virtiofs --mount-writable --memory=6 --cpus=4 --disk=100 template:docker docker context create lima-default --docker "host=unix://$HOME/.lima/default/sock/docker.sock" docker context use lima-default ``` After the initial run above, you can use `limactl start`. Run `limactl list` to see configured setup. When your computer restarts, you’ll need to `limactl start` again. > [!WARNING] > > **Docker contexts let the Docker client point at the right Docker server** > > The Docker provider you’re using is selected with `docker context`. You can see the available contexts with `docker context ls` and the currently selected one with `docker context show`. With the setup above you’ll want `docker context use lima-default`. > [!WARNING] > > **Lima only mounts filesystems in your home directory unless you do further configuration** > > By default, Lima only works with DDEV projects in your home directory. You must have your projects somewhere in your home directory for DDEV to work unless you do additional configuration. If your project is not in your home directory, you must add additional mounts, as described in [mounts example](). ### Docker Desktop for Mac Docker Desktop for Mac can be downloaded from [docker.com](). It has long been supported by DDEV and has extensive automated testing. It is not open-source, may require a license for many users, and sometimes has stability problems. > [!WARNING] > > **Ports unavailable?** > > If you get messages like `Ports are not available... exposing port failed... is vmnetd running?` it means you need to check the “Allow privileged port mapping (requires password)” checkbox in the “Advanced” section of the Docker Desktop configuration. You may have to stop and restart Docker Desktop, and you may have to turn it off, restart Docker Desktop, turn it on again, restart Docker Desktop. (More extensive problem resolution is in [Docker Desktop issue]().) ### Rancher Desktop Rancher Desktop is an easy-to-install free and open-source Docker provider. Install from [Rancher Desktop](). It has automated testing with DDEV. When installing, choose only the Docker option and turn off Kubernetes. ### Colima [Colima]() is a free and open-source project which bundles Lima. 1. Install Colima with `brew install colima`, which also installs Lima and other dependencies. 2. If you don’t have the `docker` client (if `docker help` fails) then install it with `brew install docker`. 3. Start Colima with 4 CPUs, 6GB memory, 100GB storage, Cloudflare DNS, and SSH-agent forwarding, adjusting as needed: ``` colima start --cpu 4 --memory 6 --disk 100 --vm-type=vz --mount-type=virtiofs --dns=1.1.1.1 --ssh-agent ``` After the initial run above, you can use `colima start` or use `colima start -e` to edit the configuration file. Run `colima status` at any time to check Colima’s status. > [!WARNING] > > **Docker contexts let the Docker client point at the right Docker server** > > Colima activates its own Docker context to prevent conflicts with Docker Desktop. If you run `docker context ls`, you’ll see a list of available contexts where the currently-active one is indicated with a `*`—which will be `colima` after you’ve started it. You can change to the default (Docker Desktop) with `docker context use default` or change back with `docker context use colima`. This means you can run Docker Desktop and Colima at the same time, but be mindful of which context you’re pointing at! > [!WARNING] > > **Colima can only work in your home directory unless you do further configuration** > > By default, Colima only works with DDEV projects in your home directory. You need to have your projects somewhere in your home directory for DDEV to work unless you do additional configuration. See the `~/.colima/default/colima.yaml` for more information, or notes in [colima.yaml](). ### Migrating Projects Between Docker Providers - OrbStack has built-in migration of images and volumes from Docker Desktop. - Move projects between other Docker providers using [How can I migrate from one Docker provider to another?](). Podman on macOS runs containers in a lightweight VM managed by `podman machine`. You can also use [Podman Desktop]() for a GUI. > [!WARNING] > > **Podman on macOS can’t use ports 80/443** > > Podman on macOS cannot bind to privileged ports (80/443), so you must configure DDEV to use unprivileged ports. After installing DDEV, run: > > ``` > ddev config global --router-http-port=8080 --router-https-port=8443 > ``` > > Your projects will then be reachable at `https://yourproject.ddev.site:8443` instead of the standard `https://yourproject.ddev.site`. ### Install Podman Install Podman using Homebrew: ``` brew install podman ``` Or install [Podman Desktop]() if you prefer a GUI. For more information, see the [official Podman installation guide for macOS]() and the [Podman tutorials](). ### Install the Docker CLI Podman provides a Docker-compatible API, so you can use the Docker CLI as a front-end for Podman. This lets you use familiar `docker` commands, switch between runtimes with Docker contexts, and keep compatibility with tools that expect the `docker` command. ``` brew install docker ``` ### Initialize and start the Podman machine The Podman machine gets only 2GB of memory by default, which runs out with more than a project or two running, so create it with 6GB, adjusting to your own needs: ``` # check `podman machine init -h` for more options podman machine init --provider applehv --memory 6144 podman machine start ``` ### Point the Docker CLI at Podman ``` SOCKET=$(podman machine inspect | jq -r '.[0].ConnectionInfo.PodmanSocket.Path') docker context create podman-rootless \ --description "Podman (rootless)" \ --docker "host=unix://${SOCKET}" docker context use podman-rootless docker ps ``` ### Reclaim disk space > [!WARNING] > > **The Podman machine doesn’t reclaim its own disk space** > > The `podman machine` VM grows over time and does not shrink automatically, even after you remove images and containers. Periodically reclaim the freed space by trimming the VM’s filesystem: > > ``` > podman machine start > podman machine ssh 'sudo fstrim -av' > ``` ## Linux Choose a container runtime: - **Docker** (recommended) - The best-tested option, with the best performance and stability. Install Docker CE or Docker Desktop. - **Docker rootless** - Runs the Docker daemon without root, which reduces the attack surface. Full Docker compatibility. - **Podman rootless** - Rootless by default; a good fit where Docker is forbidden. More setup and slower than Docker. Install one of the supported Docker providers: - [Docker CE](<#docker-for-linux>): Recommended, free, open-source, best performance and stability. Install from the official Docker repository. - [Docker Desktop for Linux](<#docker-desktop-for-linux>): Not explicitly supported by DDEV and has no automated testing. ### Docker for Linux The best way to install Docker on Linux is to use your native package management tool (`apt`, `dnf`, etc.) with the official Docker repository. While many Linux distributions provide Docker packages in their own repositories, these are often outdated and may not include the latest features required for stability in a development environment like DDEV. To ensure you’re using a supported version, install Docker directly from the official Docker repository. #### Debian/Ubuntu ``` # Ensure sudo credentials are cached for copy/paste of this block sudo true # Remove any old/conflicting Docker packages sudo apt-get remove -y docker.io docker-doc docker-compose podman-docker containerd runc 2>/dev/null || true # Install prerequisites sudo apt-get update && sudo apt-get install -y ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings # Add Docker’s GPG key sudo curl -fsSL https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Remove old repository files if present sudo rm -f /etc/apt/keyrings/docker.gpg /etc/apt/sources.list.d/docker.list # Add Docker repository in deb822 format printf "Types: deb\nURIs: https://download.docker.com/linux/%s\nSuites: %s\nComponents: stable\nSigned-By: /etc/apt/keyrings/docker.asc\n" \ "$(. /etc/os-release && echo "$ID")" \ "$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")" \ | sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null # Install Docker CE sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # Add your user to the docker group (create group if it doesn’t exist) sudo groupadd -f docker && sudo usermod -aG docker ${SUDO_USER:-$USER} # Start and enable Docker sudo systemctl enable --now docker ``` Run `sg docker` to open a new subshell with the `docker` group active, then verify with `docker run hello-world`. (A full log out and back in applies the change to your whole session.) > [!TIP] > > **Prefer to run as a script?** > > Create a script file, then run it: > > ``` > cat > /tmp/install-docker.sh << 'SCRIPT' > #!/usr/bin/env bash > set -euo pipefail > sudo true > sudo apt-get remove -y docker.io docker-doc docker-compose podman-docker containerd runc 2>/dev/null || true > sudo apt-get update && sudo apt-get install -y ca-certificates curl > sudo install -m 0755 -d /etc/apt/keyrings > sudo curl -fsSL "https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg" -o /etc/apt/keyrings/docker.asc > sudo chmod a+r /etc/apt/keyrings/docker.asc > sudo rm -f /etc/apt/keyrings/docker.gpg /etc/apt/sources.list.d/docker.list > printf "Types: deb\nURIs: https://download.docker.com/linux/%s\nSuites: %s\nComponents: stable\nSigned-By: /etc/apt/keyrings/docker.asc\n" \ > "$(. /etc/os-release && echo "$ID")" \ > "$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")" \ > | sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null > sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin > sudo groupadd -f docker && sudo usermod -aG docker ${SUDO_USER:-$USER} > sudo systemctl enable --now docker > SCRIPT > ``` > > Review the script, then run it: `bash /tmp/install-docker.sh` See the full [Docker Engine installation docs for Ubuntu]() or [Debian]() for more details. #### Other Linux Distributions Follow these distribution-specific instructions to set up Docker Engine from the official Docker repository: - [CentOS]() - [Fedora]() - [binaries]() After installing Docker, add your user to the `docker` group and enable the Docker daemon to start at boot: ``` sudo groupadd -f docker && sudo usermod -aG docker ${SUDO_USER:-$USER} sudo systemctl enable --now docker ``` Run `sg docker` to open a new subshell with the `docker` group active, or log out and back in to apply the change to your whole session. See [Post-installation steps for Linux]() for more details. > [!WARNING] > > **Don’t `sudo` with `docker` or `ddev`** > > Don’t use `sudo` with the `docker` command. If you find yourself needing it, you haven’t finished the installation. You also shouldn’t use `sudo` with `ddev` unless it’s specifically for the [`ddev hostname`]() command. On systems without `systemd` or its equivalent—mostly if you’re installing inside WSL2—you’ll need to manually start Docker with `service docker start` or the equivalent in your distro. You can add this to your shell profile. ### Docker Desktop for Linux As an alternative to Docker CE, Docker Desktop for Linux can be downloaded from [docker.com](). Casual manual testing seems to work, but DDEV does not have explicit support for it and does not have automated testing. [Docker rootless mode]() runs the Docker daemon and containers as a non-root user, which reduces the attack surface (no root daemon, container processes can’t touch root-owned files). DDEV supports Docker rootless on Linux and WSL2, with full Docker compatibility. > [!TIP] > > **`no_bind_mounts` is no longer required** > > Earlier DDEV versions (v1.25.0-v1.25.2) required [`no_bind_mounts`]() with Docker rootless. That is no longer necessary—Docker rootless now works with bind mounts. > > With bind mounts under Docker rootless, you appear as `root` inside the web container (for example, in [`ddev ssh`]()). This is expected: rootless maps container `root` to your regular host user, so it has no special privileges on the host. If you prefer not to work as `root` in the container, run `ddev config global --no-bind-mounts=true`. This enables [`no_bind_mounts`](), which relies on [Mutagen]() to sync files rather than mounting them directly. ### Install Docker rootless Follow the official [Docker rootless installation guide](). ### Configure the system ``` # Allow privileged port access if needed if [ -f /proc/sys/net/ipv4/ip_unprivileged_port_start ]; then if [ "1024" = "$(cat /proc/sys/net/ipv4/ip_unprivileged_port_start)" ]; then echo 'net.ipv4.ip_unprivileged_port_start=0' | sudo tee -a /etc/sysctl.d/60-rootless.conf sudo sysctl --system fi fi # Allow loopback connections (needed for working Xdebug) # See https://github.com/moby/moby/issues/47684#issuecomment-2166149845 mkdir -p ~/.config/systemd/user/docker.service.d cat << 'EOF' > ~/.config/systemd/user/docker.service.d/override.conf [Service] Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_DISABLE_HOST_LOOPBACK=false" EOF ``` ### Enable the Docker socket ``` systemctl --user enable --now docker.socket # You should see `/run/user/1000/docker.sock` (the number may vary): ls $XDG_RUNTIME_DIR/docker.sock ``` ### Point the Docker CLI at the rootless socket ``` # View existing contexts docker context ls # Create the rootless context if it doesn't exist docker context inspect rootless >/dev/null 2>&1 || \ docker context create rootless \ --description "Rootless runtime socket" \ --docker host="unix://$XDG_RUNTIME_DIR/docker.sock" # Switch to the context docker context use rootless # Verify it works docker ps ``` [Podman]() is rootless by default, which makes it a good fit for environments that forbid Docker or require rootless operation. Podman is slower than Docker and has more setup, but Podman rootless on Linux is solid. ### Install Podman Install Podman with your distribution’s package manager (see the [official Podman installation guide for Linux]()): ``` # Ubuntu/Debian sudo apt-get update && sudo apt-get install podman # Fedora sudo dnf install --refresh podman ``` **Last resort: a newer Podman from Homebrew** [Homebrew]() packages a current Podman. DDEV’s CI installs it this way so that tests always run against the latest Podman, but it is not the recommended way to get Podman on a workstation: it means running a container stack from a second package manager, and most of the [troubleshooting](<#podman-rootless-troubleshooting>) entries below exist only because of it. Prefer your distribution’s Podman even when it’s older. If you do use it, remove the distribution’s Podman *and what it brought in* first, keeping only the `uidmap` helpers, which are root-owned privileged binaries that Homebrew cannot provide: ``` # Ubuntu/Debian sudo apt-get install -y uidmap && sudo apt-mark manual uidmap sudo apt-get purge -y --auto-remove podman crun netavark aardvark-dns # Fedora sudo dnf remove -y podman crun netavark aardvark-dns brew install podman hash -r ``` The `apt-mark manual` is not optional: `uidmap` arrives as an automatic dependency of the distribution’s Podman, so the `--auto-remove` above — or any later `apt-get autoremove` run for unrelated cleanup — removes it again and breaks rootless Podman. You can also install [Podman Desktop]() if you prefer a GUI. For more information, see the [Podman tutorials](). ### Install the Docker CLI and buildx Podman provides a Docker-compatible API, so you can use the Docker CLI as a front-end for Podman. This lets you use familiar `docker` commands, switch between runtimes with Docker contexts, and keep compatibility with tools that expect the `docker` command. 1. [Set up Docker’s repository](). 2. Install the CLI and the buildx plugin (you don’t need `docker-ce`, the Docker engine): ``` # Ubuntu/Debian sudo apt-get update && sudo apt-get install docker-ce-cli docker-buildx-plugin # Fedora sudo dnf install --refresh docker-ce-cli docker-buildx-plugin ``` > [!WARNING] > > **buildx is required, and DDEV won’t install it for you** > > DDEV builds its project images with `docker buildx`, and its default setting (`docker_buildx_version: "system"`) uses whatever buildx is already installed rather than downloading one. Installing only `docker-ce-cli` leaves buildx missing, and `ddev start` then fails at the build step. Check with `docker buildx version`; DDEV requires 0.17.0 or newer. ### Configure Podman rootless Prepare the system by configuring `subuid`/`subgid` ranges and enabling `userns` options (see the [Arch Linux Wiki]() for details). If another user on this machine already holds `100000-165535`, give yourself a range that doesn’t overlap theirs — two users mapping the same host IDs break each other’s containers: ``` # Add subuid and subgid ranges if they don't exist for the current user grep "^$(id -un):\|^$(id -u):" /etc/subuid >/dev/null 2>&1 || sudo usermod --add-subuids 100000-165535 $(whoami) grep "^$(id -un):\|^$(id -u):" /etc/subgid >/dev/null 2>&1 || sudo usermod --add-subgids 100000-165535 $(whoami) # Propagate changes to subuid and subgid podman system migrate # Debian requires setting unprivileged_userns_clone if [ -f /proc/sys/kernel/unprivileged_userns_clone ]; then if [ "1" != "$(cat /proc/sys/kernel/unprivileged_userns_clone)" ]; then echo 'kernel.unprivileged_userns_clone=1' | sudo tee -a /etc/sysctl.d/60-rootless.conf sudo sysctl --system fi fi # Fedora requires setting max_user_namespaces if [ -f /proc/sys/user/max_user_namespaces ]; then if [ "0" = "$(cat /proc/sys/user/max_user_namespaces)" ]; then echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.d/60-rootless.conf sudo sysctl --system fi fi # Allow privileged port access if needed if [ -f /proc/sys/net/ipv4/ip_unprivileged_port_start ]; then if [ "1024" = "$(cat /proc/sys/net/ipv4/ip_unprivileged_port_start)" ]; then echo 'net.ipv4.ip_unprivileged_port_start=0' | sudo tee -a /etc/sysctl.d/60-rootless.conf sudo sysctl --system fi fi ``` Enable the Podman socket and verify it’s running (see the [Podman socket activation documentation]()): ``` systemctl --user enable --now podman.socket # You should see `/run/user/1000/podman/podman.sock` (the number may vary): ls $XDG_RUNTIME_DIR/podman/podman.sock # You can also check the socket path with: podman info --format '{{.Host.RemoteSocket.Path}}' ``` Configure the Docker CLI to use Podman (see the [Podman rootless tutorial]()): ``` # View existing contexts docker context ls # Create Podman rootless context docker context create podman-rootless \ --description "Podman (rootless)" \ --docker host="unix://$XDG_RUNTIME_DIR/podman/podman.sock" # Switch to the new context docker context use podman-rootless # Verify it works docker ps ``` > [!WARNING] > > **WSL2: the root filesystem isn’t a shared mount** > > On WSL2, `podman info` and container starts may warn `"/" is not a shared mount, this could cause issues or missing mounts with rootless containers`. This is specific to WSL2: its own boot process mounts the root filesystem before `systemd` starts and never marks it shared, unlike a natively booted Linux, where it’s shared from boot. It hasn’t caused failures in DDEV’s own testing, but to fix it for the current session: > > ``` > sudo mount --make-rshared / > ``` > > That reverts on every `wsl --shutdown` or instance restart. To make it permanent, add to `/etc/wsl.conf`: > > ``` > [boot] > command = mount --make-rshared / > ``` ### Validate the setup DDEV ships a script that checks everything on this page and explains anything it finds. It works with any Podman install, however you got it, and changes nothing unless you ask it to install: ``` curl -fsSL https://raw.githubusercontent.com/ddev/ddev/main/scripts/linux-podman-rootless.sh -o /tmp/podman-rootless.sh bash /tmp/podman-rootless.sh ``` It verifies the Podman and `netavark` version pairing, the socket, `cgroup` manager, `subuid`/`subgid` ranges, privileged ports, the Docker context, and buildx. It then stops inferring and starts testing: it runs a real container, builds an image and loads it back into the engine, and watches a container healthcheck report `healthy`. Those last three are the steps `ddev start` fails at on a machine where everything else looks correct. To perform the whole setup described above instead of just checking it, run the script with `--install`. There’s also `--install-brew` for the Homebrew last resort, which is what DDEV’s CI uses. ### Podman rootless performance optimization What’s slower under Podman rootless is container image and filesystem work: `ddev start`, image builds, `ddev import-db`, `ddev export-db`, `ddev snapshot` (see [Podman run/build performance issues]() and the [Podman performance documentation]()). Serving your site is not — once the containers are up, requests go through the same web server, PHP, and database as under Docker. The storage driver is the biggest lever on the operations that are affected. On Linux kernel 5.13 and newer, Podman uses native rootless `overlay`, which is the fastest option and needs no configuration at all. Check what you’re getting: ``` podman info --format '{{.Store.GraphDriverName}} {{index .Store.GraphStatus "Native Overlay Diff"}}' ``` `overlay true` means you’re already on the fast path — do nothing. If it prints `overlay false`, something has configured the `fuse-overlayfs` mount program, which *disables* native overlay diff; remove `mount_program` from `~/.config/containers/storage.conf` to get the faster native path back. > [!WARNING] > > **Existing containers require a reset** > > If you already have Podman containers, images, or volumes, you’ll need to reset Podman for the storage change to take effect: `podman system reset`. This removes all existing containers, images, and volumes (similar to `docker system prune -a`). ### Podman rootless troubleshooting The Docker CLI hides most Podman-side errors behind a generic message. When something fails, read the real error from the Podman service: ``` journalctl --user -u podman.service --since '-5min' ``` | Symptom | Cause | Fix | | --- | --- | --- | | `unable to upgrade to tcp, received 500` from `docker run`, and containers stuck in `Created` | Your `netavark` major version doesn’t match Podman’s — they’re released as a pair, so Podman 6.x needs `netavark` 2.x and Podman 4.x/5.x need 1.x. The Podman log shows `failed to load network options: invalid type: sequence, expected a map`. The usual cause is a Podman installed outside the distribution’s packages while the distribution’s older `netavark` is still there. | Remove the distribution’s `netavark`/`aardvark-dns` so Podman uses its own, or install a Podman matching them. Don’t mix stacks. | | `could not find "netavark" in one of [...]` | `engine.helper_binaries_dir` is set somewhere. It *replaces* Podman’s helper search path rather than extending it, so if the list omits the `libexec/podman` directory next to your `podman` binary, Podman finds no `netavark` at all — or a wrong-version one. | Remove the `helper_binaries_dir` setting from your `containers.conf` files. | | `failed to load image: ... Source image rejected: ... is rejected by policy`, after a build that ran to completion | Your `policy.json` rejects by default and doesn’t allow the `docker-archive` and `oci-archive` transports that `buildx --load` uses to hand the built image back. Registry pulls go through a different transport and keep working, so pulling, running and building all look healthy. | In the `policy.json` that applies (`~/.config/containers/policy.json` overrides `/etc/containers/policy.json`), add `"docker-archive"` and `"oci-archive"` entries set to `insecureAcceptAnything` under `transports`. That leaves the rest of the policy intact. | | `Cannot connect to the Docker daemon`, and `systemctl --user is-active podman.socket` says `failed` | Repeated activation failures tripped `systemd`’s start limit. Fixing the underlying config doesn’t clear this on its own. | `systemctl --user reset-failed podman.socket podman.service`, then `systemctl --user start podman.socket`. | | `crun: create /sys/fs/cgroup/docker: Permission denied` during a build | Podman is using the `cgroupfs` `cgroup` manager, either because there’s no lingering user session or because something set `cgroup_manager = "cgroupfs"` system-wide. GitHub Actions’ Ubuntu runner image does the latter: it installs Podman from the [`podman-static`]() bundle, which ships that setting in `/etc/containers/containers.conf`. | `sudo loginctl enable-linger $(whoami)`. If `grep -rn cgroup_manager /etc/containers/` finds a setting, override it with `cgroup_manager = "systemd"` under `[engine]` in `~/.config/containers/containers.conf.d/ddev-podman.conf`, which outranks `/etc`. Confirm with `podman info --format '{{.Host.CgroupManager}}'`. | | Containers fail to start, or behave oddly, and `podman info` shows an `ociRuntime` path outside your Podman’s own prefix | Something pins the runtime with `[engine.runtimes]` in a `containers.conf` under `/etc`. GitHub Actions’ runner image pins `crun = ["/usr/local/bin/crun"]`, the statically built `crun` from its Podman 5.x bundle, which then gets used by an unrelated Podman. | Point it back at the `crun` next to your own `podman` — `/usr/bin/crun` for a distribution Podman, `/home/linuxbrew/.linuxbrew/bin/crun` for Homebrew’s — with `[engine.runtimes]` and `crun = [""]` in `~/.config/containers/containers.conf.d/ddev-podman.conf`. | | `compose build requires buildx 0.17.0 or later` | The buildx plugin isn’t installed. | `sudo apt-get install docker-buildx-plugin` | | `Must provide a valid firewall backend, got iptables` | Newer `netavark` dropped the `iptables` backend, but Podman still requests it. | Add `firewall_driver = "nftables"` under `[network]` in `~/.config/containers/containers.conf.d/ddev-podman.conf`. | | `registries.conf must be in v2 format but is in v1` | Podman 6 rejects the old registries format. | Replace `/etc/containers/registries.conf` with `unqualified-search-registries = ["docker.io"]`. | | `Include journald in compilation path to log to systemd journal` | Homebrew’s `conmon` is built without `journald` support, but Podman defaults to `journald` logging. | Set `log_driver = "k8s-file"` under `[containers]` and `events_logger = "file"` under `[engine]` in `~/.config/containers/containers.conf.d/ddev-podman.conf`. | | `failed to become ready ... timed out without becoming healthy` for every container, with an empty health log (`{"Status":"starting","FailingStreak":0,"Log":null}`) | Podman schedules each container’s healthcheck as a transient `systemd` **user** timer. Either your Podman was built without its `systemd` build tag, which makes the timer functions silent no-ops, or your user session cannot create transient units. The healthcheck script itself is fine — running it with `podman exec` succeeds, which is why this is easy to misdiagnose. | Check the tags with `go version -m $(command -v podman) \\| grep -- -tags=`. If `systemd` is missing, that Podman can’t run healthchecks at all and no configuration changes it — use your distro’s or Homebrew’s build instead. Otherwise check `sudo loginctl enable-linger $(whoami)` and that `systemd-run --user --quiet /bin/true` succeeds. | | `Unit podman.socket could not be found` from `systemctl --user status podman.socket`, on a Homebrew install | Homebrew’s systemd user units live under `$(brew --prefix)/lib/systemd/user/`, which isn’t one of `systemctl --user`’s search paths (`~/.config/systemd/user/` is). | `mkdir -p ~/.config/systemd/user && ln -s "$(brew --prefix)"/lib/systemd/user/podman.{socket,service} ~/.config/systemd/user/`, then `systemctl --user daemon-reload`. Note that the units Homebrew ships carry no `PATH`, so symlinking them leads straight to `could not find a working conmon binary` below. `scripts/linux-podman-rootless.sh --install-brew` writes its own units with that `PATH` already set. | | `could not find a working conmon binary`, even though `conmon --version` works in your shell | `systemd --user` services start with a minimal `PATH` that excludes Homebrew’s `bin`, so `podman.service` can’t find `conmon`, `crun`, or other helpers even though your interactive shell can. | Add Homebrew’s `bin` to the service’s `PATH`: `systemctl --user edit podman.service`, add `Environment=PATH=/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin` under `[Service]`, then `systemctl --user daemon-reload && systemctl --user restart podman.socket`. | | `netavark (exit code 127): ... libgcc_s.so.1: cannot open shared object file`, or containers silently get no networking | Homebrew’s Linux dynamic linker doesn’t search the host’s system library paths, and `gcc` — which provides `libgcc_s.so.1` — isn’t installed via Homebrew even though `netavark`/`aardvark-dns` (Rust binaries) need it at runtime. | `brew install gcc` | | `config file not found: no policy.json file found; searched paths: [...]` on a Homebrew install | Homebrew’s Podman ships a default `policy.json` under its own prefix, but nothing links it into a path Podman actually searches (`~/.config/containers/`, `/etc/containers/`, `/usr/share/containers/`). | `mkdir -p ~/.config/containers && cp "$(brew --prefix podman)"/etc/containers/policy.json ~/.config/containers/policy.json` | | `unable to retrieve auth token: invalid username/password: unauthorized: incorrect username or password` pulling a public image | Stale or invalid Docker Hub credentials in `~/.docker/config.json` interfere with anonymous pulls, the same underlying cause as [`403 authentication required`](<#troubleshooting>) below. | `docker logout` | ## Windows First [install WSL2](<#install-wsl2>) below, then [choose a container runtime](<#choose-a-container-runtime>): - **Docker** (recommended) - The best-tested option. Run Docker CE inside WSL2, or use Docker Desktop or Rancher Desktop. - **Docker rootless** - Runs the Docker daemon without root, which reduces the attack surface. Runs inside WSL2 using the same setup as Linux. - **Podman rootless** - Rootless by default; a good fit where Docker is forbidden. Works via Podman Desktop, but is less mature on Windows than on Linux. ### Install WSL2 In PowerShell, run: ``` # Install WSL2; reboot if prompted, then continue: wsl --install --no-distribution # Update WSL2 if previously installed: wsl --update ``` Create a Debian-based WSL2 distro (skip if you’re using Traditional Windows). Ubuntu is recommended: ``` # You'll be asked to set a username and password for the distro: wsl --install Ubuntu-26.04 --name DDEV ``` > [!TIP] > > **"DDEV" is just a suggested name — use any name you like.** > [!TIP] > > **Other Debian-based distros also work** > > The DDEV installer supports Ubuntu and Debian and has been tested on Kali Linux and eLxr. If you prefer one of those, substitute its name, for example `wsl --install Debian --name DDEV`. Verify the “DDEV” distro is set as default: ``` > wsl -l -v NAME STATE VERSION * DDEV Stopped 2 ``` ### Choose a container runtime Install one of the supported Docker providers: - [Docker CE inside WSL2](<#docker-ce-inside-wsl2>): Recommended, most popular, performant, and best-supported. The [DDEV installer]() installs it automatically. Requires WSL2. - [Docker Desktop for Windows](<#docker-desktop-for-windows>): Works with both WSL2 and traditional Windows. Extensive automated testing, but some performance and reliability problems. Not open-source; may require a license. - [Rancher Desktop for Windows](<#rancher-desktop-for-windows>): Free, open-source. Manually tested on traditional Windows; no automated testing. Works with both WSL2 and traditional Windows. ### Docker CE inside WSL2 The [DDEV Windows installer]() automatically installs Docker CE in your WSL2 environment — no manual Docker installation is needed. After installing WSL2 above, proceed to [install DDEV](). To install Docker CE manually instead, open your WSL2 terminal (Ubuntu) and run: ``` # Ensure sudo credentials are cached for copy/paste of this block sudo true # Remove any old/conflicting Docker packages sudo apt-get remove -y docker.io docker-doc docker-compose podman-docker containerd runc 2>/dev/null || true # Install prerequisites sudo apt-get update && sudo apt-get install -y ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings # Add Docker's GPG key sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Remove old repository files if present sudo rm -f /etc/apt/keyrings/docker.gpg /etc/apt/sources.list.d/docker.list # Add Docker repository in deb822 format printf "Types: deb\nURIs: https://download.docker.com/linux/ubuntu\nSuites: %s\nComponents: stable\nSigned-By: /etc/apt/keyrings/docker.asc\n" \ "$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")" \ | sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null # Install Docker CE sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # Add your user to the docker group (create group if it doesn't exist) sudo groupadd -f docker && sudo usermod -aG docker ${SUDO_USER:-$USER} ``` Run `sg docker` to open a new subshell with the `docker` group active, or log out and back in to apply the change to your whole session. On WSL2 systems without `systemd`, you may need to start Docker manually with `sudo service docker start`. > [!TIP] > > **Prefer to run as a script?** > > Create a script file, then run it: > > ``` > cat > /tmp/install-docker.sh << 'SCRIPT' > #!/usr/bin/env bash > set -euo pipefail > sudo true > sudo apt-get remove -y docker.io docker-doc docker-compose podman-docker containerd runc 2>/dev/null || true > sudo apt-get update && sudo apt-get install -y ca-certificates curl > sudo install -m 0755 -d /etc/apt/keyrings > sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc > sudo chmod a+r /etc/apt/keyrings/docker.asc > sudo rm -f /etc/apt/keyrings/docker.gpg /etc/apt/sources.list.d/docker.list > printf "Types: deb\nURIs: https://download.docker.com/linux/ubuntu\nSuites: %s\nComponents: stable\nSigned-By: /etc/apt/keyrings/docker.asc\n" \ > "$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")" \ > | sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null > sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin > sudo groupadd -f docker && sudo usermod -aG docker ${SUDO_USER:-$USER} > SCRIPT > ``` > > Review the script, then run it: `bash /tmp/install-docker.sh` ### Docker Desktop for Windows 1. Download and install Docker Desktop from [docker.com](). 2. During installation, ensure “Use WSL 2 instead of Hyper-V” is selected. 3. After installation, open Docker Desktop settings and navigate to **Resources → WSL Integration**. 4. Enable integration with your Debian-based WSL2 distro (e.g., Ubuntu-26.04, Debian). 5. Apply the changes and restart Docker Desktop if prompted. 6. Verify that `docker ps` works in git-bash, PowerShell, or WSL2, wherever you’re using it. ### Rancher Desktop for Windows 1. Download and install [Rancher Desktop](). 2. During installation, choose **Docker** as the container runtime and disable Kubernetes. 3. After installation, open Rancher Desktop and go to **WSL Integration** in the settings. 4. Enable integration with your Debian-based WSL2 distro (e.g., Ubuntu-26.04, Debian). 5. Apply the changes and restart Rancher Desktop if needed. 6. Verify that `docker ps` works in git-bash, PowerShell, or WSL2, wherever you’re using it. Docker rootless runs inside WSL2 using the same setup as Linux. In your WSL2 distro, follow the [Docker rootless instructions for Linux](<#linux-docker-rootless>). Windows users can run Podman via [Podman Desktop](), which manages the WSL2-based VM for you. Podman on Windows works but is less mature than on Linux. ### Install Podman Install [Podman Desktop](), which includes Podman. Alternatively, install Podman directly following the [official Podman installation guide for Windows](). For more information, see the [Podman tutorials](). ### Configure Podman Setup and configuration follow the same patterns as the Linux/WSL2 setup, but with Podman Desktop managing the VM for you. Follow the [Podman rootless instructions for Linux](<#linux-podman-rootless>). ## GitHub Codespaces You can set up [GitHub Codespaces]() following the instructions in the [DDEV Installation]() section. ## Docker Buildx DDEV requires the [Docker buildx plugin](). GUI-based providers (OrbStack, Docker Desktop, Rancher Desktop) bundle it. If `docker buildx version` doesn’t work, install it: - **macOS (Homebrew):** `brew install docker-buildx` - **Linux/WSL2 (Debian/Ubuntu):** `sudo apt-get install docker-buildx-plugin` (requires the [official Docker repository]() to be configured) - **Other:** see [Docker buildx requirement]() for installation instructions across platforms. > [!TIP] > > **Can’t install the plugin?** > > As a fallback, DDEV can manage its own copy via [`docker_buildx_version`](). ## Running Multiple Container Runtimes You can run Docker and Podman sockets simultaneously and switch between them using Docker contexts. For example, here’s a system with three active Docker contexts: ``` $ docker context ls NAME DESCRIPTION DOCKER ENDPOINT default Current DOCKER_HOST based configuration unix:///var/run/docker.sock podman-rootless * Podman (rootless) unix:///run/user/1000/podman/podman.sock rootless Rootless runtime socket unix:///run/user/1000/docker.sock ``` Switch between them with: ``` docker context use "" ``` ## Switching Runtimes with DDEV DDEV automatically detects your active container runtime. To switch: 1. Stop DDEV projects: ``` ddev poweroff ``` 1. Switch the Docker context or change the `DOCKER_HOST` environment variable. 2. Start your project: ``` ddev start ``` For background on the trade-offs between these runtimes, see [Podman and Docker Rootless in DDEV](). ## Troubleshooting Docker ### Common Connection Errors > `Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?` A message like this can mean that your Docker provider hasn’t been started (or hasn’t been installed). It can also mean that the wrong Docker context is selected, which happens sometimes when people are switching to a new Docker provider. `docker context ls` will show you the available contexts and which one is in use. But this error often indicates that either Docker is not installed or the Docker daemon is not running: - **macOS/Traditional Windows:** Start your Docker provider (e.g., OrbStack, Docker Desktop, Rancher Desktop) from your applications menu - **Linux/WSL2:** Run `sudo systemctl enable --now docker` to start and enable it to start automatically on boot --- > `permission denied while trying to connect to the docker API at unix:///var/run/docker.sock` A message like this means that your user is not in the `docker` group: ``` sudo groupadd -f docker && sudo usermod -aG docker ${SUDO_USER:-$USER} # sg opens a new subshell with the `docker` group active; # log out and back in to apply the change to your full session sg docker ``` --- > `error during connect: Get "http://host:2375/v1.51/version": dial tcp: lookup host on 127.0.0.53:53: server misbehaving` or > `unable to resolve docker endpoint: context "docker-desktop": context not found` These errors indicate Docker context issues: - List available contexts: `docker context ls` - Switch to default (or different) context: `docker context use default` - Validate the current context: `docker ps` > [!WARNING] > > **Environment variables override Docker context** If you have set `DOCKER_HOST` and/or `DOCKER_CONTEXT` environment variables, they will override the `docker context` settings. This can lead to connection issues if the host is unreachable or the specified context is incorrect. Check your shell profile (`~/.bashrc`, `~/.zshrc`) for these variables. > [!TIP] > > **Creating a Docker context** You can create a new Docker context using the `docker context create` command. See [Remote Docker Environments]() for examples. ### Testing Docker Setup For DDEV to work properly, Docker needs to: - Mount your project code directory (typically under your home folder) from host to container - Access TCP ports on the host for HTTP/HTTPS (default ports 80 and 443, configurable per project) Run this command in your *project directory* to verify Docker configuration (use Git Bash if you’re on Windows!): ``` docker run --rm -t -p 80:80 -p 443:443 -v "//$PWD:/tmp/projdir" ddev/ddev-utilities sh -c "echo ---- Project Directory && ls /tmp/projdir" ``` **Expected result:** A list of files in the current directory. Common test command issues: > `port is already allocated` Another service is using ports 80 or 443. See the [Web Server Ports Troubleshooting]() section. > `invalid mount config for type "bind": bind mount source path does not exist` The filesystem path isn’t properly shared with Docker: - **Docker Desktop:** Go to Settings → Resources → File Sharing and add your project directory or drive - **Linux:** Ensure proper permissions on the project directory > `The path (...) is not shared and is not known to Docker` **Docker Desktop:** Add the path in Settings → Resources → File Sharing and restart Docker Desktop after making changes. > `Error response from daemon: Get registry-1.docker.io/v2/` Docker daemon isn’t running, or no internet connection: - Start/restart Docker and verify internet connectivity - Check if corporate firewall blocks Docker Hub access > `403 authentication required` during `ddev start` Stale Docker Hub authentication interfering with public image pulls: - **Solution:** Run `docker logout` and try again - **Note:** Docker authentication is *not* required for normal DDEV operations # Installation Whatever system you’re on, you’ll first need to [install a Docker provider](), then [install DDEV](), and optionally [configure your browser](). For the best experience, consider [performance tuning]() and [enabling shell completion](). # Performance DDEV is continually focused on quick project startup and fast responses to its web requests. DDEV’s performance is mostly an issue of how Docker runs on your workstation. On Linux, including Windows WSL2, Docker is fast. Most people are happy with Linux performance and don’t need to change anything. On macOS and Windows with Docker Desktop, allocated resources and mounted filesystem performance can be significant bottlenecks. Taking a bit of time to optimize your setup can yield massive performance gains. ## Filesystem Performance Mutagen offers a huge performance boost on macOS and Traditional Windows. It’s fast and doesn’t need any setup; It’s enabled by default in those environments, but you have the option of turning it off. Mutagen is enabled by default on Mac and traditional Windows, and it can be disabled per-project or globally. ## Mutagen ### What Mutagen Does The [Mutagen]() asynchronous caching feature is the best way to improve DDEV’s web-serving performance on macOS and Windows, and we recommend it for most projects. It offers enormous webserving performance benefits over most basic Docker providers, and it makes filesystem watchers (`fsnotify`/`inotify`) work correctly. Mutagen decouples in-container reads and writes from reads and writes on the host machine, so each can enjoy near-native speed. A change on the host gets changed “pretty soon” in the container, and a change in the container gets updated “pretty soon” on the host; neither filesystem is stuck waiting on the other one. This “pretty soon” means, however, that there’s a brief window where files on the host may not exactly match the files inside the container—so files that manage to change in both places can lead to conflicts. Docker bind-mounts, the traditional approach to getting your code into DDEV’s web container, check every file access against the file on the host. Docker’s way of doing these checks macOS and Windows is not very performant. Linux and Linux-like systems are faster because Docker provides native file-access performance. While Mutagen works fine and has automated tests for Linux and Windows WSL2, it may not be worth enabling on those systems since it won’t make the dramatic difference it does on macOS and Windows. Mutagen supports filesystem notifications, so file-watchers on both the host and inside the container will be notified when changes occur. This is a great advantage for many development tools, which otherwise have to poll for changes at greater expense. Instead, they can be notified via normal `inotify`/`fsnotify` techniques. ### Enabling and Disabling Mutagen > [!WARNING] > > **Don’t Install Mutagen** > > You do not need to install anything to use Mutagen. DDEV installs and maintains its own `mutagen` binary. On macOS and traditional Windows, Mutagen is enabled globally by default. To disable Mutagen on a single project, run `ddev config --performance-mode=none && ddev restart`. To revert Mutagen back to the default globally controlled mode for a single project, run `ddev config --performance-mode-reset && ddev restart`. (This removes the configuration line so global settings are used instead.) To disable Mutagen globally on all ddev projects, run `ddev mutagen reset && ddev config global --performance-mode=none && ddev config --performance-mode=none`. To then use Mutagen on a specific project, run [`ddev stop`](), enable it with `ddev config --performance-mode=mutagen`, and [`ddev start`]() again. ### Mutagen and User-Generated Uploads When Mutagen is enabled, DDEV attempts to exclude user-generated files in `upload_dirs` (if they exist) from syncing. It does this by using a bind-mount in the generated docker-compose configuration, and excluding the directories from syncing in `.ddev/mutagen/mutagen.yml`. If you have a non-standard location for user-generated files, like `private/fileadmin` with the deprecated `typo3-secure-web` approach, you should override the project defaults by setting `upload_dirs` in `.ddev/config.yaml` and pointing it at the correct directory. This will allow Mutagen to sync correctly. If you change the `upload_dirs`, run `ddev mutagen reset` to let Mutagen know about the changed behavior. ### Mutagen Integration Caveats If you run into an issue with Mutagen, please try to recreate and report it via a [support channel]()! Mutagen has generally been great for those using it, but it’s good to be aware of its trade-offs: - **It may not be the right choice for every project.** Filesystem consistency has been excellent with Mutagen, but performance is its specialty. If consistency is your highest priority, then there are reasons to be cautious. Two-way sync is a very difficult computational problem, and problems *may* surface. - **The initial sync takes longer.** For most projects, the first-time Mutagen sync of a project takes no more than 30 seconds, and subsequent starts take 10 seconds or less. If your project is taking a long time to sync, especially after the initial sync, see [Advanced Configuration](<#advanced-mutagen-configuration-options>) below to figure out what’s taking time and how to bind-mount it. - **Reset if you change `mutagen.yml`.** If you take control of the `mutagen.yml` file and make changes to it, run `ddev mutagen reset` after making changes. - **Avoid file changes when DDEV is stopped.** If you change files—checking out a different branch, removing a file—while DDEV is stopped, Mutagen has no way to know about it. When you start again, it will get the files that are stored and bring them back to the host. If you *do* change files while DDEV is stopped, run `ddev mutagen reset` before restarting the project so Mutagen only starts with awareness of the host’s file contents. - **It modestly increases disk usage.** Mutagen integration increases the size of your project code’s disk usage, because the code exists both on your computer *and* inside a Docker volume. Your user-uploaded files directories (`upload_dirs`) are normally excluded from Mutagen so they’re not a problem for most project types or generic configurations where `upload_dirs` is specified. Take care that you have enough overall disk space, and that on macOS you’ve allocated enough file space in Docker Desktop. If you have other large directories you can [exclude specific directories from getting synced](<#advanced-mutagen-configuration-options>) and use a regular Docker mount for them instead. - **Beware simultaneous changes to the same file in both filesystems.** As we pointed out above, any project likely to change the same file on the host *and* inside the container may encounter conflicts. - **Massive changes can cause problems.** Massive file changes on the host or in the container are the most likely to introduce issues. This integration has been tested extensively with major changes introduced by `ddev composer` and `ddev composer create-project`, but be aware of this issue. Changing Git branches, `npm install`, `yarn install`, or a script that deletes huge sections of the synced data are related behaviors that should raise caution. Again, use `ddev mutagen reset` before restarting the project if you want to be sure Mutagen starts out looking at the host machine’s files. - **Mutagen is asynchronous.** A massive change in either filesystem can result in lag as all changed files are handled. You can use `ddev mutagen monitor` to get a better look at what’s happening. - **You can manually trigger a sync.** [`ddev start`]() and [`ddev stop`]() automatically force a Mutagen sync. You can cause an explicit sync with `ddev mutagen sync` and see syncing status with [`ddev mutagen status`](). - **Be mindful of in-container Composer actions.** If you run Composer actions inside the container with [`ddev ssh`](), it’s a good idea to run [`ddev mutagen sync`]() and make sure they’re synced as soon as possible. Most people won’t notice the difference and Mutagen will get it synced soon enough. - **Perform big Git operations on the host side.** Git actions that change lots of files, like switching branches, are best done on the host side and not inside the container. You may want to do an explicit `ddev mutagen sync` command after doing something like that to be sure all changes are picked up quickly. - **Share projects carefully with non-Mutagen users.** If you share a project with some users that want Mutagen, perhaps on macOS, and other users that don’t want or need it, perhaps on WSL2, don’t check in `.ddev/config.yaml`’s [`performance_mode: "mutagen"`](). Instead, either use [global performance mode configuration]() or add a not-checked-in, project-level `.ddev/config.performance.yaml` solely to include `performance_mode: "mutagen"` in it. That way, only users with that file will have Mutagen enabled. - **Windows symlinks have some Mutagen restrictions.** On macOS and Linux (including WSL2) the default `.ddev/mutagen/mutagen.yml` chooses the `posix-raw` type of symlink handling. (See [mutagen docs]()). This basically means any symlink created will try to sync, regardless of whether it’s valid in the other environment. Mutagen, however, does not support `posix-raw` on traditional Windows, so DDEV uses the `portable` symlink mode. The result is that on Windows, using Mutagen, symlinks must be strictly limited to relative links that are inside the Mutagen section of the project. - **It’s a filesystem feature. Make backups!** If we’ve learned anything from computer file-storage adventures, it’s that backups are always a good idea! - **Large `node_modules` can cause cause slow sync times** When you’re compiling static, front-end assets with tools like `npm` and `yarn`, e.g. Drupal themes, syncing the `node_modules` directory can be very slow. **The solution is to add `node_modules` to the `upload_dirs` list in `.ddev/config.yaml`**, which will exclude it from Mutagen sync while still making it available in the container via Docker bind-mount. **Important:** When overriding `upload_dirs`, you must include any existing CMS-specific defaults (like `sites/default/files` for Drupal) or they will be lost. For example, for a Drupal project: ``` upload_dirs: - sites/default/files # Keep the CMS default - node_modules # Add your exclusion ``` Then run `ddev restart`. This problem can also occur with directories that contain large binaries or fonts, for example. ### Syncing After `git checkout` In general, it’s best practice on most projects to do significant Git operations on the host, but they can be disruptive to the sync. It’s easy to add a Git post-checkout hook to do a `ddev mutagen sync` operation though. Add a `.git/hooks/post-checkout` file to your project and make it executable with `chmod +x .git/hooks/post-checkout`: ``` #!/usr/bin/env bash ddev mutagen sync || true ``` ### Syncing After `yarn`, `npm`, and `pnpm` Actions Actions by those programs can also set off massive filesystem changes. You should run [`ddev mutagen sync`]() in order to get things into sync, or simply wait. ### Advanced Mutagen Configuration Options > [!WARNING] > > **Editing mutagen.yml is rarely needed** > > **In almost all cases, you should use `upload_dirs` in `.ddev/config.yaml` to exclude directories from Mutagen sync** (see example above). This is simpler and doesn’t require understanding Mutagen’s configuration format. Only edit `.ddev/mutagen/mutagen.yml` if you need advanced control beyond what `upload_dirs` provides. The Mutagen project provides [extensive configuration options](). Each DDEV project includes a `.ddev/mutagen/mutagen.yml` file by default, with basic defaults you can override if you remove the `#ddev-generated` line at the beginning of the file. If you need to edit the `.ddev/mutagen/mutagen.yml` file: - Remove the `#ddev-generated` line - Execute a [`ddev mutagen reset`]() to avoid the situation where the Docker volume and Mutagen session still have files from an older configuration. The most common need is to exclude a path from Mutagen syncing: - **Recommended approach:** Add directories to the `upload_dirs` list in `.ddev/config.yaml` (see example above). This automatically excludes them from Mutagen sync while making them available in the container via Docker bind-mount. - **Advanced approach:** Manually configure exclusions in the `paths:` section of the `ignore:` stanza in `.ddev/mutagen/mutagen.yml` and set up custom bind mounts with `docker-compose.*.yaml` files. For example, if you want the `node_modules` subdirectory of the project to be available inside the container, but don’t need Mutagen to be syncing it, you can either: **Option 1 (Recommended - Simple):** Add to `upload_dirs` in `.ddev/config.yaml` (remember to include existing defaults): ``` upload_dirs: - sites/default/files # Keep existing CMS default (if applicable) - node_modules # Add your exclusion ``` Then `ddev restart`. **Option 2 (Advanced):** Manually configure Mutagen exclusion and bind mount: - Take over the `.ddev/mutagen/mutagen.yml` by removing the `#ddev-generated` line. - Add `/web/core/node_modules` to the excluded paths: ``` ignore: paths: - "/web/core/node_modules" ``` - Add a `.ddev/docker-compose.bindmount.yaml`: ``` services: web: volumes: - "../web/core/node_modules:/var/www/html/web/core/node_modules" ``` - `ddev mutagen reset` and `ddev start` to get the new configuration. ### Troubleshooting Mutagen Sync Issues - Please make sure that DDEV projects work *without* Mutagen before troubleshooting it. Run `ddev config --performance-mode=none && ddev restart`. - - Volume sizes (warns if \>5GB, critical if \>10GB) - Upload directories configuration - Sync session status and problems - Large files being synced that could impact performance - Ignore pattern configuration **Run `ddev utility mutagen-diagnose`** to automatically check for common Mutagen issues. This command analyzes: The diagnostic output provides actionable recommendations for improving sync performance. Use `ddev utility mutagen-diagnose --all` to see all Mutagen volumes across all projects. See the [command reference]() for details. - Rename your project’s `.ddev/mutagen/mutagen.yml` file to `.ddev/mutagen/mutagen.yml.bak` and run `ddev restart`. This ensures you’ll have a fresh version in case the file has been changed and `#ddev-generated` removed. - Avoid having Mutagen sync large binaries, which can cause `ddev start` to take a long time. The `.tarballs` directory is automatically excluded, so Mutagen will ignore anything you move there. To see what Mutagen is trying to sync, run `ddev mutagen status -l` in another window. - `DDEV_DEBUG=true ddev start` will provide more information about what’s going on with Mutagen. - DDEV’s Mutagen daemon keeps its data in a DDEV-only `MUTAGEN_DATA_DIRECTORY` in `$HOME/.ddev_mutagen_data_directory`. - DDEV’s private Mutagen binary is installed in `$HOME/.ddev/bin/mutagen` (see [global configuration directory]()). You can use all the features of Mutagen with `ddev utility mutagen`. For example: ``` ddev utility mutagen sync list --template "{{ json (index . 0) }}" | docker run -i --rm ddev/ddev-utilities jq -r ddev utility mutagen sync monitor -l ``` - You can run the [diagnose\_mutagen.sh]() script to gather information about Mutagen’s setup. Please share output from it when creating an issue or seeking support. - Try `ddev poweroff` or `$HOME/.ddev/bin/mutagen daemon stop && $HOME/.ddev/bin/mutagen daemon start` to restart the Mutagen daemon if you suspect it’s hanging. - Use `ddev mutagen reset` if you suspect trouble, and *always* after changing `.ddev/mutagen/mutagen.yml`. This restarts the project’s Mutagen data (Docker volume + Mutagen session) from scratch. - `ddev mutagen monitor` can help watch Mutagen behavior. It’s the same as `$HOME/.ddev/bin/mutagen sync monitor `. - If you’re working on the host and expecting things to show up immediately inside the container, you can learn a lot by running `ddev mutagen monitor` in a separate window as you work. You’ll see when Mutagen responds to your changes and get an idea about how much delay there is. - Consider `ddev stop` before massive file change operations, like moving a directory. - If you get in real trouble, run `ddev stop`, reset your files with Git, and run `ddev mutagen reset` to throw away the Docker volume which may already have incorrect files on it. #### Debugging Long Mutagen Startup Time Normally, a first-time `ddev start` on a new or changed project should only take a minute or less. If it’s taking longer than that, there are likely some huge files or directories that are being synced that we don’t need to sync. (All we really want to sync is PHP files, everything else is a waste. So if we’re syncing fonts or user-generated files or anything else, we want to figure out what it is and stop it. As noted elsewhere here, `node_modules` can cause this behavior.) To see what’s causing the slow syncing try this technique: ``` ddev mutagen reset # Cleans up all synced files so starting from scratch ddev start ``` Then in another terminal window view the syncing behavior with: ``` while true; do ddev mutagen st -l | grep "^Current"; sleep 1; done ``` You’ll see something like this, which may help understand what mutagen is working so hard at. This example is a 5GB file named `dummyfile` in `vendor/bin`. ``` Current file: vendor/bin/dummyfile (306 MB/5.2 GB) Current file: vendor/bin/dummyfile (687 MB/5.2 GB) Current file: vendor/bin/dummyfile (1.1 GB/5.2 GB) Current file: vendor/bin/dummyfile (1.6 GB/5.2 GB) Current file: vendor/bin/dummyfile (2.0 GB/5.2 GB) Current file: vendor/bin/dummyfile (2.4 GB/5.2 GB) Current file: vendor/bin/dummyfile (2.8 GB/5.2 GB) Current file: vendor/bin/dummyfile (3.1 GB/5.2 GB) Current file: vendor/bin/dummyfile (3.5 GB/5.2 GB) Current file: vendor/bin/dummyfile (4.0 GB/5.2 GB) Current file: vendor/bin/dummyfile (4.4 GB/5.2 GB) Current file: vendor/bin/dummyfile (4.8 GB/5.2 GB) Current file: vendor/mck89/peast/lib/Peast/Syntax/Parser.php (66 kB/131 kB) Current file: web/core/tests/Drupal/Tests/Core/Config/Entity/EntityDisplayBaseTest.php (2.0 kB/2.0 kB) ``` #### Advanced Mutagen Troubleshooting You can observe what Mutagen is doing by watching `ddev mutagen monitor` in another terminal window to see the results. However, Mutagen has full logging. You can run it with `ddev mutagen logs`. ### Mutagen Strategies and Design Considerations Mutagen provides enormous speed boosts in everyday usage, but it’s trying desperately under the hood to keep container changes reflected on the host and vice versa. DDEV mounts a fast Docker volume onto `/var/www/html` inside the `web` container, then delegates to the Mutagen daemon (on the host). That daemon has the job of keeping all host project contents in sync with the contents of the Docker volume. Consistency is a high priority for DDEV’s integration, which tries at key points to make sure everything is completely consistent and in sync. The Mutagen daemon’s life cycle and sync sessions are something like this: 1. On `ddev start`, the Mutagen agent will be started if it’s not already running. 2. If there’s already a sync session for the project, it’s resumed. 3. On `ddev stop` the sync session is flushed to ensure consistency, then paused. A synchronous flush happens after any `ddev composer` command, since Composer may cause massive changes to the filesystem inside the container that need to be synced before operation continues. If you need to reset everything for a project, you can do it with `ddev mutagen reset`, which starts the Mutagen session from scratch and removes the Docker volume so it can be recreated from scratch. ### Safe to Use with Other Mutagen Installations DDEV requires and provides a specific version of Mutagen, which you can see running [`ddev version`](). If another `mutagen` instance or daemon is installed on your workstation it doesn’t matter, because DDEV’s version runs separately and uses a different data directory. ## Freeing Up System Resources Every project you run uses system resources, and may compete for those resources. A reasonable practice is to individually stop projects you’re not using. You could also stop all projects with [`ddev poweroff`]() and only start the one you’re working on. [`ddev list`]() will display all your projects along with each one’s status. ## Docker Desktop for Mac Settings Open Docker Desktop’s *Preferences*, and visit *Resources* → *Advanced*. Here you can adjust the CPUs, memory, and disk allocated to Docker. The defaults work well for a small project or two, but you may want to adjust these upward based on your experience. Most people raise the memory allocation to 6GB or higher. The disk allocation almost always needs to be raised to accommodate increased downloaded images. Your experience will determine what to do with CPUs. # PhpStorm Setup If you work with the [PhpStorm]() IDE, you can configure it for tight integration with DDEV and Docker to more easily run actions, access services, enable autocompletion, and enjoy a more convenient development experience. ## Before Starting Make sure to get at least one project going with `ddev start` before trying to set up the plugin, because the plugin assumes it has a project to work with. For Drupal, see [Configuring PhpStorm](). ## DDEV Integration Plugin It’s easiest to use the DDEV Integration Plugin, which you can install from [its landing page]() or by searching the in-app marketplace (*Settings* → *Plugins* → *Marketplace*) for “DDEV”. The integration plugin handles nearly everything on this page automatically, and works on all platforms. Install and enable the plugin, then [set up `phpunit`](<#enabling-phpunit>) since it doesn’t yet handle that for you. > [!NOTE] > > **PHP and Node interpreter automatic setup** > > The plugin automatically adds PHP and Node interpreters to your project. > > If you installed the plugin *after* a project was already open in PhpStorm, you’ll need to manually switch to the newly added PHP and Node interpreters in *Preferences*. Alternatively, close your IDE and delete the `.idea` directory in the project root to let the plugin set them up automatically. ## Manual Setup If you’re not using the DDEV Integration Plugin, you can follow these steps instead: 1. Start your project by running [`ddev start`](). 2. Open the DDEV project. In this example, the project name is `drup` and the site is `drup.ddev.site`. - If you’re on Windows, running PhpStorm on the Windows side but using WSL2 for your DDEV project, open the project as a WSL2 project. In other words, in the *Open* dialog, browse to `\\wsl$\Ubuntu\home\rfay\workspace\drup` (in this example). (If you’re running PhpStorm inside WSL2, there are no special instructions.) 3. Under *Build, Execution, Deployment* → *Docker*, set the correct Docker provider, for example “Colima” or “Docker for Mac”. 4. Set up your project to do normal Xdebug, as described in the [Step Debugging section](). This will result in a PhpStorm “Server” with the proper name, normally the same as the FQDN of the project. In this example, `drup.ddev.site`. (All you have to do here is click the little “bug” to “Start listening for PHP Debug Connections”, run `ddev xdebug on`, and visit a web page and choose the correct mapping from host to server.) 5. Under *File* → *Settings* → *PHP* (Windows) or *Preferences* → *PHP* (macOS), click “…” to the right of “CLI Interpreter”. 1. Use the “+” to select “From Docker, Vagrant, VM…”. 2. Choose “Docker Compose”. 3. Create a “server”; Choose the appropriate Docker provider configured above under *Build, Execution, Deployment* → *Docker*. 4. In the “Path mappings” of the “Server” you may have to map the local paths (which on WSL2 means `/home/...`) to the in-container paths, especially if you have Mutagen enabled. So “Virtual Machine Path” would be `/var/www/html` and “Local path” would be something like `/Users/rfay/workspace/drup` (on macOS) or `\\wsl$\Ubuntu\home\rfay\workspace\drup` on Windows using WSL2. 5. Now back in the “Configure Remote PHP Interpreter” for “Configuration files” use `.ddev/.ddev-docker-compose-full.yaml`. On macOS, you may need to use ⌘ + SHIFT + ., to show hidden dotfiles. 6. Service: `web`. 7. Environment variables: Set the `COMPOSE_PROJECT_NAME` variable to the name of the project prefixed with `ddev`. In the `drup` example this would become `ddev-drup`. You can find the name of the project in je `config.yaml` of your project. The result will be `COMPOSE_PROJECT_NAME=ddev-drup`. Note that the content of COMPOSE\_PROJECT\_NAME needs to be lowercase even when project name itself has uppercase characters. 8. In the CLI interpreter “Lifecycle” select “Connect to existing container”. 9. Here’s an example filled out: ![example configuration]() 6. In the main PHP setup dialog, add an entry to the path mappings, as it doesn’t correctly derive the full path mapping. Add an entry that maps your project location to `/var/www/html`. So in this example, the “Local Path” is `/Users/rfay/workspace/drup` and the “Remote Path” is `/var/www/html`: ![example mapping]() 7. Configure composer under *PHP* → *Composer*. - Use “remote interpreter” - CLI Interpreter will be “web” ## Enabling PHPUnit This assumes you’ll need to use PHPUnit and you already have it installed. 1. Under “Test Frameworks” click “+” to add PHPUnit. - PHPUnit by remote interpreter - Interpreter “DDEV” - Choose “Path to phpunit.phar” and use `/var/www/html/vendor/bin/phpunit`, or wherever your PHPUnit is inside the container. You need PHPUnit properly Composer-installed for your project. For example, for Drupal 10, `ddev composer require --dev --with-all-dependencies drupal/core-dev:^10` and `ddev composer require --dev phpspec/prophecy-phpunit:^2`. - Default configuration file: `/var/www/html/web/core/phpunit.xml` or wherever yours is inside the container. ![Example config]() 2. Open Run/Debug configurations and use “+” to add a PHPUnit configuration. Give it a name. - Test scope (as you wish, by directory or class or whatever). - Interpreter: “web” (the one we set up). ![Run-debug configuration]() 3. Enable Xdebug if you want to debug tests with `ddev xdebug on`. 4. Run the runner you created: ![Example PHPUnit run]() ## PhpStorm Basic Setup on Windows WSL2 It’s possible to use PhpStorm with DDEV on WSL2 in at least three different ways: 1. Run PhpStorm in Windows as usual, opening the project on the WSL2 filesystem at `\\wsl$\` (e.g. `\\wsl$\Ubuntu`). PhpStorm is slow to index files but can be slow to respond to file changes in this mode, although it’s adequate for many people. 2. “Remote Development” in PhpStorm 2022.3+, which has explicit support for WSL2 and actually runs a PhpStorm instance on WSL2. 3. Enabling X11 on Windows and running PhpStorm inside WSL2 as a Linux app. PhpStorm works fine this way; it does require WSL2 graphics support, which is now available on all Windows 10 and Windows 11 systems. We’ll walk through each of these approaches. ### Basics - Start with a working DDEV/WSL2 setup as described in the [docs](). Until that’s all working it doesn’t help to go farther. - If you haven’t used Xdebug with DDEV and PhpStorm before, you’ll want to read the [step debugging instructions](). - Your project absolutely must be in `/home` inside WSL2, which is on the Linux filesystem. Although you could keep your project on the Windows filesystem and access it in WSL2 via `/mnt/c`, the performance is even worse than native Windows. It does work though, but don’t do it. You’ll be miserable. ### PhpStorm Running On Windows Side and Using Docker Desktop With the [DDEV Integration Plugin]() almost everything is already done for you, so use it. Create your project inside WSL2 (on the `/home` partition) and get it started first. 1. Your working project will be on the `/home` partition, so you’ll open it using Windows PhpStorm as `\\wsl$\Ubuntu\home\\...\`. 2. On some systems and some projects it may take a very long time for PhpStorm to index the files. 3. File changes are noticed only by polling, and PhpStorm will complain about this in the lower right, “External file changes sync may be slow”. 4. Temporarily disable your Windows firewall. When you have everything working, you can turn it back on again. 5. Use `ddev start` and `ddev xdebug on`. 6. Click the Xdebug listen button on PhpStorm (the little phone icon) to make it start listening. 7. Set a breakpoint on or near the first line of your `index.php`. 8. Visit the project with a web browser or cURL. You should get a popup asking for mapping of the host-side files to the in-container files. Make sure `/home//.../` is mapped to `/var/www/html`. Debugging should be working. You can step through your code, set breakpoints, view variables, etc. Set the PhpStorm terminal path (*Settings* → *Tools* → *Terminal* → *Shell Path*) to `C:\Windows\System32\wsl.exe`. That way when you use the terminal Window in WSL2 it’s using the Bash shell in WSL2. ### PhpStorm with “Remote Development” Option 1. Open your WSL2 project using *File* → *Remote Development* → *WSL* and choose the distro, then the project. 2. For Xdebug you’ll want to use `ddev config global --xdebug-ide-location=wsl2` because essentially the IDE is running inside WSL2 (and listening there). 3. Use `ddev xdebug on` as normal; test with a simple breakpoint in your `index.php`. ### PhpStorm Inside WSL2 in Linux 1. On Windows 10 or 11 get “Windows Subsystem for Linux” from the Microsoft Store, which now includes graphical support. 2. Edit `/etc/wsl.conf` to enable systemd support, which allows for installing PhpStorm with snap, see [details about WSL2 and systemd](): ``` [boot] systemd=true ``` 3. Run `wsl --shutdown` on the Windows side and wait 10 seconds, then restart your distro. 4. In WSL2, `sudo snap install --classic phpstorm`. 5. DDEV should already be installed as described in [DDEV installation](). 6. `ddev config global --xdebug-ide-location=wsl2` to tell DDEV that the IDE is listening in WSL2 as opposed to listening on the Windows side. 7. Start PhpStorm, `phpstorm &`. 8. Use `ddev start` and `ddev xdebug on`. 9. Click the Xdebug listen button in PhpStorm (the little phone icon) to make it start listening. 10. Set a breakpoint on or near the first line of your `index.php`. 11. Visit the project with a web browser or cURL. You should get a popup asking for mapping of the host-side files to the in-container files. Make sure `/home//.../` is mapped to `/var/www/html`. Debugging should be working! You can step through your code, set breakpoints, view variables, etc. # Shell Completion & Autocomplete Most people like to have shell completion on the command line. In other words, when you’re typing a command, you can hit `` and the shell will show you what the options are. For example, if you type `ddev `, you’ll see all the possible commands. `ddev utility ` will show you the options for the command. And `ddev list -` will show you all the flags available for [`ddev list`](). Shells like Bash and Zsh need help to do this though, they have to know what the options are. DDEV provides the necessary hint scripts, and if you use Homebrew, they get installed automatically. ## macOS Bash with Homebrew The easiest way to use Bash completion on macOS is install it with Homebrew ([docs]()). `brew install bash-completion`. When you install it though, it will warn you with something like this, which **may vary on your system**. Add the following line to your `~/.bash_profile` file (or if that doesn’t exist, to your `~/.profile`: ``` [[ -r "$(brew --prefix)/etc/profile.d/bash_completion.sh" ]] && . "$(brew --prefix)/etc/profile.d/bash_completion.sh" ``` > [!NOTE] > > **Bash profile** > > You must add the include to your `.bash_profile` or `.profile` or nothing will work. Use `source ~/.bash_profile` or `source ~/.profile` to make it take effect immediately in your current terminal window. Link completions by running `brew completions link`. When you install DDEV via Homebrew, each new release will automatically get a refreshed completions script. ## macOS Zsh with Homebrew To make Homebrew completions available in Zsh the Homebrew-managed path `zsh/site-functions` has to be added to the FPATH variable. Add the following block to your `~/.zshrc` file (see [docs]()): ``` if type brew &>/dev/null then FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}" autoload -Uz compinit compinit fi ``` Note that the updating of the FPATH variable has to happen before the Zsh completion index is initialized with `compinit`. Oh My Zsh is calling `compinit` for you when `oh-my-zsh.sh` is sourced (see [docs]()). Instead of adding the block that was necessary for `macOS Zsh` place the following line right before the `oh-my-zsh.sh` file is sourced in your `~/.zshrc` file: ``` FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}" ``` To avoid any potential caching issue remove and rebuild the `.zcompdump` file, which is the index for Zsh completions: ``` rm -f ~/.zcompdump; compinit ``` In case you run into any `zsh compinit: insecure directories` warnings, run: ``` chmod -R go-w "$(brew --prefix)/share" ``` ## macOS Fish with Homebrew `fish` shell completions are automatically installed at `/usr/local/share/fish/vendor_completions.d/ddev_fish_completion.sh` when you install DDEV via Homebrew. If you installed `fish` without Homebrew, you can extract the fish completions (`ddev_fish_completion.sh`) from the tar archive of completion scripts included with each release. See [below](<#tar-archive-of-completion-scripts-for-manual-deployment>). ## Bash/Zsh/Fish on Linux including WSL2 On Debian and Yum based systems, if you installed DDEV using `apt-get install ddev`, the `bash`, `zsh`, and `fish` completions should be automatically installed at `/usr/share/bash-completion/completions/ddev`, `/usr/share/zsh/vendor-completions/_ddev` and `/usr/share/fish/completions/ddev.fish` respectively, and the `bash` completions should be automatically installed at `/usr/share/bash-completion/completions/bash`. Otherwise, you can download the completion files for manual installation as described [below](<#tar-archive-of-completion-scripts-for-manual-deployment>). Every Linux distro requires a different manual installation technique. On Debian/Ubuntu, you could deploy the `ddev_bash_completion.sh` script where it needs to be by running `sudo mkdir -p /usr/share/bash-completion/completions && sudo cp ddev_bash_completion.sh /usr/share/bash-completion/completions/ddev`. ## Git Bash Git Bash completions (`ddev_bash_completion.sh`) are provided in the tar archive of completion scripts included with each release. See [below](<#tar-archive-of-completion-scripts-for-manual-deployment>). Completions in Git Bash are sourced from at least the `~/bash_completion.d` directory. You can copy `ddev_bash_completion.sh` to that directory by running `mkdir -p ~/bash_completion.d && cp ddev_bash_completion.sh ~/bash_completion.d/ddev.bash`. ## PowerShell PowerShell completions (`ddev_powershell_completion.ps1`) are provided in the tar archive of completion scripts included with each release. See [below](<#tar-archive-of-completion-scripts-for-manual-deployment>). You can run the `ddev_powershell_completion.ps1` script manually or install it so it will be run whenever PS is opened using the technique described at [Run PowerShell Script When You Open PowerShell](). ## tar Archive of Completion Scripts for Manual Deployment Although most people will use techniques like Homebrew for installation, a tar archive of shell completion scripts for various shells is available in each release, called `ddev_shell_completion_scripts..tar.gz`. If you need to manually install, you can download the files and extract them with the following commands, replacing the VERSION number in the first line with your version: ``` VERSION=v1.24.0 curl -sSLf https://github.com/ddev/ddev/releases/download/${VERSION}/ddev_shell_completion_scripts.${VERSION}.tar.gz tar -zxf ddev_shell_completion_scripts.${VERSION}.tar.gz ``` Alternatively, you could download the tar archive using a browser, from a URL such as the following, replacing the version numbers with your version: [https://github.com/ddev/ddev/releases/download/v1.22.6/ddev\_shell\_completion\_scripts.v1.22.6.tar.gz](). After extracting the archive, copy the appropriate completion script where you need it, for example by running `sudo cp ddev_bash_completion.sh /etc/bash_completion.d/ddev`. Detailed instructions for various shells are given above. # Starting a Project Once [DDEV is installed](), setting up a new project should be quick: 1. Clone or create the code for your project. 2. `cd` into the project directory and run [`ddev config`]() to initialize a DDEV project. 3. Run [`ddev start`]() to spin up the project. 4. Run [`ddev launch`]() to open your project in a browser. You can also type `ddev` to manage your projects from an [interactive dashboard](). DDEV can automatically detect your project type and docroot. If it guessed wrong or there’s something else you want to change, use the [`ddev config`]() command or update [project options]() by editing `.ddev/config.yaml`. You can see the result by running [`ddev describe`](). > [!TIP] > > **What is the `php` project type?** > > A `php` project type is the most general, ready for whatever modern PHP or static HTML/JS project you might be working on. It’s as full-featured as other [CMS-specific options](), without any assumptions about your configuration or presets. (You can use this with a CMS or framework fine!) If you need to configure your app to connect to the database, the hostname, username, password, and database name are all `db`. While you’re getting your bearings, use [`ddev describe`]() to get project details, and [`ddev help`]() to investigate commands. Next, you may want to run [`ddev composer install`](), [import a database](), or [load user-managed files](). If you’re new to DDEV, check out [Using the `ddev` Command]() for an overview of what’s available. # CMS Quickstarts DDEV is [ready to go]() with generic project types for PHP frameworks, and more specific project types for working with popular platforms and CMSes. To learn more about how to manage projects in DDEV visit [Managing Projects](). Before proceeding, make sure your installation of DDEV is up to date. In a new and empty project folder, using your favorite shell, run the following commands: ## Asterios Use a new or existing Composer project, or clone a Git repository. The Asterios project type can be used for [Scaffolding]() and others, as it is used with basic Asterios. DDEV automatically updates or creates the `.env` file with the database information. Create the project directory and configure DDEV: ``` mkdir -p my-asterios-site && cd my-asterios-site ddev config --project-type=asterios --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Asterios via Composer: ``` ddev composer create-project asterios/app ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-asterios.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-asterios-site && cd my-asterios-site > ddev config --project-type=asterios --docroot=public > ddev start -y > ddev composer create-project asterios/app > ddev launch > EOF > chmod +x setup-asterios.sh > ./setup-asterios.sh > ``` ## Backdrop You can start a new [Backdrop]() project or configure an existing one. Create the project directory and configure DDEV: ``` mkdir -p my-backdrop-site && cd my-backdrop-site ddev config --project-type=backdrop # Add the official Bee CLI add-on ddev add-on get backdrop-ops/ddev-backdrop-bee ``` Start DDEV (this may take a minute): ``` ddev start ``` Download Backdrop core and create admin user: ``` # Download Backdrop core ddev bee download-core # Create admin user ddev bee si --username=admin --password=Password123 --db-name=db --db-user=db --db-pass=db --db-host=db --auto ``` Launch the site: ``` # Login using `admin` user and `Password123` password ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-backdrop.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-backdrop-site && cd my-backdrop-site > ddev config --project-type=backdrop > ddev add-on get backdrop-ops/ddev-backdrop-bee > ddev start -y > ddev bee download-core > ddev bee si --username=admin --password=Password123 --db-name=db --db-user=db --db-pass=db --db-host=db --auto > ddev launch > EOF > chmod +x setup-backdrop.sh > ./setup-backdrop.sh > ``` You can start using DDEV with an existing project, too—but make sure you have a database backup handy! Create project directory and clone your repository: ``` mkdir -p my-backdrop-site && cd my-backdrop-site git clone https://github.com/ddev/test-backdrop.git . ``` Configure DDEV: ``` ddev config --project-type=backdrop ``` Add the official Bee CLI add-on: ``` ddev add-on get backdrop-ops/ddev-backdrop-bee ``` Start DDEV (this may take a minute): ``` ddev start ``` Import database and files backups: ``` ddev import-db --file=/path/to/db.sql.gz ddev import-files --source=/path/to/files.tar.gz ddev bee cc all ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-backdrop-existing.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-backdrop-site && cd my-backdrop-site > git clone https://github.com/ddev/test-backdrop.git . > ddev config --project-type=backdrop > ddev add-on get backdrop-ops/ddev-backdrop-bee > ddev start -y > ddev import-db --file=/path/to/db.sql.gz > ddev import-files --source=/path/to/files.tar.gz > ddev bee cc all > ddev launch > EOF > chmod +x setup-backdrop-existing.sh > ./setup-backdrop-existing.sh > ``` ## CakePHP You can start a new [CakePHP]() project or configure an existing one. The CakePHP project type can be used with any CakePHP project \>= 3.x, but it has been fully tested with CakePHP 5.x. DDEV automatically creates the `.env` file with the database information, email transport configuration and a random salt. If `.env` file already exists, `.env.ddev` will be created, so you can take any variable and put it into your `.env` file. Please note that you will need to change the PHP version to 7.4 to be able to work with CakePHP 3.x. Create the project directory and configure DDEV: ``` mkdir -p my-cakephp-composer-site && cd my-cakephp-composer-site ddev config --project-type=cakephp --docroot=webroot ``` Start DDEV (this may take a minute): ``` ddev start ``` Install CakePHP via Composer: ``` ddev composer create-project --prefer-dist --no-interaction cakephp/app:~5.0 ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-cakephp.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-cakephp-composer-site && cd my-cakephp-composer-site > ddev config --project-type=cakephp --docroot=webroot > ddev start -y > ddev composer create-project --prefer-dist --no-interaction cakephp/app:~5.0 > ddev launch > EOF > chmod +x setup-cakephp.sh > ./setup-cakephp.sh > ``` Clone the repository and configure DDEV: ``` git clone my-cakephp-git-site cd my-cakephp-git-site ddev config --project-type=cakephp --docroot=webroot ``` Start DDEV (this may take a minute): ``` ddev start ``` Install dependencies and launch: ``` ddev composer install ddev cake ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-cakephp-git.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > git clone my-cakephp-git-site > cd my-cakephp-git-site > ddev config --project-type=cakephp --docroot=webroot > ddev start -y > ddev composer install > ddev cake > ddev launch > EOF > chmod +x setup-cakephp-git.sh > ./setup-cakephp-git.sh > ``` ## CiviCRM (Standalone) [CiviCRM Standalone]() allows running [CiviCRM]() without a CMS. Visit [Install CiviCRM (Standalone)]() for more installation details. Create the project directory and configure DDEV: ``` mkdir -p my-civicrm-site && cd my-civicrm-site ddev config --project-type=php --composer-root=core --upload-dirs=public/media ``` Start DDEV (this may take a minute): ``` ddev start ``` Download and extract CiviCRM: ``` ddev exec "curl -fsSL https://download.civicrm.org/latest/civicrm-STABLE-standalone.tar.gz -o /tmp/civicrm-standalone.tar.gz" ddev exec "tar --strip-components=1 -xzf /tmp/civicrm-standalone.tar.gz" ddev composer require civicrm/cli-tools --no-scripts ``` Install CiviCRM (or use `ddev launch` to install manually): ``` # You can install CiviCRM manually in your browser using `ddev launch` # and selecting `db` for the server and `db` for database/username/password # or do the same automatically using the command below: # The parameter `-m loadGenerated=1` includes sample data ddev exec cv core:install \ --cms-base-url='$DDEV_PRIMARY_URL' \ --db=mysql://db:db@db/db \ -m loadGenerated=1 \ -m extras.adminUser=admin \ -m extras.adminPass=admin \ -m extras.adminEmail=admin@example.com ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-civicrm.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-civicrm-site && cd my-civicrm-site > ddev config --project-type=php --composer-root=core --upload-dirs=public/media > ddev start -y > ddev exec "curl -fsSL https://download.civicrm.org/latest/civicrm-STABLE-standalone.tar.gz -o /tmp/civicrm-standalone.tar.gz" > ddev exec "tar --strip-components=1 -xzf /tmp/civicrm-standalone.tar.gz" > ddev composer require civicrm/cli-tools --no-scripts > ddev exec cv core:install \ > --cms-base-url='$DDEV_PRIMARY_URL' \ > --db=mysql://db:db@db/db \ > -m loadGenerated=1 \ > -m extras.adminUser=admin \ > -m extras.adminPass=admin \ > -m extras.adminEmail=admin@example.com > ddev launch > EOF > chmod +x setup-civicrm.sh > ./setup-civicrm.sh > ``` ## CodeIgniter Use a new or existing Composer project, or clone a Git repository. DDEV automatically updates or creates the `.env` file with the database information. Create the project directory and configure DDEV: ``` mkdir -p my-ci4-site && cd my-ci4-site ddev config --project-type=codeigniter --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install CodeIgniter via Composer: ``` ddev composer create-project codeigniter4/appstarter ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-codeigniter.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-ci4-site && cd my-ci4-site > ddev config --project-type=codeigniter --docroot=public > ddev start -y > ddev composer create-project codeigniter4/appstarter > ddev launch > EOF > chmod +x setup-codeigniter.sh > ./setup-codeigniter.sh > ``` ## Contao Further information on the DDEV procedure can also be found in the [Contao documentation](). Create the project directory and configure DDEV: ``` mkdir -p my-contao-composer-site && cd my-contao-composer-site ddev config --project-type=php --docroot=public --webserver-type=apache-fpm --php-version=8.4 ``` Install Contao via Composer (this may take a minute): ``` ddev composer create-project contao/managed-edition:5.7 ``` Configure database and mailer settings: ``` ddev dotenv set .env.local --database-url=mysql://db:db@db:3306/db --mailer-dsn=smtp://localhost:1025 ``` Create the database: ``` ddev exec contao-console contao:migrate --no-interaction ``` Create backend user: ``` ddev exec contao-console contao:user:create --username=admin --name=Administrator --email=admin@example.com --language=en --password=Password123 --admin ``` Launch the administration area: ``` ddev launch contao ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-contao.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-contao-composer-site && cd my-contao-composer-site > ddev config --project-type=php --docroot=public --webserver-type=apache-fpm --php-version=8.4 > ddev composer create-project contao/managed-edition:5.7 > ddev dotenv set .env.local --database-url=mysql://db:db@db:3306/db --mailer-dsn=smtp://localhost:1025 > ddev exec contao-console contao:migrate --no-interaction > ddev exec contao-console contao:user:create --username=admin --name=Administrator --email=admin@example.com --language=en --password=Password123 --admin > ddev launch contao > EOF > chmod +x setup-contao.sh > ./setup-contao.sh > ``` Like most PHP projects, Contao could be installed and updated with Composer. The [Contao Manager]() is a tool that provides a graphical user interface to manage a Contao installation. Create the project directory and configure DDEV: ``` mkdir -p my-contao-manager-site && cd my-contao-manager-site ddev config --project-type=php --docroot=public --webserver-type=apache-fpm --php-version=8.4 ``` Configure database and mailer settings: ``` ddev dotenv set .env.local --database-url=mysql://db:db@db:3306/db --mailer-dsn=smtp://localhost:1025 ``` Start DDEV (this may take a minute): ``` ddev start ``` Download the Contao Manager: ``` ddev exec "wget -O public/contao-manager.phar.php https://download.contao.org/contao-manager/stable/contao-manager.phar" ``` Launch the Contao Manager and follow the setup wizard: ``` ddev launch contao-manager.phar.php ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-contao-manager.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-contao-manager-site && cd my-contao-manager-site > ddev config --project-type=php --docroot=public --webserver-type=apache-fpm --php-version=8.4 > ddev dotenv set .env.local --database-url=mysql://db:db@db:3306/db --mailer-dsn=smtp://localhost:1025 > ddev start -y > ddev exec "wget -O public/contao-manager.phar.php https://download.contao.org/contao-manager/stable/contao-manager.phar" > ddev launch contao-manager.phar.php > EOF > chmod +x setup-contao-manager.sh > ./setup-contao-manager.sh > ``` The [Contao demo website]() is maintained for the currently supported Contao versions and can be [optionally installed](). Via the Contao Manager you can select this option during the first installation. ## Craft CMS Start a new [Craft CMS]() project or retrofit an existing one. DDEV injects a number of special environment variables into the container (via `.ddev/.env.web`) that [automatically configure]() Craft’s database connection and the project’s primary site URL. You may opt out of this behavior with the [`disable_settings_management`]() setting. > [!TIP] > > **Compatibility with Craft CMS 3** > > The `craftcms` project works best with configuration features that became available in Craft CMS 4.x. If you are using Craft CMS 3.x or earlier, you may want to use the `php` project type and explicitly define [database connection details]() via [Craft’s `db.php`](). Create the project directory and configure DDEV: ``` mkdir -p my-craft-site && cd my-craft-site ddev config --project-type=craftcms --docroot=web ``` Start DDEV (this may take a minute): ``` ddev start ``` Scaffold a new project with Composer: ``` ddev composer create-project craftcms/craft ``` Craft’s setup wizard will start automatically! [Third-party starter projects]() can be substituted for `craftcms/craft` when running `ddev composer create-project`. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-craft.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-craft-site && cd my-craft-site > ddev config --project-type=craftcms --docroot=web > ddev start -y > ddev composer create-project craftcms/craft > EOF > chmod +x setup-craft.sh > ./setup-craft.sh > ``` You can start using DDEV with an existing Craft project, too. All you need is the codebase and a database backup! Clone the repository (or navigate to a local project directory): ``` git clone https://github.com/example/example-site my-craft-site cd my-craft-site ``` Configure DDEV: ``` ddev config --project-type=craftcms --docroot=web ``` Start DDEV and install Composer packages: ``` ddev start ddev composer install ``` Import database backup and launch: ``` ddev craft db/restore /path/to/db.sql.gz ddev launch ``` Craft CMS projects use MySQL 8.0, by default. You can override this setting (and the PHP version) during setup with [`config` command flags]() or after setup via the [configuration files](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-craft-existing.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > git clone https://github.com/example/example-site my-craft-site > cd my-craft-site > ddev config --project-type=craftcms --docroot=web > ddev start -y > ddev composer install > ddev craft db/restore /path/to/db.sql.gz > ddev launch > EOF > chmod +x setup-craft-existing.sh > ./setup-craft-existing.sh > ``` ### Running Craft in a Subdirectory Set [`composer_root`]() to the subdirectory where Craft is installed. For example, `ddev config --composer-root=app`. > [!TIP] > > **Installing Craft** > > Read more about installing Craft in the [official documentation](). ## Drupal Create the project directory and configure DDEV: ``` mkdir -p my-drupal11-site && cd my-drupal11-site ddev config --project-type=drupal11 --docroot=web ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Drupal via Composer: ``` ddev composer create-project drupal/recommended-project ddev composer require drush/drush ``` Run Drupal installation and launch: ``` ddev drush site:install --account-name=admin --account-pass=admin -y ddev launch # or automatically log in with: ddev launch $(ddev drush uli) ``` Read more about [Drupal Core]() & [Documentation](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-drupal11.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-drupal11-site && cd my-drupal11-site > ddev config --project-type=drupal11 --docroot=web > ddev start -y > ddev composer create-project drupal/recommended-project > ddev composer require drush/drush > ddev drush site:install --account-name=admin --account-pass=admin -y > ddev launch > EOF > chmod +x setup-drupal11.sh > ./setup-drupal11.sh > ``` Create the project directory and configure DDEV: ``` mkdir -p my-drupalcms-site && cd my-drupalcms-site ddev config --project-type=drupal11 --docroot=web ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Drupal CMS via Composer: ``` ddev composer create-project drupal/cms # Note: recipe-unpack runs automatically in DDEV v1.25.0+ ddev composer drupal:recipe-unpack ``` Launch the site: ``` ddev launch ``` Read more about [Drupal CMS]() & [Documentation](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-drupal-cms.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-drupalcms-site && cd my-drupalcms-site > ddev config --project-type=drupal11 --docroot=web > ddev start -y > ddev composer create-project drupal/cms > # Note: recipe-unpack runs automatically in DDEV v1.25.0+ > ddev composer drupal:recipe-unpack > ddev launch > EOF > chmod +x setup-drupal-cms.sh > ./setup-drupal-cms.sh > ``` Create the project directory and configure DDEV: ``` mkdir -p my-drupal10-site && cd my-drupal10-site ddev config --project-type=drupal10 --docroot=web ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Drupal via Composer: ``` ddev composer create-project "drupal/recommended-project:^10" ddev composer require drush/drush ``` Run Drupal installation and launch: ``` ddev drush site:install --account-name=admin --account-pass=admin -y ddev launch # or automatically log in with: ddev launch $(ddev drush uli) ``` Read more about [Drupal Core]() & [Documentation](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-drupal10.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-drupal10-site && cd my-drupal10-site > ddev config --project-type=drupal10 --docroot=web > ddev start -y > 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 launch > EOF > chmod +x setup-drupal10.sh > ./setup-drupal10.sh > ``` Clone your Drupal repository: ``` git clone https://github.com/example/my-drupal-site cd my-drupal-site ``` Configure DDEV (follow prompts): ``` ddev config ``` Start DDEV and launch: ``` ddev start ddev launch /install.php ``` Drupal 7 doesn’t know how to redirect from the front page to `/install.php` if the database is not set up but the settings files *are* set up, so launching with `/install.php` gets you started with an installation. You can also run `drush site-install`, then `ddev exec drush site-install --yes`. See [Importing a Database](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-drupal67.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > git clone https://github.com/example/my-drupal-site > cd my-drupal-site > ddev config > ddev start -y > ddev launch /install.php > EOF > chmod +x setup-drupal67.sh > ./setup-drupal67.sh > ``` This example shows how to work on an existing Drupal project, using a clone of a test repository. Replace with your Git repository. Clone your web project repository: ``` PROJECT_GIT_URL=https://github.com/ddev/test-drupal11.git git clone ${PROJECT_GIT_URL} my-drupal11-site cd my-drupal11-site ``` Configure and start DDEV: ``` ddev config --project-type=drupal11 --docroot=web ddev start ``` Install dependencies and set up Drupal: ``` ddev composer install ddev drush site:install --account-name=admin --account-pass=admin -y ``` Launch the site: ``` ddev launch ``` Read more about [Drupal Core]() & [Documentation](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-drupal-git.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > PROJECT_GIT_URL=https://github.com/ddev/test-drupal11.git > git clone ${PROJECT_GIT_URL} my-drupal11-site > cd my-drupal11-site > ddev config --project-type=drupal11 --docroot=web > ddev start -y > ddev composer install > ddev drush site:install --account-name=admin --account-pass=admin -y > ddev launch > EOF > chmod +x setup-drupal-git.sh > ./setup-drupal-git.sh > ``` Those who want to try out the unreleased upcoming Drupal 12 can install it this way: ``` mkdir -p my-drupal12-site && cd my-drupal12-site ddev config --project-type=drupal12 --docroot=web ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Drupal via Composer: ``` ddev composer create-project drupal/recommended-project:main-dev@dev ddev composer require drush/drush ``` Run Drupal installation and launch: ``` ddev drush site:install --account-name=admin --account-pass=admin -y ddev launch # or automatically log in with: ddev launch $(ddev drush uli) ``` Read more about [Drupal Core]() & [Documentation](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-drupal12.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-drupal12-site && cd my-drupal12-site > ddev config --project-type=drupal12 --docroot=web > ddev start -y > ddev composer create-project drupal/recommended-project:main-dev@dev > ddev composer require drush/drush > ddev drush site:install --account-name=admin --account-pass=admin -y > ddev launch > EOF > chmod +x setup-drupal12.sh > ./setup-drupal12.sh > ``` ## ExpressionEngine Create the project directory: ``` mkdir -p my-ee-zip-site && cd my-ee-zip-site ``` Download and extract the latest ExpressionEngine release: ``` DOWNLOAD_URL=$(curl -fsSL https://api.github.com/repos/ExpressionEngine/ExpressionEngine/releases/latest | docker run -i --rm ddev/ddev-utilities jq -r '.assets | map(select(.name | test("^ExpressionEngine.*\\.zip$")))[0].browser_download_url') curl -fsSL -o ee.zip "${DOWNLOAD_URL}" unzip ee.zip && rm -f ee.zip ``` Configure and start DDEV: ``` ddev config --database=mysql:8.0 ddev start ``` Launch the installation wizard: ``` ddev launch /admin.php ``` When the installation wizard prompts for database settings, enter `db` for the *DB Server Address*, *DB Name*, *DB Username*, and *DB Password*. Visit your site. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-ee.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-ee-zip-site && cd my-ee-zip-site > DOWNLOAD_URL=$(curl -fsSL https://api.github.com/repos/ExpressionEngine/ExpressionEngine/releases/latest | docker run -i --rm ddev/ddev-utilities jq -r '.assets | map(select(.name | test("^ExpressionEngine.*\\.zip$")))[0].browser_download_url') > curl -fsSL -o ee.zip "${DOWNLOAD_URL}" > unzip ee.zip && rm -f ee.zip > ddev config --database=mysql:8.0 > ddev start -y > ddev launch /admin.php > EOF > chmod +x setup-ee.sh > ./setup-ee.sh > ``` Follow these steps based on the [ExpressionEngine Git Repository README.md](): Create the project directory and clone the repository: ``` mkdir -p my-ee-git-site && cd my-ee-git-site git clone https://github.com/ExpressionEngine/ExpressionEngine . ``` Configure and start DDEV: ``` ddev config --database=mysql:8.0 ddev start ``` Install Composer dependencies and prepare installation: ``` ddev composer install touch system/user/config/config.php echo "EE_INSTALL_MODE=TRUE" >.env.php ``` Launch the installation wizard: ``` ddev launch /admin.php ``` When the installation wizard prompts for database settings, enter `db` for the *DB Server Address*, *DB Name*, *DB Username*, and *DB Password*. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-ee-git.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-ee-git-site && cd my-ee-git-site > git clone https://github.com/ExpressionEngine/ExpressionEngine . > ddev config --database=mysql:8.0 > ddev start -y > ddev composer install > touch system/user/config/config.php > echo "EE_INSTALL_MODE=TRUE" >.env.php > ddev launch /admin.php > EOF > chmod +x setup-ee-git.sh > ./setup-ee-git.sh > ``` ## Generic The [`webserver_type: generic`]() allows you to define your own web server process(es) and exposed ports for projects that don’t use the standard `nginx-fpm` or `apache-fpm` configurations. > [!TIP] > > **Looking for more advanced generic web server examples?** > > Check out the [Node.js](<#nodejs>) and [Wagtail](<#wagtail-python-generic>) examples below. > > See also the [ddev-frankenphp]() add-on, which uses the `generic` webserver under the hood. This trivial example demonstrates running PHP’s built-in web server inside DDEV’s web container. The `ddev-webserver` container will not start the default `nginx` or `php-fpm` daemons—the PHP built-in server will handle all requests. You probably wouldn’t find this useful compared to the normal `nginx-fpm` or `apache-fpm` configurations, but it’s offered here as an example of how the `generic` webserver type works. Create the project directory and configure DDEV: ``` export GENERIC_SITENAME=my-generic-site mkdir -p ${GENERIC_SITENAME} && cd ${GENERIC_SITENAME} ddev config --project-type=php ``` Create a sample PHP info page: ``` echo "" > index.php ``` Configure the web server to run PHP’s built-in server: ``` cat <<'EOF' > .ddev/config.php-server.yaml webserver_type: generic web_extra_daemons: - name: "php-server" command: "php -S 0.0.0.0:8000 -t \"${DDEV_DOCROOT:-.}\"" directory: /var/www/html web_extra_exposed_ports: - name: "php-server" container_port: 8000 http_port: 80 https_port: 443 EOF ``` Start DDEV (this may take a minute): ``` ddev start ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-generic.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > export GENERIC_SITENAME=my-generic-site > mkdir -p ${GENERIC_SITENAME} && cd ${GENERIC_SITENAME} > ddev config --project-type=php > echo "" > index.php > cat <<'INNEREOF' > .ddev/config.php-server.yaml > webserver_type: generic > web_extra_daemons: > - name: "php-server" > command: "php -S 0.0.0.0:8000 -t \"${DDEV_DOCROOT:-.}\"" > directory: /var/www/html > web_extra_exposed_ports: > - name: "php-server" > container_port: 8000 > http_port: 80 > https_port: 443 > INNEREOF > ddev start -y > ddev launch > EOF > chmod +x setup-generic.sh > ./setup-generic.sh > ``` ## Grav Create the project directory and configure DDEV: ``` mkdir -p my-grav-composer-site && cd my-grav-composer-site ddev config --php-version=8.3 --omit-containers=db ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Grav via Composer: ``` ddev composer create-project getgrav/grav ``` Install the [Admin2]() plugin and launch: ``` ddev exec gpm install admin2 -y ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-grav.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-grav-composer-site && cd my-grav-composer-site > ddev config --php-version=8.3 --omit-containers=db > ddev start -y > ddev composer create-project getgrav/grav > ddev exec gpm install admin2 -y > ddev launch > EOF > chmod +x setup-grav.sh > ./setup-grav.sh > ``` Create the project directory and clone Grav: ``` mkdir -p my-grav-git-site && cd my-grav-git-site git clone -b master https://github.com/getgrav/grav.git . ``` Configure DDEV: ``` ddev config --php-version=8.3 --omit-containers=db ``` Start DDEV (this may take a minute): ``` ddev start ``` Install dependencies and initialize Grav: ``` ddev composer install ddev exec grav install ``` Install the [Admin2]() plugin and launch: ``` ddev exec gpm install admin2 -y ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-grav-git.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-grav-git-site && cd my-grav-git-site > git clone -b master https://github.com/getgrav/grav.git . > ddev config --php-version=8.3 --omit-containers=db > ddev start -y > ddev composer install > ddev exec grav install > ddev exec gpm install admin2 -y > ddev launch > EOF > chmod +x setup-grav-git.sh > ./setup-grav-git.sh > ``` > [!TIP] > > **How to update?** > > Upgrade Grav core: > > ``` > ddev exec gpm selfupgrade -f > ``` > > Update plugins and themes: > > ``` > ddev exec gpm update -f > ``` Visit the [Grav Documentation]() for more information about Grav in general and visit [Local Development with DDEV]() for more details about the usage of Grav with DDEV. ## Ibexa DXP Install [Ibexa DXP]() OSS Edition. Create the project directory and configure DDEV: ``` mkdir -p my-ibexa-site && cd my-ibexa-site ddev config --project-type=symfony --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Ibexa via Composer: ``` ddev composer create-project ibexa/oss-skeleton ``` Run Ibexa installation: ``` ddev console ibexa:install --no-interaction ddev console ibexa:graphql:generate-schema ``` Launch the admin interface: ``` ddev launch /admin/login ``` In the web browser, log into your account using `admin` and `publish`. Visit [Ibexa documentation]() for more cases. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-ibexa.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-ibexa-site && cd my-ibexa-site > ddev config --project-type=symfony --docroot=public > ddev start -y > ddev composer create-project ibexa/oss-skeleton > ddev console ibexa:install --no-interaction > ddev console ibexa:graphql:generate-schema > ddev launch /admin/login > EOF > chmod +x setup-ibexa.sh > ./setup-ibexa.sh > ``` ## Joomla Create the project directory and download Joomla: ``` mkdir -p my-joomla-site && cd my-joomla-site curl -fsSL -o joomla.zip "https://www.joomla.org/latest" unzip joomla.zip && rm -f joomla.zip ``` Configure DDEV: ``` ddev config --project-type=joomla ``` Adjust `php.ini` settings for Joomla: ``` echo "display_errors = off" > .ddev/php/joomla.ini echo "output_buffering = off" >> .ddev/php/joomla.ini ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Joomla: ``` ddev php installation/joomla.php install --site-name="My Joomla Site" --admin-user="Administrator" --admin-username=admin --admin-password=AdminAdmin1! --admin-email=admin@example.com --db-type=mysql --db-encryption=0 --db-host=db --db-user=db --db-pass="db" --db-name=db --db-prefix=ddev_ --public-folder="" ``` Launch the admin interface (login with `admin` and `AdminAdmin1!`): ``` ddev launch /administrator ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-joomla.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-joomla-site && cd my-joomla-site > curl -fsSL -o joomla.zip "https://www.joomla.org/latest" > unzip joomla.zip && rm -f joomla.zip > ddev config --project-type=joomla > echo "display_errors = off" > .ddev/php/joomla.ini > echo "output_buffering = off" >> .ddev/php/joomla.ini > ddev start -y > ddev php installation/joomla.php install --site-name="My Joomla Site" --admin-user="Administrator" --admin-username=admin --admin-password=AdminAdmin1! --admin-email=admin@example.com --db-type=mysql --db-encryption=0 --db-host=db --db-user=db --db-pass="db" --db-name=db --db-prefix=ddev_ --public-folder="" > ddev launch /administrator > EOF > chmod +x setup-joomla.sh > ./setup-joomla.sh > ``` ## Kirby CMS Start a new [Kirby CMS]() project or use an existing one. Create a new Kirby CMS project from the official [Starterkit]() using DDEV’s [`composer create-project` command](): Create the project directory and configure DDEV: ``` mkdir -p my-kirby-site && cd my-kirby-site ddev config --omit-containers=db --webserver-type=apache-fpm ``` Start DDEV (this may take a minute): ``` ddev start ``` Install the Kirby Starterkit: ``` ddev composer create-project getkirby/starterkit ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-kirby.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-kirby-site && cd my-kirby-site > ddev config --omit-containers=db --webserver-type=apache-fpm > ddev start -y > ddev composer create-project getkirby/starterkit > ddev launch > EOF > chmod +x setup-kirby.sh > ./setup-kirby.sh > ``` You can start using DDEV with an existing project as well: Navigate to your existing project directory: ``` cd my-kirby-site ``` Configure DDEV: ``` ddev config --omit-containers=db --webserver-type=apache-fpm ``` Start DDEV (this may take a minute): ``` ddev start ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-kirby-existing.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > cd my-kirby-site > ddev config --omit-containers=db --webserver-type=apache-fpm > ddev start -y > ddev launch > EOF > chmod +x setup-kirby-existing.sh > ./setup-kirby-existing.sh > ``` > [!TIP] > > **Installing Kirby** > > Read more about developing your Kirby project with DDEV in our [extensive DDEV guide](). ## Laravel Use a new or existing Composer project, or clone a Git repository. The Laravel project type can be used for [StarterKits](), [Laravel Livewire]() and others, as it is used with basic Laravel. DDEV automatically updates or creates the `.env` file with the database information. Laravel defaults to SQLite, but we use MariaDB to better mimic a production environment: Create the project directory and configure DDEV: ``` mkdir -p my-laravel-composer-site && cd my-laravel-composer-site ddev config --project-type=laravel --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Laravel via Composer: ``` ddev composer create-project laravel/laravel ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-laravel.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-laravel-composer-site && cd my-laravel-composer-site > ddev config --project-type=laravel --docroot=public > ddev start -y > ddev composer create-project laravel/laravel > ddev launch > EOF > chmod +x setup-laravel.sh > ./setup-laravel.sh > ``` To use the SQLite configuration provided by Laravel: Create the project directory and configure DDEV: ``` mkdir -p my-laravel-sqlite-site && cd my-laravel-sqlite-site ddev config --project-type=laravel --docroot=public --omit-containers=db ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Laravel via Composer: ``` ddev composer create-project laravel/laravel ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-laravel-sqlite.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-laravel-sqlite-site && cd my-laravel-sqlite-site > ddev config --project-type=laravel --docroot=public --omit-containers=db > ddev start -y > ddev composer create-project laravel/laravel > ddev launch > EOF > chmod +x setup-laravel-sqlite.sh > ./setup-laravel-sqlite.sh > ``` To switch an existing Laravel project to SQLite: Configure for SQLite and restart: ``` ddev config --project-type=laravel --docroot=public --omit-containers=db ddev restart ``` Run post-install scripts: ``` ddev composer run-script post-root-package-install ddev dotenv set .env --db-connection=sqlite ddev composer run-script post-create-project-cmd ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > switch-laravel-sqlite.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > ddev config --project-type=laravel --docroot=public --omit-containers=db > ddev restart > ddev composer run-script post-root-package-install > ddev dotenv set .env --db-connection=sqlite > ddev composer run-script post-create-project-cmd > ddev launch > EOF > chmod +x switch-laravel-sqlite.sh > ./switch-laravel-sqlite.sh > ``` Create the project directory and configure DDEV: ``` mkdir -p my-laravel-installer-site && cd my-laravel-installer-site ddev config --project-type=laravel --docroot=public # For SQLite instead, use: # ddev config --project-type=laravel --docroot=public --omit-containers=db ``` Create Dockerfile to add Laravel installer: ``` cat <<'DOCKERFILEEND' >.ddev/web-build/Dockerfile.laravel ARG COMPOSER_HOME=/usr/local/composer RUN composer global require laravel/installer RUN ln -s $COMPOSER_HOME/vendor/bin/laravel /usr/local/bin/laravel DOCKERFILEEND ``` Start DDEV (this may take a minute): ``` ddev start ``` Run Laravel installer (follow prompts and select starter kit): ``` ddev exec laravel new temp --database=sqlite # SQLite is used here as other database types would fail due to # the .env file not being ready, which DDEV will fix on 'ddev restart' ``` Move files and clean up: ``` ddev exec 'rsync -rltgopD temp/ ./ && rm -rf temp' rm -f .ddev/web-build/Dockerfile.laravel .env ``` Restart and finalize: ``` ddev restart ddev composer run-script post-root-package-install ddev composer run-script post-create-project-cmd ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-laravel-installer.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-laravel-installer-site && cd my-laravel-installer-site > ddev config --project-type=laravel --docroot=public > > cat <<'INNEREOF' >.ddev/web-build/Dockerfile.laravel > ARG COMPOSER_HOME=/usr/local/composer > RUN composer global require laravel/installer > RUN ln -s $COMPOSER_HOME/vendor/bin/laravel /usr/local/bin/laravel > INNEREOF > > ddev start -y > ddev exec laravel new temp --database=sqlite > ddev exec 'rsync -rltgopD temp/ ./ && rm -rf temp' > rm -f .ddev/web-build/Dockerfile.laravel .env > ddev restart > ddev composer run-script post-root-package-install > ddev composer run-script post-create-project-cmd > ddev launch > EOF > chmod +x setup-laravel-installer.sh > ./setup-laravel-installer.sh > ``` Clone your Laravel repository: ``` git clone my-laravel-git-site cd my-laravel-git-site ``` Configure and start DDEV: ``` ddev config --project-type=laravel --docroot=public ddev start ``` Install dependencies and run post-install scripts: ``` ddev composer install ddev composer run-script post-root-package-install ddev composer run-script post-create-project-cmd ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-laravel-git.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > git clone my-laravel-git-site > cd my-laravel-git-site > ddev config --project-type=laravel --docroot=public > ddev start -y > ddev composer install > ddev composer run-script post-root-package-install > ddev composer run-script post-create-project-cmd > ddev launch > EOF > chmod +x setup-laravel-git.sh > ./setup-laravel-git.sh > ``` > [!TIP] > > **Add Vite support?** > > Since Laravel v9.19, Vite is included as the default [asset bundler](). See the [Vite Integration]() documentation for complete setup instructions. ## Magento 2 Normal details of a Composer build for Magento 2 are on the [Magento 2 site](). You must have a public and private key to install from Magento’s repository. When prompted for “username” and “password” in `composer create-project`, it’s asking for your public key as “username” and private key as “password”. > [!TIP] > > **Store Adobe/Magento Composer credentials in the global DDEV config** > > If you have Composer installed on your workstation and have an `auth.json` you can reuse the `auth.json` by making a symlink. See the example in [Composer Authentication](). > > Alternately, you can install the Adobe/Magento Composer credentials in your global `$HOME/.ddev/homeadditions/.composer/auth.json` and never have to enter them again (see below): > > **Script to store Adobe/Magento Composer credentials (click me)** > > ``` > # Enter your username/password and agree to store your credentials > ddev_dir="$(ddev version -j | docker run -i --rm ddev/ddev-utilities jq -r ".raw.\"global-ddev-dir\" | select (.!=null) // \"$HOME/.ddev\"" 2>/dev/null)" > mkdir -p $ddev_dir/homeadditions/.composer > docker_command=("docker" "run" "-it" "--rm" "-v" "$ddev_dir/homeadditions/.composer:/composer" "--workdir=/tmp" "-e" "COMPOSER_HOME=/composer" "--user" "$(id -u):$(id -g)") > auth_json_path="$ddev_dir/homeadditions/.composer/auth.json" > if [ -L "$auth_json_path" ]; then > # If auth.json is a symlink, add the optional mount > auth_json_dir=$(dirname "$(readlink -f "$auth_json_path")") > docker_command+=("-v" "$auth_json_dir:$auth_json_dir") > fi > image="$(ddev version -j | docker run -i --rm ddev/ddev-utilities jq -r ".raw.web | select (.!=null)" 2>/dev/null)" > docker_command+=("$image" "bash" "-c" "composer create-project --repository https://repo.magento.com/ magento/project-community-edition --no-install") > # Execute the command to store credentials > "${docker_command[@]}" > ``` Create the project directory and configure DDEV: ``` export MAGENTO_HOSTNAME=my-magento2-site mkdir -p ${MAGENTO_HOSTNAME} && cd ${MAGENTO_HOSTNAME} ddev config --project-type=magento2 --docroot=pub --upload-dirs=media --disable-settings-management ddev add-on get ddev/ddev-opensearch ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Magento via Composer: ``` ddev composer create-project --repository https://repo.magento.com/ magento/project-community-edition rm -f app/etc/env.php ``` Run Magento setup: ``` ddev magento setup:install --base-url="https://${MAGENTO_HOSTNAME}.ddev.site/" \ --cleanup-database --db-host=db --db-name=db --db-user=db --db-password=db \ --opensearch-host=opensearch --search-engine=opensearch --opensearch-port=9200 \ --admin-firstname=Magento --admin-lastname=User --admin-email=user@example.com \ --admin-user=admin --admin-password=Password123 --language=en_US ``` Configure Magento and launch: ``` ddev magento deploy:mode:set developer ddev magento module:disable Magento_TwoFactorAuth Magento_AdminAdobeImsTwoFactorAuth ddev config --disable-settings-management=false # Change the backend frontname URL to /admin_ddev ddev magento setup:config:set --backend-frontname="admin_ddev" --no-interaction # Login using `admin` user and `Password123` password ddev launch /admin_ddev ``` Change the admin name and related information as needed. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-magento.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > export MAGENTO_HOSTNAME=my-magento2-site > mkdir -p ${MAGENTO_HOSTNAME} && cd ${MAGENTO_HOSTNAME} > ddev config --project-type=magento2 --docroot=pub --upload-dirs=media --disable-settings-management > ddev add-on get ddev/ddev-opensearch > ddev start -y > ddev composer create-project --repository https://repo.magento.com/ magento/project-community-edition > rm -f app/etc/env.php > ddev magento setup:install --base-url="https://${MAGENTO_HOSTNAME}.ddev.site/" \ > --cleanup-database --db-host=db --db-name=db --db-user=db --db-password=db \ > --opensearch-host=opensearch --search-engine=opensearch --opensearch-port=9200 \ > --admin-firstname=Magento --admin-lastname=User --admin-email=user@example.com \ > --admin-user=admin --admin-password=Password123 --language=en_US > ddev magento deploy:mode:set developer > ddev magento module:disable Magento_TwoFactorAuth Magento_AdminAdobeImsTwoFactorAuth > ddev config --disable-settings-management=false > ddev magento setup:config:set --backend-frontname="admin_ddev" --no-interaction > ddev launch /admin_ddev > EOF > chmod +x setup-magento.sh > ./setup-magento.sh > ``` The admin login URL is specified by `frontName` in `app/etc/env.php`. You may want to add the [Magento 2 Sample Data]() with: ``` ddev magento sampledata:deploy ddev magento setup:upgrade ``` ## Maho You can start a new [Maho]() project or configure an existing one. Maho is an e-commerce platform derived from Magento 1, with modern PHP support and no external service requirements: it needs only a web server, PHP and a database. DDEV automatically creates `app/etc/local.xml` with the database connection details, so importing an existing site is just `ddev import-db` and `ddev import-files` away. For a fresh installation the Maho installer needs `--force` to take over that pre-created file. Create the project directory and configure DDEV: ``` mkdir -p my-maho-site && cd my-maho-site ddev config --project-type=maho --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Maho via Composer: ``` ddev composer create-project mahocommerce/maho-starter ``` Run the Maho installer with sample data: ``` ddev maho install --force \ --license_agreement_accepted yes \ --locale en_US --timezone UTC --default_currency USD \ --db_host db --db_name db --db_user db --db_pass db \ --url "$(ddev exec echo '$DDEV_PRIMARY_URL')" \ --use_secure 1 --secure_base_url "$(ddev exec echo '$DDEV_PRIMARY_URL')" --use_secure_admin 1 \ --admin_firstname Store --admin_lastname Admin --admin_email admin@example.com \ --admin_username admin --admin_password veryl0ngpassw0rd \ --sample_data 1 ``` Re-index and flush the cache: ``` ddev maho index:reindex:all && ddev maho cache:flush ``` Launch the admin interface (login with `admin` and `veryl0ngpassw0rd`): ``` ddev launch /admin ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-maho.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-maho-site && cd my-maho-site > ddev config --project-type=maho --docroot=public > ddev start -y > ddev composer create-project mahocommerce/maho-starter > ddev maho install --force \ > --license_agreement_accepted yes \ > --locale en_US --timezone UTC --default_currency USD \ > --db_host db --db_name db --db_user db --db_pass db \ > --url "$(ddev exec echo '$DDEV_PRIMARY_URL')" \ > --use_secure 1 --secure_base_url "$(ddev exec echo '$DDEV_PRIMARY_URL')" --use_secure_admin 1 \ > --admin_firstname Store --admin_lastname Admin --admin_email admin@example.com \ > --admin_username admin --admin_password veryl0ngpassw0rd \ > --sample_data 1 > ddev maho index:reindex:all && ddev maho cache:flush > ddev launch /admin > EOF > chmod +x setup-maho.sh > ./setup-maho.sh > ``` > [!WARNING] > > **For contributing to Maho core only** > > This setup is only for contributing to Maho itself. Never build a store on a clone of the core repository — use the Composer setup instead. Create the project directory and clone the repository: ``` mkdir -p maho && cd maho git clone https://github.com/MahoCommerce/maho . ``` Configure and start DDEV: ``` ddev config --project-type=maho --docroot=public ddev start ``` Install Composer dependencies: ``` ddev composer install ``` Run the Maho installer with sample data: ``` ddev maho install --force \ --license_agreement_accepted yes \ --locale en_US --timezone UTC --default_currency USD \ --db_host db --db_name db --db_user db --db_pass db \ --url "$(ddev exec echo '$DDEV_PRIMARY_URL')" \ --use_secure 1 --secure_base_url "$(ddev exec echo '$DDEV_PRIMARY_URL')" --use_secure_admin 1 \ --admin_firstname Store --admin_lastname Admin --admin_email admin@example.com \ --admin_username admin --admin_password veryl0ngpassw0rd \ --sample_data 1 ``` Re-index and flush the cache: ``` ddev maho index:reindex:all && ddev maho cache:flush ``` Launch the admin interface (login with `admin` and `veryl0ngpassw0rd`): ``` ddev launch /admin ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-maho-git.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p maho && cd maho > git clone https://github.com/MahoCommerce/maho . > ddev config --project-type=maho --docroot=public > ddev start -y > ddev composer install > ddev maho install --force \ > --license_agreement_accepted yes \ > --locale en_US --timezone UTC --default_currency USD \ > --db_host db --db_name db --db_user db --db_pass db \ > --url "$(ddev exec echo '$DDEV_PRIMARY_URL')" \ > --use_secure 1 --secure_base_url "$(ddev exec echo '$DDEV_PRIMARY_URL')" --use_secure_admin 1 \ > --admin_firstname Store --admin_lastname Admin --admin_email admin@example.com \ > --admin_username admin --admin_password veryl0ngpassw0rd \ > --sample_data 1 > ddev maho index:reindex:all && ddev maho cache:flush > ddev launch /admin > EOF > chmod +x setup-maho-git.sh > ./setup-maho-git.sh > ``` ## MODX Revolution DDEV supports both [MODX]() Revolution 2.x and 3.x. Create the project directory and configure DDEV: ``` mkdir -p my-modx-site && cd my-modx-site ddev config --project-type=modx ``` Start DDEV (this may take a minute): ``` ddev start ``` Install MODX Revolution via Composer: ``` ddev composer create-project modx/revolution ``` Create the project directory: ``` mkdir -p my-modx-site && cd my-modx-site ``` Download the latest MODX Revolution release and extract it. The archive extracts into a versioned subdirectory, so move its contents up to the project root: ``` MODX_VERSION=$(curl -fsSL https://api.github.com/repos/modxcms/revolution/tags | grep -o '"v[0-9][0-9.]*-pl"' | head -1 | tr -dc '0-9.') curl -fLo modx.zip "https://modx.s3.amazonaws.com/releases/${MODX_VERSION}/modx-${MODX_VERSION}-pl.zip" unzip -q modx.zip && rm -f modx.zip (shopt -s dotglob && mv "modx-${MODX_VERSION}-pl"/* .) && rmdir "modx-${MODX_VERSION}-pl" ``` Set `MODX_VERSION` yourself to install a specific release, for example `MODX_VERSION=2.8.9` for MODX Revolution 2.x. Releases are also listed on [modx.com](). Configure and start DDEV: ``` ddev config --project-type=modx ddev start ``` DDEV generates a `#ddev-generated` `core/config/config.inc.php` with the DDEV database credentials (database name, user, and password are all `db`; host is `db`). Install MODX with the CLI installer, which performs a fresh install using those credentials: ``` ddev php setup/cli-install.php \ --database_server=db --database=db --database_user=db --database_password=db \ --table_prefix=modx_ --http_host=my-modx-site.ddev.site \ --cmsadmin=admin --cmspassword=Admin123! --cmsadminemail=admin@example.com --language=en \ --context_mgr_path=/var/www/html/manager/ --context_mgr_url=/manager/ \ --context_connectors_path=/var/www/html/connectors/ --context_connectors_url=/connectors/ ``` The four `--context_*` arguments are the installer’s own defaults. Passing them explicitly keeps the install non-interactive; leave them out and the installer stops to ask for each one. Launch the manager (log in with `admin` / `Admin123!`): ``` ddev launch /manager/ ``` > [!NOTE] > > **Web installer and the DDEV-managed config** > > You can also install through the web wizard with `ddev launch /setup/`. Because DDEV has already written a valid `core/config/config.inc.php`, MODX opens the web wizard in *upgrade* mode; for a brand-new site use the CLI installer above (or remove `core/config/config.inc.php` before opening `/setup/`). When you import an existing MODX site and its database (for example with [`ddev import-db`]()), the DDEV-generated `config.inc.php` connects it straight away with no installer step. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-modx.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-modx-site && cd my-modx-site > ddev config --project-type=modx > ddev start -y > ddev composer create-project modx/revolution > ddev php setup/cli-install.php \ > --database_server=db --database=db --database_user=db --database_password=db \ > --table_prefix=modx_ --http_host=my-modx-site.ddev.site \ > --cmsadmin=admin --cmspassword=Admin123! --cmsadminemail=admin@example.com --language=en \ > --context_mgr_path=/var/www/html/manager/ --context_mgr_url=/manager/ \ > --context_connectors_path=/var/www/html/connectors/ --context_connectors_url=/connectors/ > ddev launch /manager/ > EOF > chmod +x setup-modx.sh > ./setup-modx.sh > ``` ## Moodle Create the project directory and configure DDEV: ``` mkdir -p my-moodle-site && cd my-moodle-site ddev config --docroot=public --webserver-type=apache-fpm ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Moodle via Composer: ``` ddev composer create-project moodle/moodle ``` Run Moodle installation: ``` ddev exec 'php admin/cli/install.php --non-interactive --agree-license --wwwroot=$DDEV_PRIMARY_URL --dbtype=mariadb --dbhost=db --dbname=db --dbuser=db --dbpass=db --fullname="DDEV Moodle Demo" --shortname=Demo --adminemail=admin@example.com --adminpass=password' ``` Launch Moodle (login with `admin` and `password`):: ``` ddev launch /login ``` Visit the [Moodle Admin Quick Guide]() for more information. > [!TIP] > > Moodle relies on a periodic cron job—don’t forget to set that up! See [ddev/ddev-cron](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-moodle.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-moodle-site && cd my-moodle-site > ddev config --docroot=public --webserver-type=apache-fpm > ddev start -y > ddev composer create-project moodle/moodle > ddev exec 'php admin/cli/install.php --non-interactive --agree-license --wwwroot=$DDEV_PRIMARY_URL --dbtype=mariadb --dbhost=db --dbname=db --dbuser=db --dbpass=db --fullname="DDEV Moodle Demo" --shortname=Demo --adminemail=admin@example.com --adminpass=password' > ddev launch /login > EOF > chmod +x setup-moodle.sh > ./setup-moodle.sh > ``` ## Node.js This example installation sets up the SvelteKit demo in DDEV with the `generic` webserver. Node.js support as in this example is experimental, and your suggestions and improvements are welcome. ``` export SVELTEKIT_SITENAME=my-sveltekit-site mkdir -p ${SVELTEKIT_SITENAME} && cd ${SVELTEKIT_SITENAME} ddev config --project-type=generic --webserver-type=generic ddev start cat < .ddev/config.sveltekit.yaml web_extra_exposed_ports: - name: svelte container_port: 3000 http_port: 80 https_port: 443 web_extra_daemons: - name: "sveltekit-demo" command: "node build" directory: /var/www/html EOF # When it prompts "Directory not empty. Continue?", choose Yes ddev exec "npx sv create --template=demo --types=ts --no-add-ons --no-install ." # Use adapter-node, and set the port and allowed hosts in vite.config.ts ddev exec "sed -i -e 's#adapter-auto#adapter-node#' -e 's#^export default defineConfig({#&\n\tserver: { port: 3000, allowedHosts: true },#' vite.config.ts" ddev npm install @sveltejs/adapter-node ddev npm install ddev npm run build ddev restart ddev launch ``` SvelteKit requires just a bit of configuration to make it run. There are many ways to make any Node.js site work, these are just examples. SvelteKit 3 no longer uses `svelte.config.js`; its configuration is passed to the `sveltekit()` plugin in `vite.config.ts`, which can be adapted in many ways. For more Vite configuration options, see the [Vite Integration]() documentation. - The `sed` command switches the adapter from `adapter-auto` to `adapter-node`. - It also sets `server.port` to 3000 and `server.allowedHosts` to `true` in `vite.config.ts`. ``` export NODEJS_SITENAME=my-nodejs-site mkdir -p ${NODEJS_SITENAME} && cd ${NODEJS_SITENAME} ddev config --project-type=generic --webserver-type=generic ddev start ddev npm install express cat < .ddev/config.nodejs.yaml web_extra_exposed_ports: - name: node-example container_port: 3000 http_port: 80 https_port: 443 web_extra_daemons: - name: "node-example" command: "node server.js" directory: /var/www/html EOF ddev exec curl -fsSLO https://raw.githubusercontent.com/ddev/test-nodejs/main/server.js ddev restart ddev launch ``` The [`server.js`]() used here is a trivial Express-based Node.js webserver. Yours will be more extensive. ## OpenMage Visit [OpenMage Docs]() for more installation details. Create the project directory and configure DDEV: ``` mkdir -p my-openmage-composer-site && cd my-openmage-composer-site ddev config --project-type=magento --docroot=public_test --php-version=8.3 --web-environment-add=MAGE_IS_DEVELOPER_MODE=1 ``` Start DDEV (this may take a minute): ``` ddev start ``` Initialize and configure Composer: ``` ddev composer init --name "openmage/composer-test" --description "OpenMage starter project" --type "project" -l "OSL-3.0" -s "dev" -q ddev composer config prefer-stable true ddev composer config policy.advisories.block false ddev composer config extra.magento-root-dir "public_test" ddev composer config extra.magento-core-package-type "magento-source" ddev composer config allow-plugins.vaimo/composer-patches true ddev composer config allow-plugins.magento-hackathon/magento-composer-installer true ddev composer config allow-plugins.aydin-hassan/magento-core-composer-installer true ddev composer config allow-plugins.openmage/composer-plugin true ddev composer require --no-update "aydin-hassan/magento-core-composer-installer":"^2.1.0" "openmage/magento-lts":"^20.19" ``` Download the OpenMage install command and install dependencies: ``` ddev exec wget -O .ddev/commands/web/openmage-install https://raw.githubusercontent.com/OpenMage/magento-lts/refs/heads/main/.ddev/commands/web/openmage-install ddev composer install ``` Run OpenMage silent installation with sample data: ``` ddev openmage-install -q ``` Launch the admin interface (login with `admin` and `veryl0ngpassw0rd`): ``` ddev launch /admin ``` > [!NOTE] > > **Make sure that `docroot` is set correctly** > > DDEV config `--docroot` has to match Composer config `extra.magento-root-dir`. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-openmage.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-openmage-composer-site && cd my-openmage-composer-site > ddev config --project-type=magento --docroot=public_test --php-version=8.3 --web-environment-add=MAGE_IS_DEVELOPER_MODE=1 > ddev start -y > ddev composer init --name "openmage/composer-test" --description "OpenMage starter project" --type "project" -l "OSL-3.0" -s "dev" -q > ddev composer config prefer-stable true > ddev composer config policy.advisories.block false > ddev composer config extra.magento-root-dir "public_test" > ddev composer config extra.magento-core-package-type "magento-source" > ddev composer config allow-plugins.vaimo/composer-patches true > ddev composer config allow-plugins.magento-hackathon/magento-composer-installer true > ddev composer config allow-plugins.aydin-hassan/magento-core-composer-installer true > ddev composer config allow-plugins.openmage/composer-plugin true > ddev composer require --no-update "aydin-hassan/magento-core-composer-installer":"^2.1.0" "openmage/magento-lts":"^20.19" > ddev exec wget -O .ddev/commands/web/openmage-install https://raw.githubusercontent.com/OpenMage/magento-lts/refs/heads/main/.ddev/commands/web/openmage-install > ddev composer install > ddev openmage-install -q > ddev launch /admin > EOF > chmod +x setup-openmage.sh > ./setup-openmage.sh > ``` Create the project directory and clone the repository: ``` mkdir -p my-openmage-git-site && cd my-openmage-git-site git clone https://github.com/OpenMage/magento-lts . ``` Configure and start DDEV: ``` ddev config --project-type=magento --php-version=8.3 --web-environment-add=MAGE_IS_DEVELOPER_MODE=1 ddev start ``` Install Composer dependencies: ``` ddev composer install ``` Run OpenMage silent installation with sample data: ``` ddev openmage-install -q ``` Launch the admin interface (login with `admin` and `veryl0ngpassw0rd`): ``` ddev launch /admin ``` Note that OpenMage itself provides several custom DDEV commands, including `openmage-install`, `openmage-admin`, `phpmd`, `rector`, `phpcbf`, `phpstan`, `vendor-patches`, and `php-cs-fixer`. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-openmage-git.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-openmage-git-site && cd my-openmage-git-site > git clone https://github.com/OpenMage/magento-lts . > ddev config --project-type=magento --php-version=8.3 --web-environment-add=MAGE_IS_DEVELOPER_MODE=1 > ddev start -y > ddev composer install > ddev openmage-install -q > ddev launch /admin > EOF > chmod +x setup-openmage-git.sh > ./setup-openmage-git.sh > ``` ## October CMS Start a new [October CMS]() project or configure an existing one. October CMS is built on Laravel, so it uses the `laravel` project type. DDEV automatically updates or creates the `.env` file with the database information. Create the project directory and configure DDEV: ``` mkdir -p my-october-site && cd my-october-site ddev config --project-type=laravel --webserver-type=apache-fpm ``` Start DDEV (this may take a minute): ``` ddev start ``` Install October CMS via Composer: ``` ddev composer create-project october/october ``` Run the installer: ``` ddev artisan october:install --no-interaction ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-october.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-october-site && cd my-october-site > ddev config --project-type=laravel --webserver-type=apache-fpm > ddev start -y > ddev composer create-project october/october > ddev artisan october:install --no-interaction > ddev launch > EOF > chmod +x setup-october.sh > ./setup-october.sh > ``` You can start using DDEV with an existing October CMS project too — but make sure you have a database backup handy! Clone the repository (or navigate to a local project directory): ``` git clone my-october-site cd my-october-site ``` Configure DDEV: ``` ddev config --project-type=laravel --webserver-type=apache-fpm ``` Start DDEV and install dependencies: ``` ddev start ddev composer install ``` Import your database and run migrations: ``` ddev import-db --file=/path/to/db.sql.gz ddev artisan october:migrate ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-october-existing.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > git clone my-october-site > cd my-october-site > ddev config --project-type=laravel --webserver-type=apache-fpm > ddev start -y > ddev composer install > ddev import-db --file=/path/to/db.sql.gz > ddev artisan october:migrate > ddev launch > EOF > chmod +x setup-october-existing.sh > ./setup-october-existing.sh > ``` > [!TIP] > > **Installing October CMS** > > Read more about installing October CMS in the [official documentation](). ## Pimcore Using the [Pimcore skeleton]() repository: Create the project directory and configure DDEV: ``` mkdir -p my-pimcore-site && cd my-pimcore-site ddev config --project-type=php --docroot=public --webimage-extra-packages='php${DDEV_PHP_VERSION}-amqp' ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Pimcore via Composer: ``` ddev composer create-project pimcore/skeleton ``` Run Pimcore installation: ``` ddev exec pimcore-install \ --mysql-username=db \ --mysql-password=db \ --mysql-host-socket=db \ --mysql-database=db \ --admin-password=admin \ --admin-username=admin ``` Create consumer daemon configuration: ``` echo "web_extra_daemons: - name: consumer command: 'while true; do /var/www/html/bin/console messenger:consume pimcore_core pimcore_maintenance pimcore_scheduled_tasks pimcore_image_optimize pimcore_asset_update --memory-limit=250M --time-limit=3600; done' directory: /var/www/html" >.ddev/config.pimcore.yaml ``` Restart and launch: ``` ddev restart ddev launch /admin ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-pimcore.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-pimcore-site && cd my-pimcore-site > ddev config --project-type=php --docroot=public --webimage-extra-packages='php${DDEV_PHP_VERSION}-amqp' > ddev start -y > ddev composer create-project pimcore/skeleton > ddev exec pimcore-install \ > --mysql-username=db \ > --mysql-password=db \ > --mysql-host-socket=db \ > --mysql-database=db \ > --admin-password=admin \ > --admin-username=admin > echo "web_extra_daemons: > - name: consumer > command: 'while true; do /var/www/html/bin/console messenger:consume pimcore_core pimcore_maintenance pimcore_scheduled_tasks pimcore_image_optimize pimcore_asset_update --memory-limit=250M --time-limit=3600; done' > directory: /var/www/html" >.ddev/config.pimcore.yaml > ddev restart > ddev launch /admin > EOF > chmod +x setup-pimcore.sh > ./setup-pimcore.sh > ``` ## ProcessWire To get started with [ProcessWire](), create a new directory and use the ZIP file download, composer, or Git checkout to build. These instructions are adapted from [ProcessWire Install Documentation](). Create the project directory: ``` mkdir -p my-processwire-zip-site && cd my-processwire-zip-site ``` Download and extract ProcessWire: ``` curl -LJOf https://github.com/processwire/processwire/archive/master.zip unzip processwire-master.zip && rm -f processwire-master.zip cp -R processwire-master/. . && rm -rf processwire-master ``` Configure and start DDEV: ``` ddev config --project-type=php --webserver-type=apache-fpm ddev start ``` Launch ProcessWire: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-processwire-zip.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-processwire-zip-site && cd my-processwire-zip-site > curl -LJOf https://github.com/processwire/processwire/archive/master.zip > unzip processwire-master.zip && rm -f processwire-master.zip > cp -R processwire-master/. . && rm -rf processwire-master > ddev config --project-type=php --webserver-type=apache-fpm > ddev start -y > ddev launch > EOF > chmod +x setup-processwire-zip.sh > ./setup-processwire-zip.sh > ``` Create the project directory and configure DDEV: ``` mkdir -p my-processwire-composer-site && cd my-processwire-composer-site ddev config --project-type=php --webserver-type=apache-fpm ``` Start DDEV (this may take a minute): ``` ddev start ``` Install ProcessWire via Composer: ``` ddev composer create-project "processwire/processwire:^3" ``` Launch ProcessWire: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-processwire.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-processwire-composer-site && cd my-processwire-composer-site > ddev config --project-type=php --webserver-type=apache-fpm > ddev start -y > ddev composer create-project "processwire/processwire:^3" > ddev launch > EOF > chmod +x setup-processwire.sh > ./setup-processwire.sh > ``` Create the project directory: ``` mkdir -p my-processwire-git-site && cd my-processwire-git-site ``` Clone ProcessWire (main branch for stable release): ``` git clone https://github.com/processwire/processwire.git . # For latest features, use dev branch instead: # git clone -b dev https://github.com/processwire/processwire.git . ``` Configure and start DDEV: ``` ddev config --webserver-type=apache-fpm ddev start ``` Launch ProcessWire: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-processwire-git.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-processwire-git-site && cd my-processwire-git-site > git clone https://github.com/processwire/processwire.git . > ddev config --webserver-type=apache-fpm > ddev start -y > ddev launch > EOF > chmod +x setup-processwire-git.sh > ./setup-processwire-git.sh > ``` When the installation wizard prompts for database settings, enter: - `DB Name` = `db` - `DB User` = `db` - `DB Pass` = `db` - `DB Host` = `db` (**not** `localhost`!) - `DB Charset` = `utf8mb4` - `DB Engine` = `InnoDB` If you get a warning about “Apache mod\_rewrite” during the compatibility check, Click “refresh”. **After installation,** configure `upload_dirs` to specify where user-generated files are managed by Processwire: ``` ddev config --upload-dirs=sites/assets/files && ddev restart ``` ## Shopware Though you can set up a Shopware 6 environment many ways, we recommend the following technique. DDEV creates a `.env.local` file for you by default; if you already have one DDEV adds necessary information to it. When `ddev composer create-project` asks if you want to include Docker configuration, answer `x`, as this approach does not use their Docker configuration. Create the project directory and configure DDEV: ``` mkdir -p my-shopware-site && cd my-shopware-site ddev config --project-type=shopware6 --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Shopware via Composer: ``` ddev composer create-project shopware/production # If it asks `Do you want to include Docker configuration from recipes?` # answer `x`, as we're using DDEV for this rather than its recipes. ``` Run Shopware installation and launch: ``` ddev console system:install --basic-setup ddev launch /admin # Default username and password are `admin` and `shopware` ``` Log into the admin site (`/admin`) using the web browser. The default credentials are username `admin` and password `shopware`. You can use the web UI to install sample data or accomplish many other tasks. > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-shopware.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-shopware-site && cd my-shopware-site > ddev config --project-type=shopware6 --docroot=public > ddev start -y > # Answer `x` to "Do you want to include Docker configuration from recipes?" > # so Shopware's Docker recipe is not added (DDEV provides the environment). > printf "x\n" | ddev composer create-project shopware/production > ddev console system:install --basic-setup > ddev launch /admin > EOF > chmod +x setup-shopware.sh > ./setup-shopware.sh > ``` For more advanced tasks, such as - setting up workers and the scheduler as automated tasks, - installing additional services like RabbitMQ, or - mirroring production media through an nginx proxy, see [notebook.vanwittlaer.de](). ### shopware-cli and hot-reload watchers For `shopware6` projects, DDEV bundles the [`shopware-cli`]() tool and hot-reload watcher commands automatically, with no add-on required. The `shopware-cli` binary is baked into the web image, and these commands become available: - `ddev shopware-cli` — run `shopware-cli` inside the web container - `ddev storefront-watch` — hot-reload the storefront (proxy on port 9998) - `ddev admin-watch ` — hot-reload a plugin’s administration (Vite, port 5173) Reach the watchers through your project’s primary URL with the relevant port, for example `https://my-shopware-site.ddev.site:9998` for the storefront, not `localhost`. > [!NOTE] > > **Requires Shopware 6.7.4.2 or newer** > > The bundled watchers target the Vite-based administration (port 5173) introduced in Shopware 6.7.4.2. Older Shopware versions use a different watcher setup. ## Silverstripe CMS Use a new or existing Composer project, or clone a Git repository. Create the project directory and configure DDEV: ``` mkdir -p my-silverstripe-composer-site && cd my-silverstripe-composer-site ddev config --project-type=silverstripe --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Silverstripe via Composer: ``` ddev composer create-project --prefer-dist silverstripe/installer ddev sake dev/build flush=all ``` Launch the admin area: ``` ddev launch /admin ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-silverstripe.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-silverstripe-composer-site && cd my-silverstripe-composer-site > ddev config --project-type=silverstripe --docroot=public > ddev start -y > ddev composer create-project --prefer-dist silverstripe/installer > ddev sake dev/build flush=all > ddev launch /admin > EOF > chmod +x setup-silverstripe.sh > ./setup-silverstripe.sh > ``` ``` git clone my-silverstripe-git-site cd my-silverstripe-git-site ddev config --project-type=silverstripe --docroot=public ddev start ddev composer install ddev sake dev/build flush=all ``` Your Silverstripe CMS project is now ready. The CMS can be found at `/admin`, log into the default admin account using `admin` and `password`. Visit the Silverstripe CMS [user documentation]() and [developer documentation]() for more information. `ddev sake` can be used as a shorthand for the Silverstripe CLI command `ddev exec vendor/bin/sake`. To open the CMS directly from CLI, run `ddev launch /admin`. ## Statamic Use a new or existing Composer project, or clone a Git repository. The Laravel project type can be used for [Statamic]() like it can for Laravel. DDEV automatically updates or creates the `.env` file with the database information. Create the project directory and configure DDEV: ``` mkdir -p my-statamic-composer-site && cd my-statamic-composer-site ddev config --project-type=laravel --docroot=public ``` Install Statamic via Composer: ``` ddev composer create-project --prefer-dist statamic/statamic ``` Create admin user and launch: ``` ddev php please make:user admin@example.com --password=admin1234 --super --no-interaction ddev launch /cp ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-statamic.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-statamic-composer-site && cd my-statamic-composer-site > ddev config --project-type=laravel --docroot=public > ddev composer create-project --prefer-dist statamic/statamic > ddev php please make:user admin@example.com --password=admin1234 --super --no-interaction > ddev launch /cp > EOF > chmod +x setup-statamic.sh > ./setup-statamic.sh > ``` ``` git clone my-statamic-git-site cd my-statamic-git-site ddev config --project-type=laravel --docroot=public ddev start ddev composer install ddev exec "php artisan key:generate" ddev launch /cp ``` ## Sulu Create the project directory and configure DDEV: ``` mkdir -p my-sulu-site && cd my-sulu-site ddev config --project-type=php --docroot=public --upload-dirs=uploads --database=mysql:8.0 ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Sulu via Composer: ``` ddev composer create-project sulu/skeleton ``` Create your default webspace configuration `mv config/webspaces/website.xml config/webspaces/my-sulu-site.xml` and adjust the values for `` and `` so that they are matching your project: ``` My Sulu Site my-sulu-site ``` Alternatively, use the following commands to adjust the values for `` and `` to match your project setup: Configure webspace settings: ``` export SULU_PROJECT_NAME="My Sulu Site" export SULU_PROJECT_KEY="my-sulu-site" export SULU_PROJECT_CONFIG_FILE="config/webspaces/my-sulu-site.xml" ddev exec "mv config/webspaces/website.xml ${SULU_PROJECT_CONFIG_FILE}" ddev exec "sed -i -e 's|.*|${SULU_PROJECT_NAME}|g' -e 's|.*|${SULU_PROJECT_KEY}|g' ${SULU_PROJECT_CONFIG_FILE}" ``` > [!WARNING] > > **Caution** > > Changing the `` for a webspace later on causes problems. It is recommended to decide on the value for the key before the database is build in the next step. Build the database with the `dev` argument (adds user `admin` with password `admin`): ``` ddev dotenv set .env.local --app-env=dev --database-url="mysql://db:db@db:3306/db?serverVersion=8.0&charset=utf8mb4" ddev exec bin/adminconsole sulu:build dev --no-interaction ``` Launch Sulu (login with `admin` and `admin`): ``` ddev launch /admin ``` > [!TIP] > > If you don’t want to add an admin user use the `prod` argument instead > > ``` > ddev execute bin/adminconsole sulu:build prod > ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-sulu.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-sulu-site && cd my-sulu-site > ddev config --project-type=php --docroot=public --upload-dirs=uploads --database=mysql:8.0 > ddev start -y > ddev composer create-project sulu/skeleton > export SULU_PROJECT_NAME="My Sulu Site" > export SULU_PROJECT_KEY="my-sulu-site" > export SULU_PROJECT_CONFIG_FILE="config/webspaces/my-sulu-site.xml" > ddev exec "mv config/webspaces/website.xml ${SULU_PROJECT_CONFIG_FILE}" > ddev exec "sed -i -e 's|.*|${SULU_PROJECT_NAME}|g' -e 's|.*|${SULU_PROJECT_KEY}|g' ${SULU_PROJECT_CONFIG_FILE}" > ddev dotenv set .env.local --app-env=dev --database-url="mysql://db:db@db:3306/db?serverVersion=8.0&charset=utf8mb4" > ddev exec bin/adminconsole sulu:build dev --no-interaction > ddev launch /admin > EOF > chmod +x setup-sulu.sh > ./setup-sulu.sh > ``` ## Symfony There are many ways to install Symfony, here are a few of them based on the [Symfony docs](). DDEV automatically updates or creates the `.env.local` file with the database information. Create the project directory and configure DDEV: ``` mkdir -p my-symfony-composer-site && cd my-symfony-composer-site ddev config --project-type=symfony --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Symfony via Composer, pinned to the current [long-term support (LTS) release](): ``` ddev composer create-project symfony/skeleton:"7.4.*" ddev composer require webapp # When it asks if you want to include docker configuration, say "no" with "x" ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-symfony.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-symfony-composer-site && cd my-symfony-composer-site > ddev config --project-type=symfony --docroot=public > ddev start -y > ddev composer create-project symfony/skeleton:"7.4.*" > ddev composer require webapp > ddev launch > EOF > chmod +x setup-symfony.sh > ./setup-symfony.sh > ``` ``` mkdir -p my-symfony-cli-site && cd my-symfony-cli-site ddev config --project-type=symfony --docroot=public ddev start ddev exec symfony check:requirements # --version=lts always resolves to the current long-term support release ddev exec symfony new temp --webapp --version=lts # 'symfony new' can't install in the current directory right away, # so we use 'rsync' to move the installed files one level up ddev exec 'rsync -rltgopD temp/ ./ && rm -rf temp' ddev launch ``` ``` git clone my-symfony-git-site cd my-symfony-git-site ddev config --project-type=symfony --docroot=public ddev start ddev composer install ddev launch ``` > [!TIP] > > **Want to run Symfony Console (`bin/console`)?** > > ``` > ddev console list > # ddev console doctrine:schema:update --force > ``` > [!TIP] > > **Consuming Messages (Running the Worker)** > > Edit `.ddev/config.yaml` in your project directory and uncomment `post-start` hook to see `messenger:consume` command logs, and run: > > ``` > ddev exec symfony server:log > ``` ## Tempest Create the project directory and configure DDEV: ``` PROJECT_NAME=my-tempest-site mkdir -p ${PROJECT_NAME} && cd ${PROJECT_NAME} ddev config --project-type=php --docroot=public --php-version=8.5 --omit-containers=db ``` Start DDEV (this may take a minute): ``` ddev start ``` Install Tempest via Composer: ``` ddev composer create-project tempest/app ``` Launch the site: ``` ddev launch ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-tempest.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > PROJECT_NAME=my-tempest-site > mkdir -p ${PROJECT_NAME} && cd ${PROJECT_NAME} > ddev config --project-type=php --docroot=public --php-version=8.5 --omit-containers=db > ddev start -y > ddev composer create-project tempest/app > ddev launch > EOF > chmod +x setup-tempest.sh > ./setup-tempest.sh > ``` ## TYPO3 Create the project directory and configure DDEV: ``` PROJECT_NAME=my-typo3-site mkdir -p ${PROJECT_NAME} && cd ${PROJECT_NAME} ddev config --project-type=typo3 --docroot=public ``` Start DDEV (this may take a minute): ``` ddev start ``` Install TYPO3 via Composer: ``` ddev composer create-project "typo3/cms-base-distribution:^14" ``` Install the Camino default theme [recommended for TYPO3 Core](): ``` ddev composer require typo3/theme-camino ``` Run the TYPO3 setup: ``` ddev typo3 setup \ --admin-user-password="Demo123*" \ --driver=mysqli \ --server-type=other \ --dbname=db \ --username=db \ --password=db \ --port=3306 \ --host=db \ --admin-username=admin \ --admin-email=admin@example.com \ --project-name="My TYPO3 site" \ --force ``` Launch the site content: ``` ddev launch /camino ``` Launch the backend: ``` ddev launch /typo3/ ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-typo3.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > PROJECT_NAME=my-typo3-site > mkdir -p ${PROJECT_NAME} && cd ${PROJECT_NAME} > ddev config --project-type=typo3 --docroot=public > ddev start -y > ddev composer create-project "typo3/cms-base-distribution:^14" > ddev composer require typo3/theme-camino > ddev typo3 setup \ > --admin-user-password="Demo123*" \ > --driver=mysqli \ > --server-type=other \ > --dbname=db \ > --username=db \ > --password=db \ > --port=3306 \ > --host=db \ > --admin-username=admin \ > --admin-email=admin@example.com \ > --project-name="My TYPO3 site" \ > --force > ddev launch /camino > ddev launch /typo3/ > EOF > chmod +x setup-typo3.sh > ./setup-typo3.sh > ``` This example uses a clone of a test repository, `github.com/ddev/test-typo3.git`. Replace that with your git repository. ``` PROJECT_GIT_URL=https://github.com/ddev/test-typo3.git PROJECT_NAME=my-typo3-site mkdir -p ${PROJECT_NAME} && cd ${PROJECT_NAME} git clone ${PROJECT_GIT_REPOSITORY} . ddev config --project-type=typo3 --docroot=public ddev start ddev composer install ddev exec touch public/FIRST_INSTALL ddev launch /typo3/install.php ``` ## Wagtail (Python, Generic) [Wagtail]() is a popular, open-source content management system built on the Django web framework. This quickstart installs Wagtail into the web image, as described in [Python Packages from `requirements.txt`](), and runs the Django development server. Create the project directory and configure DDEV: ``` export WAGTAIL_SITENAME=my-wagtail-site mkdir -p ${WAGTAIL_SITENAME} && cd ${WAGTAIL_SITENAME} ddev config --project-type=generic --webserver-type=generic \ --webimage-extra-packages=python3-venv \ --web-environment-add=DJANGO_SETTINGS_MODULE=mysite.settings.dev \ --omit-containers=db ``` Add a placeholder `requirements.txt` that installs Wagtail so it can create the project, a Django setting to detect HTTPS behind the [Traefik]() router, and the DDEV configuration: ``` echo wagtail > requirements.txt mkdir -p mysite/settings echo 'SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")' > mysite/settings/local.py cat <<'EOF' > .ddev/docker-compose.wagtail.yaml services: web: build: additional_contexts: project: ${DDEV_APPROOT} EOF cat <<'EOF' > .ddev/web-build/Dockerfile.wagtail COPY --from=project --chmod=644 requirements.txt /tmp/requirements.txt RUN install -d -o "$username" /opt/wagtail \ && runuser -u "$username" -- python3 -m venv /opt/wagtail \ && runuser -u "$username" -- /opt/wagtail/bin/pip install --no-cache-dir -r /tmp/requirements.txt \ && echo 'export PATH="/opt/wagtail/bin:$PATH"' > /etc/bashrc/wagtail.bashrc EOF cat <<'EOF' > .ddev/config.wagtail.yaml web_extra_daemons: - name: "wagtail" command: "python manage.py runserver 0.0.0.0:8000" directory: /var/www/html web_extra_exposed_ports: - name: "wagtail" container_port: 8000 http_port: 80 https_port: 443 EOF ``` Start DDEV (this may take a minute): ``` ddev start ``` Create the Wagtail project, which replaces the placeholder with its own `requirements.txt`: ``` ddev exec "rm -f requirements.txt && wagtail start mysite ." ``` Run database migrations and create a superuser: ``` ddev exec python manage.py migrate --noinput ddev exec "DJANGO_SUPERUSER_PASSWORD=admin python manage.py createsuperuser --username=admin --email=admin@example.com --noinput" ``` Restart DDEV to rebuild the virtual environment from Wagtail’s `requirements.txt` and start the server: ``` ddev restart ``` Launch the Wagtail admin interface (login with `admin` and `admin`): ``` ddev launch /admin ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-wagtail.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > export WAGTAIL_SITENAME=my-wagtail-site > mkdir -p ${WAGTAIL_SITENAME} && cd ${WAGTAIL_SITENAME} > ddev config --project-type=generic --webserver-type=generic \ > --webimage-extra-packages=python3-venv \ > --web-environment-add=DJANGO_SETTINGS_MODULE=mysite.settings.dev \ > --omit-containers=db > echo wagtail > requirements.txt > mkdir -p mysite/settings > echo 'SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")' > mysite/settings/local.py > cat <<'INNEREOF' > .ddev/docker-compose.wagtail.yaml > services: > web: > build: > additional_contexts: > project: ${DDEV_APPROOT} > INNEREOF > cat <<'INNEREOF' > .ddev/web-build/Dockerfile.wagtail > COPY --from=project --chmod=644 requirements.txt /tmp/requirements.txt > RUN install -d -o "$username" /opt/wagtail \ > && runuser -u "$username" -- python3 -m venv /opt/wagtail \ > && runuser -u "$username" -- /opt/wagtail/bin/pip install --no-cache-dir -r /tmp/requirements.txt \ > && echo 'export PATH="/opt/wagtail/bin:$PATH"' > /etc/bashrc/wagtail.bashrc > INNEREOF > cat <<'INNEREOF' > .ddev/config.wagtail.yaml > web_extra_daemons: > - name: "wagtail" > command: "python manage.py runserver 0.0.0.0:8000" > directory: /var/www/html > web_extra_exposed_ports: > - name: "wagtail" > container_port: 8000 > http_port: 80 > https_port: 443 > INNEREOF > ddev start -y > ddev exec "rm -f requirements.txt && wagtail start mysite ." > ddev exec python manage.py migrate --noinput > ddev exec "DJANGO_SUPERUSER_PASSWORD=admin python manage.py createsuperuser --username=admin --email=admin@example.com --noinput" > ddev restart > ddev launch /admin > EOF > chmod +x setup-wagtail.sh > ./setup-wagtail.sh > ``` ## WordPress There are several easy ways to use DDEV with WordPress: DDEV has built-in support for [WP-CLI](), the command-line interface for WordPress. Create the project directory and configure DDEV: ``` mkdir -p my-wp-cli-site && cd my-wp-cli-site # Create a new DDEV project inside the newly-created folder # (Primary URL automatically set to `https://.ddev.site`) ddev config --project-type=wordpress ``` Start DDEV (this may take a minute): ``` ddev start ``` Download WordPress: ``` ddev wp core download ``` Install WordPress (or use `ddev launch` to install via browser): ``` # You can launch in browser to finish installation: # ddev launch # OR use the following installation command ddev wp core install --url="$(ddev exec echo '$DDEV_PRIMARY_URL')" --title='My WordPress site' --admin_user=admin --admin_password=admin --admin_email=admin@example.com ``` Launch WordPress admin dashboard: ``` ddev launch wp-admin/ ``` > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-wordpress.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-wp-cli-site && cd my-wp-cli-site > ddev config --project-type=wordpress > ddev start -y > ddev wp core download > ddev wp core install --url="$(ddev exec echo '$DDEV_PRIMARY_URL')" --title='My WordPress site' --admin_user=admin --admin_password=admin --admin_email=admin@example.com > ddev launch wp-admin/ > EOF > chmod +x setup-wordpress.sh > ./setup-wordpress.sh > ``` [Bedrock]() is a modern, Composer-based installation in WordPress: ``` mkdir -p my-wp-bedrock-site && cd my-wp-bedrock-site ddev config --project-type=wp-bedrock ddev start ddev composer create-project roots/bedrock ``` DDEV automatically manages the `.env` file with the correct database credentials and `WP_HOME` URL. No manual `.env` configuration is needed. You can then run: ``` ddev wp core install --url="$(ddev exec echo '$DDEV_PRIMARY_URL')" --title='My Bedrock Site' --admin_user=admin --admin_password=admin --admin_email=admin@example.com ddev launch /wp/wp-admin/ ``` For more details, see [Bedrock installation](). > [!TIP] > > **Prefer to run as a script?** > > To run the whole setup as a script, examine and run this script: > > ``` > cat > setup-wp-bedrock.sh << 'EOF' > #!/usr/bin/env bash > set -euo pipefail > mkdir -p my-wp-bedrock-site && cd my-wp-bedrock-site > ddev config --project-type=wp-bedrock > ddev start -y > ddev composer create-project roots/bedrock > ddev wp core install --url="$(ddev exec echo '$DDEV_PRIMARY_URL')" --title='My Bedrock Site' --admin_user=admin --admin_password=admin --admin_email=admin@example.com > ddev launch /wp/wp-admin/ > EOF > chmod +x setup-wp-bedrock.sh > ./setup-wp-bedrock.sh > ``` To get started using DDEV with an existing WordPress project, clone the project’s repository. ``` PROJECT_GIT_URL=https://github.com/ddev/test-wordpress.git git clone ${PROJECT_GIT_URL} my-wp-git-site cd my-wp-git-site ddev config --project-type=wordpress ddev start ddev wp core install --url="$(ddev exec echo '$DDEV_PRIMARY_URL')" --title='My WordPress site' --admin_user=admin --admin_password=admin --admin_email=admin@example.com ddev launch wp-admin/ ``` You’ll see a message like: > An existing user-managed wp-config.php file has been detected! Project DDEV settings have been written to: > > /Users/rfay/workspace/bedrock/web/wp-config-ddev.php Comment out any database connection settings in your `wp-config.php` file and add the following snippet to your `wp-config.php`, near the bottom of the file and before the include of `wp-settings.php`: ``` // Include for DDEV-managed settings in wp-config-ddev.php. $ddev_settings = __DIR__ . '/wp-config-ddev.php'; if (is_readable($ddev_settings) && !defined('DB_USER')) { require_once($ddev_settings); } ``` If you don’t care about those settings, or config is managed elsewhere (like in a `.env` file), you can eliminate this message by adding a comment to `wp-config.php`: ``` // wp-config-ddev.php not needed ``` Now run [`ddev start`]() and continue [Importing a Database]() if you need to. # Usage # How DDEV Works DDEV is a [Go]() application that stores its configuration in [files on your workstation](<#directory-tour>). It uses those blueprints to mount your project files into [Docker containers](<#container-architecture>) that facilitate the operation of a local development environment. DDEV writes and uses [docker-compose]() files for you, which is a detail you can cheerfully ignore unless you’re Docker-curious or [defining your own services](). ## Directory Tour DDEV stores configuration in two places: a single `.ddev` directory in your home folder (can be [moved](<#global-files>) to another location), and a `.ddev` directory for each project you set up. The [global configuration directory](<#global-files>) is used to keep track of your projects and any of the [global settings]() that apply across all projects. You’ll probably spend more time working with the [per-project `.ddev` directories](<#project-files>) for their configuration and overrides. ### Project Files A project’s `.ddev` directory can be intimidating at first, so let’s take a look at what lives in there. > [!TIP] > > **Yours May Differ Slightly** > > You may have some directories or files that aren’t listed here, likely added by custom services. > For example, if you see a `solr` directory, it probably pertains to a custom Solr [add-on service](). **`addon-metadata` directory** Contains metadata about add-on services that have been added to the project. This allows commands like `ddev add-on list --installed` and `ddev add-on remove` to work, see [Using Add-ons](). **`apache` directory** Default Apache configuration when using `webserver_type: apache-fpm`, which [can be customized](). **`commands` subdirectories** Project custom [shell commands]() that can run on the host or inside a container. **`config.yaml`** The project’s main [configuration file](). **`config.*.yaml` files** [Environmental overrides]() for parts of `config.yaml`. **`db-build` directory** Can be used to provide a [custom Dockerfile]() for the database container. **`db_snapshots` directory** Where snapshots go when you run the [`ddev snapshot`]() command. You can safely delete anything in here that you don’t need. **`docker-compose.*.yaml` files** Where Docker-friendly users can provide their own [custom compose files]() that add or override services. Read more in [Additional Service Configurations & Add-ons](). **`homeadditions` directory** Files to be copied into the web container on startup. You could use this, for example, to override the default home directory contents (`.profile`, `.bashrc`, `.composer`, `.ssh`), or include scripts that you’d like to be available inside the container. (You can do the same thing globally in `$HOME/.ddev/homeadditions`.) Check out the [homeadditions docs]() for more. Changes made in the container aren’t copied back, see [Persisting Changes Across Restarts](). **`mutagen` directory** Contains `mutagen.yml`, where you can [override the default Mutagen configuration](). **`mysql` directory** Contains [optional `mysql` or `mariadb` configuration](). **`nginx` directory** Can be used for add-on [nginx snippets](). **`nginx_full` directory** Contains the nginx configuration used by the web container, which [can be customized](). **`postgres` directory** Contains `postgresql.conf`, which can be edited if needed. Remove the `#ddev-generated` line at the top to take it over. **`providers` directory** Contains examples and implementations to demonstrate how the [`ddev pull`]() command can work with [hosting providers](). **`traefik` directory** Configures the [Traefik]()-based `ddev-router`. Full details in the [Traefik docs](). **`web-build` directory** Can be used to provide a [custom Dockerfile]() for the web container. **`web-entrypoint.d` directory** Custom scripts (named `*.sh`) in this directory will be run during web container startup, before the php-fpm server or other daemons are run. This can be useful, for example, for introducing environment variables into the context of the nginx and php-fpm servers. Use this carefully, because custom entrypoints can very easily break things. **`xhprof` directory** Contains the `xhprof_prepend.php` file that can be used to customize [xhprof]() behavior for different types of websites. #### Hidden Project Files Files beginning with `.` are hidden because they shouldn’t be fiddled with; most are regenerated, and thus overwritten, on every `ddev start`: **`.dbimageBuild` directory** The generated Dockerfile used to customize the `db` container on first start. **`.ddev-docker-compose-base.yaml`** The base docker-compose file used to describe a project. **`.ddev-docker-compose-full.yaml`** The result of preprocessing `.ddev-docker-compose-base.yaml` using `docker-compose config`. Mostly it replaces environment variables with their values. **`.gitignore`** The `.gitignore` is generated by DDEV and should generally not be edited or checked in. (It gitignores itself to make sure you don’t check it in.) It’s generated on every `ddev start` and will change as DDEV versions change, so if you check it in by accident it will always be showing changes that you don’t need to see in `git status`. **`.homeadditions`** Temporary directory used to consolidate global `homeadditions` with project-level `homeadditions`. You shouldn’t ever have to look here. **`.webimageBuild` directory** The generated Dockerfile used to customize the web container on first start. ### Global Files There’s only one global `.ddev` directory, which normally lives in your home directory: `$HOME/.ddev`, but can be overwritten (first match wins): 1. `$DDEV_XDG_CONFIG_HOME/ddev` if `$DDEV_XDG_CONFIG_HOME` is set (all platforms) 2. `$HOME/.ddev`, which means: - `/Users//.ddev` on macOS - `/home//.ddev` on Linux/WSL2 - `C:\Users\\.ddev` on Windows 3. `$HOME/.config/ddev` on Linux/WSL2 only, if `$HOME/.ddev` doesn’t exist To find your actual location, run: ``` ddev version | grep global-ddev-dir ``` > [!TIP] > > **Moving `$HOME/.ddev` to a custom location** > > To use a custom directory instead of `$HOME/.ddev`, set `$DDEV_XDG_CONFIG_HOME` to the parent directory. For example, to use `$HOME/.config/ddev`: > > ``` > ddev poweroff > export DDEV_XDG_CONFIG_HOME="$HOME/.config" > mv $HOME/.ddev ${DDEV_XDG_CONFIG_HOME}/ddev > ``` > > On Linux/WSL2, you can also move the directory to `$HOME/.config/ddev` without setting the variable: > > ``` > mv $HOME/.ddev $HOME/.config/ddev > ``` **`global_config.yaml`** This YAML file defines your global configuration, which consists of various [config settings](). **`global_config.*.yaml`** Files like `global_config.local.yaml` are merged over `global_config.yaml` in alphabetical order, with the same rules as [`config.*.yaml` files](): simple values are replaced and lists are merged, unless the file sets `override_config: true`. DDEV never writes their values into `global_config.yaml`. **`project_list.yaml`** This YAML file defines your project list that lets DDEV keep track of the projects you’ve added. **`bin` directory** This is where DDEV stores private executable binaries it needs, like [`mutagen`]() and [`docker-buildx`](). **`commands` directory** Directory for storing DDEV commands that should be available in containers, like `npm`, `artisan`, `cake` and `drush` for example. These are organized in subdirectories named for where they’ll be used: `db`, `host`, and `web`. You can add your own [custom commands]() here. **`db-build` directory** Like the per-project `db-build` directory, can be used to provide a [custom Dockerfile]() for the database container, applied to **every** project on this machine. **`homeadditions` directory** Like the per-project `homeadditions` directory, files you add here will automatically be copied into the web container’s home directory. Files from the *global* homeadditions directory will be copied into **every** web container’s home directory. **`web-build` directory** Like the per-project `web-build` directory, can be used to provide a [custom Dockerfile]() for the web container, applied to **every** project on this machine. #### Hidden Global Files Again, these files are mostly regenerated on every `ddev start` so it’s best to leave them alone: **`.gitignore`** Prevents files from getting checked in when they shouldn’t be. **`.global.lock`** A lockfile that makes concurrent `ddev` commands take turns while they set up the shared router, SSH agent, and `ddev_default` network. It’s safe to delete when no `ddev` command is running. **`.global.lock.info`** Which `ddev` command holds `.global.lock`, so another command waiting for it can say what it’s waiting for. See [Waiting for Another DDEV Process](). **`.router-compose-full.yaml`** The complete, generated docker-compose directive used for DDEV’s router. **`.router-compose.yaml`** The base docker-compose directive used in generating `.router-compose-full.yaml`. **`router-compose.*.yaml`** `docker-compose` files with the name `router-compose.*.yaml` can be used to override stanzas in the `.router-compose.yaml` file. **`.ssh-auth-compose-full.yaml`** The complete, generated docker-compose directive used for DDEV’s SSH agent. **`.ssh-auth-compose.yaml`** The base docker-compose directive used in generating `.ssh-auth-compose-full.yaml`. **`ssh-auth-compose.*.yaml`** `docker-compose` files with the name `ssh-auth-compose.*.yaml` can be used to override stanzas in the `.ssh-auth-compose.yaml` file. **`.update`** An empty file whose purpose is mysterious and intriguing. > [!TIP] > > **`.ddev_mutagen_data_directory`** > > DDEV uses a global `$HOME/.ddev_mutagen_data_directory` for storing [Mutagen]() sync data. ## Container Architecture It’s easiest to think of DDEV as a set of little networked computers (Docker containers) that are in a different network from your workstation but still reachable from it. When you install or upgrade DDEV you’re mostly installing a single `ddev` binary. When you use it, it downloads the Docker images it needs, and then starts them based on what’s needed for your projects. - The `ddev-webserver` container (one per project) runs `nginx` or `apache` and `php-fpm` for a single site, so it does all the basic work of a PHP-interpreting web server. - The `ddev-dbserver` container (one per project) handles MariaDB/MySQL/PostgreSQL database management. It can be reached from the web server by the hostname `db` or with the more explicit name `ddev--db`. - Additional add-on services may be there for a given project, for example `phpmyadmin`, `solr`, `elasticsearch`, or `memcached`. Although it’s not common usage, different projects can communicate with each other as described in the [FAQ](). Now for the two oddball global containers (there’s only one of each): - The `ddev-router` container is a “reverse proxy”. It takes incoming HTTP/S requests, looks up the hostname in the incoming URL, and routes it to the correct project’s `ddev-webserver`. Depending on the project’s configuration with [`additional_hostnames`]() and [`additional_fqdns`](), it can route many different URLs to a single project’s `ddev-webserver`. If, like most people, you use the named URLs like `https://something.ddev.site`, your request goes through the router. When you use the `127.0.0.1` URLs, the requests go directly to the `ddev-webserver`. - The `ddev-ssh-agent` container runs an `ssh-agent` inside the Docker network so that after run [`ddev auth ssh`]() all the different projects can use your SSH keys for outgoing requests—like private Composer access or SCP from a remote host. Here’s a basic diagram of how it works inside the Docker network: ![DDEV Docker Network Architecture]() ## The `ddev-global-cache` Volume `ddev-global-cache` is a Docker volume shared by all projects and mounted at `/mnt/ddev-global-cache`. The `web`, `db`, and `ddev-router` containers use it, and so do custom services that run [custom commands](). Unlike the containers, it survives [`ddev restart`]() and [`ddev poweroff`](). | Directory | Scope | Contents | | --- | --- | --- | | `bashhistory/-db`, `bashhistory/-web` | Per project | Bash history | | `composer` | Shared | Composer cache | | `corepack` | Shared | Package managers installed by `corepack` | | `global-commands` | Shared | [Global commands](), copied from the host | | `mkcert` | Shared | The mkcert certificate authority, copied from the host and used for HTTPS certificates | | `mysqlhistory/-db`, `mysqlhistory/-web` | Per project | MySQL client history | | `n_prefix/-web` | Per project | Node.js versions downloaded by `n` | | `npm` | Shared | npm cache | | `pip` | Shared | pip cache, created only if you install pip, for example with [`webimage_extra_packages`]() | | `terminus/cache` | Shared | Terminus cache | | `traefik` | Shared | Router configuration and certificates, with files for each project | | `yarn/berry`, `yarn/classic` | Shared | Yarn caches | [`ddev delete`]() removes the project’s own directories and router files. To keep your own files in this volume, see [Persisting Changes Across Restarts](). To clear the volume, for example to free Docker provider disk space, remove it. (Any stored global cache for all projects will be lost, but will be rebuilt as needed.) This also removes the Bash and MySQL history of every project. DDEV creates the volume again on the next [`ddev start`](): ``` ddev poweroff docker volume rm ddev-global-cache ``` # Using the `ddev` Command Type `ddev help` or `ddev --help` or `ddev -h` in a terminal window to see the available DDEV [commands](). There are commands to configure a project, start, stop, describe, etc. Each command also has help using `ddev help ` or `ddev command -h`. For example, `ddev help snapshot` will show help and examples for the snapshot command. ## Interactive Dashboard Running `ddev` with no arguments launches an interactive terminal dashboard. The dashboard shows all your DDEV projects and their status, and lets you manage them with these keyboard shortcuts. ### Navigation | Key | Action | | --- | --- | | Enter | Open project detail view | | ↑ / ↓ or k / j | Move the selection, or scroll the detail and log views | | PgUp/b / PgDn/f, Home/g / End/G | Scroll the list, detail or logs a screen at a time, or jump to the start or end | | u / d | Scroll the detail or logs half a screen up or down | | Esc / Backspace | Go back | | / | Filter projects by name, type, status, or directory name; Enter, Tab, ↑ or ↓ ends typing, and Esc clears the filter | | r | Refresh | | ? | Show or hide the full list of keys | | q | Quit | ### Projects | Key | Action | | --- | --- | | s / S | Start / Stop the project | | R | Restart the project | | D | Delete the project and its database, like [`ddev delete`](); confirm with y, or O to skip the snapshot | | l | Launch project URL in browser | | m | Launch Mailpit in browser | | x | Launch XHGui in browser | | o | Open project directory in the file manager | | v / p | Open project in VS Code / PhpStorm | | e | SSH into web container (detail view) | | L | Follow logs (detail view) | | X | Toggle Xdebug (detail view) | | c | Copy the project URL (detail view) | | n | Make a database snapshot, like [`ddev snapshot`](), while the project runs (detail view) | | N | Restore the latest snapshot, like [`ddev snapshot restore --latest`](); confirm with y (detail view) | | U | Update the installed add-ons that have a newer release, like [`ddev add-on update`](); confirm with y (detail view) | | a / A | Start all / Stop all projects; confirm with y | | P | Poweroff all DDEV projects and containers; confirm with y | | C | Run `ddev config` interactively in the current directory | Commands such as start and delete run in the terminal itself, so their output, errors included, stays in the terminal after you quit the dashboard. In terminals that support [OSC 8 hyperlinks](), project paths and URLs in the dashboard and detail view are clickable, as are the VS Code and PhpStorm links in the detail view. The editor links use the `vscode://` and `phpstorm://` URL schemes, so they work only when that editor is installed, and some terminals block those schemes. PhpStorm does not register `phpstorm://` on Windows, so there the PhpStorm link needs a separately installed handler. The p key avoids this by using PhpStorm’s command-line launcher when it is on your `PATH`, which [JetBrains Toolbox]() can install. To disable the dashboard and show the classic help text instead, set [`no_tui: true`]() in your global configuration (`$HOME/.ddev/global_config.yaml`), or set the environment variable `DDEV_NO_TUI=true`. ### Terminal Compatibility The dashboard automatically detects your terminal’s color capabilities and adapts its output. If colors or styling don’t render correctly in your terminal, you have several options: - **Disable colors**: Run `NO_COLOR=1 ddev` or `export NO_COLOR=1` in your shell to disable all color output while keeping the dashboard functional. - **Use simple formatting globally**: Run `ddev config global --simple-formatting=true` to disable colors and table formatting across all DDEV commands, including the dashboard. See [`simple_formatting`](). - **Set `TERM` appropriately**: If your terminal supports color but DDEV doesn’t detect it, make sure your `TERM` environment variable is set correctly (e.g., `xterm-256color`). Some SSH clients or multiplexers like `tmux` or `screen` may need explicit configuration. - **Disable the dashboard entirely**: If the interactive dashboard doesn’t work well in your environment (e.g., a very limited terminal or CI), use `DDEV_NO_TUI=true` or set [`no_tui: true`]() in `$HOME/.ddev/global_config.yaml` to fall back to the classic help output. - [`ddev config`]() configures a project’s type and docroot, either interactively or with flags. - [`ddev start`]() starts up a project. - [`ddev launch`]() opens a web browser showing the project. - [`ddev list`]() shows current projects and their state. - [`ddev describe`]() gives all the info about the current project. - [`ddev ssh`]() takes you into the web container. - [`ddev exec `]() executes a command inside the web container. - [`ddev stop`]() stops a project and removes its memory usage (but does not throw away any data). - [`ddev poweroff`]() stops all resources that DDEV is using and stops the Mutagen daemon if it’s running. - [`ddev delete`]() destroys the database and DDEV’s knowledge of the project without touching your code. - [`ddev add-on`]() adds an add-on service. ## Lots of Other Commands - [`ddev mysql`]() gives direct access to the MySQL client and [`ddev psql`]() to the PostgreSQL `psql` client. - [`ddev sequelace`](), [`ddev tableplus`](), [`ddev tablepro`](), and [`ddev querious`]() (macOS only, if the app is installed) give access to the Sequel Ace, TablePlus, TablePro or Querious database browser GUIs. - [`ddev dbeaver`]() (if installed) gives access to the DBeaver database browser GUI. - [`ddev heidisql`]() (Windows/WSL2/Linux, if installed) gives access to the HeidiSQL database browser GUI. - [`ddev import-db`]() and [`ddev export-db`]() import or export SQL or compressed SQL files. - [`ddev composer`]() runs Composer inside the container. For example, `ddev composer install` will do a full composer install for you without even needing Composer on your computer. See [developer tools](). - [`ddev snapshot`]() makes a very fast snapshot of your database that can be easily and quickly restored with [`ddev snapshot restore`](). - [`ddev share`]() requires ngrok and at least a free account on [ngrok.com]() so you can let someone in the next office or on the other side of the planet see your project and what you’re working on. `ddev share -h` gives more info about how to set up ngrok. - [`ddev xdebug`]() enables Xdebug, `ddev xdebug off` disables it, and `ddev xdebug status` shows status. You can toggle Xdebug on and off easily using `ddev xdebug toggle`. - [`ddev xhprof`]() enables xhprof, `ddev xhprof off` disables it, and `ddev xhprof status` shows status. - [`ddev xhgui`]() enables XHGui, `ddev xhgui off` disables it, and `ddev xhgui status` shows status. - [`ddev dr`]() (Drupal 11.4+ only) gives direct access to the `dr` CLI, the next-generation Drupal CLI available in Drupal 11.4+. - [`ddev drush`]() (Drupal and Backdrop only) gives direct access to the `drush` CLI. - [`ddev artisan`]() (Laravel only) gives direct access to the Laravel `artisan` CLI. - [`ddev asterios`]() (Asterios only) gives direct access to the Asterios `asterios` CLI. - [`ddev magento`]() (Magento2 only) gives access to the `magento` CLI. - [`ddev maho`]() (Maho only) gives direct access to the `maho` CLI. - [`ddev console`]() (Symfony and Shopware only) gives access to the `symfony console` CLI. - [`ddev craft`]() (Craft CMS only) gives access to the `craft` CLI. - [`ddev npm`]() gives direct access to the `npm` CLI. - [`ddev npx`]() gives direct access to the `npx` CLI. - [`ddev yarn`]() gives direct access to the `yarn` CLI. - [`ddev cake`]() (CakePHP only) gives direct access to the `cake` CLI. - [`ddev sake`]() (Silverstripe CMS only) gives direct access to the `sake` CLI. - [`ddev spark`]() (CodeIgniter) gives direct access to the `spark` CLI. - [`ddev typo3`]() (TYPO3 only) gives direct access to the `typo3` CLI. - [`ddev wp`]() (WordPress and Bedrock only) gives direct access to the `wp` CLI. ## Node.js Tools `node`, `nodejs`, `npm`, `npx` and `yarn` are preinstalled in the web container. You can configure the default value of the installed Node.js version with the [`nodejs_version`]() option in `.ddev/config.yaml` or with `ddev config --nodejs-version`. There is also a [`ddev yarn`]() command. ## More Bundled Tools In addition to the [*commands*]() listed above, there are lots of tools included inside the containers: - [`ddev describe`]() tells how to access **Mailpit**, which captures email in your development environment. - Composer, Git, Node.js, npm, symfony, and dozens of other tools are installed in the web container, and you can access them via [`ddev ssh`]() or [`ddev exec`](). - [`ddev logs`]() gets you web server logs; `ddev logs -s db` gets database server logs. - `sqlite3` and the `mysql` and `psql` clients are inside the web container (and `mysql` or `psql` client is also in the `db` container). ## Exporting a Database You can export a database with [`ddev export-db`](), which outputs to stdout or with options to a file: ``` ddev export-db --file=/tmp/db.sql.gz ddev export-db --file=/tmp/db.sql ddev export-db --compression=zstd >/tmp/db.sql.zst ddev export-db >/tmp/db.sql.gz ``` ## `ddev import-files` To import static file assets for a project, such as uploaded images and documents, use the command [`ddev import-files`](). This command will prompt you to specify the location of your import asset, then import the assets into the project’s upload directory. To define a custom upload directory, set the [`upload_dirs`]() config option. If no custom upload directory is defined, the default will be used: - For Backdrop projects, this is the `files` directory. - For Craft CMS projects, this is the `files` directory. - For Drupal projects, this is the `sites/default/files` directory. - For Magento 1 projects, this is the `media` directory. - For Magento 2 projects, this is the `media` directory. - For Shopware projects, this is the `media` directory. - For Silverstripe CMS projects, this is the `assets` directory. - For TYPO3 projects, this is the `fileadmin` directory. - For WordPress projects, this is the `wp-content/uploads` directory. - For WordPress Bedrock projects, this is the `app/uploads` directory. Other project types need a custom configuration to be able to use this command. ``` $ ddev import-files Provide the path to the source directory or archive you wish to import. Please note: if the destination directory exists, it will be emptied and replaced with the import assets specified here. Path to file(s): ~/workspace/d10/.tarballs/files.tgz You provided an archive. Do you want to extract from a specific path in your archive? You may leave this blank if you wish to use the full archive contents. Archive extraction path: Successfully imported files for d10 ``` `ddev import-files` supports the following file types: `.tar`, `.tar.gz`, `.tar.xz`, `.tar.bz2`, `.tar.zst`, `.tgz`, or `.zip`. It can also import a directory containing static assets. If using a Tar or ZIP archive, the archive should contain *only the files inside the files directory*. For example in a Drupal site with files at `sites/default/files`, the archive should only contain the contents of the `files` directory; it should not contain a single `files` directory. To do this with the `tar` command, `cd` into the directory and create the archive there. An example Tar file creation is: ``` cd web/sites/default/files tar -czf ~/Downloads/files.tgz . ``` If you want to use `import-files` without answering prompts, use the `--source` or `-s` flag to provide the path to the import asset. If you’re importing an archive, and wish to specify the path within the archive to extract, you can use the `--extract-path` flag in conjunction with the `--source` flag. Example: `ddev import-files --source=/tmp/files.tgz` When multiple `upload_dirs` are defined and you want to import to another upload dir than the first one, use the `--target` or `-t` flag to provide the path to the desired upload dir: `ddev import-files --target=../private --source=/tmp/files.tgz` See `ddev help import-files` for more examples. ## Snapshotting and Restoring a Database The project database is stored in a Docker volume, but can be snapshotted (and later restored) with the [`ddev snapshot`]() command. A snapshot is automatically taken when you run [`ddev stop --remove-data`](). For example: ``` ddev snapshot Creating database snapshot d9_20220107124831-mariadb_10.3.gz Created database snapshot d9_20220107124831-mariadb_10.3.gz ddev snapshot restore d9_20220107124831 Stopping db container for snapshot restore of 'd9_20220107124831-mariadb_10.3.gz'... Restored database snapshot d9_20220107124831-mariadb_10.3.gz ``` Snapshots are stored as gzipped files in the project’s `.ddev/db_snapshots` directory, and the file created for a snapshot can be renamed as necessary. For example, if you rename the above `d9_20220107124831-mariadb_10.3.gz` file to `working-before-migration-mariadb_10.3.gz`, then you can use `ddev snapshot restore working-before-migration`. (The description of the database type and version—`mariadb_10.3`, for example—must remain intact.) To restore the latest snapshot add the `--latest` flag (`ddev snapshot restore --latest`). List snapshots for an existing project with `ddev snapshot --list`. (Add the `--all` option for an exhaustive list; `ddev snapshot --list --all`.) You can remove all of them with `ddev snapshot --cleanup`, or remove a single snapshot with `ddev snapshot --cleanup --name `. > [!TIP] > > The default 120-second timeout may be inadequate for restores with very large snapshots or slower systems. You can increase this timeout by setting [`default_container_timeout`]() to a higher value. > > A timeout doesn’t necessarily mean the restore failed; you can watch the snapshot restore complete by running `ddev logs -s db`. ## Interacting with Your Project DDEV provides several commands to facilitate interacting with your project in the development environment. These commands can be run within the working directory of your project while the project is running in DDEV. ### Executing Commands in Containers The [`ddev exec`]() command allows you to run shell commands in the container for a DDEV service. By default, commands are executed on the web service container, in the docroot path of your project. This allows you to use [the developer tools included in the web container](). For example, to run the `ls` command in the web container, you would run `ddev exec ls` or `ddev . ls`. To run a shell command in the container for a different service, use the `--service` (or `-s`) flag at the beginning of your `exec` command to specify the service the command should be run against. For example, to run the MySQL client in the database, container, you would run `ddev exec --service db mysql`. To specify the directory in which a shell command will be run, use the `--dir` flag. For example, to see the contents of the `/usr/bin` directory, you would run `ddev exec --dir /usr/bin ls`. To run privileged commands, sudo can be passed into `ddev exec`. For example, to update the container’s apt package lists, use `ddev exec sudo apt-get update`. Commands can also be executed using the shorter `ddev . ` alias. Normally, `ddev exec` commands are executed in the container using Bash, which means that environment variables and redirection and pipes can be used. For example, a complex command like `ddev exec 'ls -l ${DDEV_FILES_DIR} | grep x >/tmp/junk.out'` will be interpreted by Bash and will work. However, there are cases where Bash introduces too much complexity and it’s best to run the command directly. In those cases, something like `ddev exec --raw ls -l "dir1" "dir2"` may be useful. With `--raw`, the `ls` command is executed directly instead of the full command being interpreted by Bash. But you cannot use environment variables, pipes, redirection, etc. ### SSH Into Containers The [`ddev ssh`]() command opens an interactive Bash or sh shell session to the container for a DDEV service. The web service is connected by default, and the session can be ended by typing `exit`. To connect to another service, use the `--service` flag to specify the service you want to connect to. For example, to connect to the database container, you would run `ddev ssh --service db`. To specify the destination directory, use the `--dir` flag. For example, to connect to the database container and be placed into the `/home` directory, you would run `ddev ssh --service db --dir /home`. You can also use your personal SSH keys within the web container. Run `ddev auth ssh` to add the keys from your `~/.ssh` directory and provide a passphrase, and those keys will be usable from within the web container. You generally only have to `ddev auth ssh` one time per computer reboot. This is a very popular approach for accessing private Composer repositories, or for using `drush` aliases against remote servers. If your keys live in an SSH agent rather than in `~/.ssh`, as with 1Password or a hardware key, see [Using an Existing SSH Agent](<#using-an-existing-ssh-agent>). ### Using an Existing SSH Agent If you already use an SSH agent, such as the macOS agent, 1Password, Secretive, `gpg-agent` with a YubiKey, or one forwarded with `ssh -A`, DDEV’s containers can use it directly. Set this once: ``` ddev config global --ssh-agent-upstream=host ddev auth ssh # lists the keys your containers can use ``` After that, every project’s containers use your agent when they start, with nothing to add per project. `ddev auth ssh` only shows which keys are available, and `ddev exec ssh -T git@github.com` tests them. Every container on the DDEV network can ask your agent to sign while this is on; agents that confirm each use, like 1Password, limit that exposure. If `ddev auth ssh` says “Make sure that agent is running and holds your keys”, your agent is stopped or locked; open it or load your keys, and DDEV picks it up again without a restart. To go back to adding key files, run `ddev config global --ssh-agent-upstream=""` and then `ddev auth ssh`. If a connection instead fails with “Too many authentication failures”, the server capped how many keys it will try, and relaying a whole agent offers more than the few keys you’d add by hand; see [Too Many Authentication Failures]() to limit which key gets offered per host. > [!TIP] > > **macOS: which agent do my containers get?** > > That depends on your Docker provider: > > | Provider | What to do | Agent containers get | > | --- | --- | --- | > | OrbStack | Nothing extra | The agent named by `IdentityAgent` in `~/.ssh/config`, such as 1Password, otherwise the macOS agent. Restart OrbStack after changing `~/.ssh/config`. | > | Docker Desktop | Nothing extra | Always the macOS agent | > | Colima | Start it with `colima start --ssh-agent` | Always the macOS agent | > | Lima | Run `limactl edit --set .ssh.forwardAgent=true` once | Always the macOS agent | > | Rancher Desktop, Podman | Not supported | DDEV warns and uses its own agent; add key files with `ddev auth ssh` as usual | > > To use 1Password, Secretive, or `gpg-agent`: > > - With OrbStack, set `IdentityAgent` in `~/.ssh/config` as your agent’s setup instructions describe, then restart OrbStack. > - Docker Desktop, Colima, and Lima ignore `IdentityAgent`. Point the system-wide `SSH_AUTH_SOCK` at your agent as its documentation describes; for 1Password, see [Configure SSH\_AUTH\_SOCK globally for every client](). > - Exporting `SSH_AUTH_SOCK` in your shell profile doesn’t reach any of these providers. > > To use the macOS agent, load your keys into it, saving the passphrase in the Keychain: > > ``` > ssh-add --apple-use-keychain ~/.ssh/id_ed25519 > ssh-add -l > ``` > [!TIP] > > **Linux: pointing at your own agent** > > `host` uses the agent in `$SSH_AUTH_SOCK` when a project starts. A forwarded agent (`ssh -A`) gets a new socket for each login, so run `ddev auth ssh` again after logging in again. When the socket is a symlink, as with VS Code Remote’s `/tmp/vscode-ssh-auth-sock-*`, DDEV uses the socket it points to. After VS Code or SSH reconnects, a terminal opened earlier still points to the old, dead socket, so run `ddev auth ssh` from a new terminal. A desktop agent’s socket doesn’t change, so you can set it directly, for example on Ubuntu with `ddev config global --ssh-agent-upstream=/run/user/$(id -u)/gcr/ssh`. An agent you start yourself with `eval $(ssh-agent -s)` works when you run `ddev` from that shell; to use it from any shell, start it on a fixed socket with `mkdir -p -m 700 ~/.ssh-agent && eval $(ssh-agent -a ~/.ssh-agent/agent.sock -s)` and set `ddev config global --ssh-agent-upstream=$HOME/.ssh-agent/agent.sock`. Keep the socket in its own directory rather than `~/.ssh`, because DDEV mounts the socket’s whole directory into the `ddev-ssh-agent` container. > [!TIP] > > **Docker Desktop: using an agent forwarded with `ssh -A`** > > Docker Desktop relays the agent in the `SSH_AUTH_SOCK` it started with, not your shell’s, and a forwarded agent gets a new socket for each login. To follow each new login without restarting Docker Desktop, start it once on a fixed path and point that path at each login’s socket from `~/.ssh/rc`, which `sshd` runs at login: > > ``` > # Once. On macOS, use 'launchctl setenv SSH_AUTH_SOCK ...' and 'docker desktop restart' instead. > mkdir -p -m 700 ~/.ssh-agent-forward > systemctl --user set-environment SSH_AUTH_SOCK=$HOME/.ssh-agent-forward/agent.sock > systemctl --user restart docker-desktop > > # In ~/.ssh/rc: > if [ -n "$SSH_AUTH_SOCK" ]; then ln -sf "$SSH_AUTH_SOCK" ~/.ssh-agent-forward/agent.sock; fi > ``` > > The most recent `ssh -A` login wins, and after it ends, containers can’t reach an agent until you log in again. With X11 forwarding, `~/.ssh/rc` also has to run `xauth`, as `man sshd` describes. The Docker Desktop setting doesn’t last past a reboot. > [!TIP] > > **WSL2: bridging a Windows agent** > > DDEV can’t reach Windows agents such as 1Password and the Windows OpenSSH agent directly, because they listen only on a Windows named pipe. Bridge that pipe to a Unix socket with `socat` in WSL2 and [`npiperelay`]() on Windows, then point DDEV at the socket. DDEV doesn’t start the bridge for you. This works with Docker Desktop’s WSL integration as well as Docker installed inside WSL2, although Docker Desktop on Windows doesn’t forward an agent of its own. > > 1. Install `socat` in WSL2 with `sudo apt install socat`, and `npiperelay` in PowerShell with `winget install albertony.npiperelay`. `npiperelay` is x64-only; Windows on Arm runs it under emulation. > 2. With [systemd enabled in WSL2](), run the bridge as a user service, which starts with WSL2 whether or not you open a shell. Run this in a WSL2 shell, where `npiperelay.exe` is on `PATH`; the service itself doesn’t get the Windows `PATH`, so the unit records the full path: > > ``` > mkdir -p ~/.config/systemd/user > cat > ~/.config/systemd/user/ssh-agent-bridge.service < [Unit] > Description=Bridge the Windows SSH agent to ~/.1password/agent.sock > > [Service] > ExecStartPre=/usr/bin/mkdir -p -m 700 %h/.1password > ExecStart=/usr/bin/socat UNIX-LISTEN:%h/.1password/agent.sock,fork,unlink-early EXEC:"$(command -v npiperelay.exe) -ei -s //./pipe/openssh-ssh-agent",nofork > Restart=on-failure > SuccessExitStatus=143 > > [Install] > WantedBy=default.target > EOF > systemctl --user enable --now ssh-agent-bridge > ``` > > Without systemd, add this to `~/.bashrc` instead, so that each new shell starts the bridge if it isn’t already running: > > ``` > export SSH_AUTH_SOCK=$HOME/.1password/agent.sock > ssh-add -l >/dev/null 2>&1 > if [ $? -eq 2 ]; then > rm -f "$SSH_AUTH_SOCK" > mkdir -p -m 700 "$(dirname "$SSH_AUTH_SOCK")" > (setsid socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &) >/dev/null 2>&1 > fi > ``` > 3. Check that `SSH_AUTH_SOCK=~/.1password/agent.sock ssh-add -l` lists your keys, then run: > > ``` > ddev config global --ssh-agent-upstream=$HOME/.1password/agent.sock > ddev auth ssh > ``` > > To use the same agent for `ssh` and `git` in WSL2, add `export SSH_AUTH_SOCK=$HOME/.1password/agent.sock` to `~/.bashrc`. If the bridge stops, containers can’t use your keys until it runs again, and nothing in DDEV needs a restart. > [!TIP] > > **Traditional Windows: key files, or piping from 1Password** > > `ssh_agent_upstream` doesn’t work here. Windows agents, including 1Password and the Windows OpenSSH agent, listen on a named pipe that Docker Desktop and other Windows providers can’t pass to containers. DDEV warns and uses its own agent, so add your key files from `%USERPROFILE%\.ssh` with `ddev auth ssh`. To use a key stored in 1Password without saving it to a file, pipe it from the [1Password CLI](), in PowerShell or Git Bash: > > ``` > op read "op:////private key?ssh-format=openssh" | ddev auth ssh -f - > ``` > > Turn on **Settings** \> **Developer** \> **Integrate with 1Password CLI** in the 1Password app first, and add `--account .1password.com` to `op read` if you’re signed in to more than one account. Run the command again after `ddev poweroff` or a reboot, because DDEV’s agent forgets keys when it stops. ### `ddev logs` The [`ddev logs`]() command allows you to easily view error logs from the web container (both nginx/Apache and php-fpm logs are concatenated). To follow the logs in real time, run `ddev logs -f`. When you’re done, press CTRL + C to exit the log trail. Similarly, `ddev logs -s db` will show logs from a running or stopped database container. ## Stopping a Project To remove a project’s containers, run [`ddev stop`]() in the project’s working directory. To remove any running project’s containers regardless of context, specify the project name as an argument: `ddev stop `. `ddev stop` is *not* destructive. It removes the Docker containers but does not remove the database for the project, and does nothing to your code. This allows you to have many configured projects with databases loaded without wasting Docker containers on unused projects. **`ddev stop` does not affect the project codebase and files.** To remove the imported database for a project, use the flag `--remove-data`, as in `ddev stop --remove-data`. This command will destroy both the containers and the imported database contents. # Managing CMS Settings Any CMS-specific project type, meaning any of the non-generic [CMS Quickstarts](), has settings that DDEV manages to save you time and optimize configuration for local development. Generally, DDEV will: - Create a main settings file if none exists, like Drupal’s `settings.php`. - Create a specialty config file with DDEV-specific settings, like `AdditionalSettings.php` for TYPO3 or `settings.ddev.php` for Drupal. - Add an include of the specialty file if needed, like adding `settings.ddev.php` include to the bottom of Drupal’s `settings.php`. While this reduces setup time for new users, makes it easier to try out a CMS, and speeds up project creation, you may still want to modify or override DDEV’s CMS-specific behavior. ## Controlling or Removing CMS Settings There are several ways to back off DDEV’s CMS settings management: 1. **Take control of files by removing the `#ddev-generated` comment.** DDEV will automatically update any it’s added containing a `#ddev-generated` comment. This means you don’t need to touch that file, but also that any changes you make will be overwritten. As soon as you remove the comment, DDEV will ignore that file and leave you fully in control over it. (Don’t forget to check it into version control!) > **Reversing the change** > > If you change your mind and want DDEV to take over the file again, delete it and run [`ddev start`](). DDEV will recreate its own version, which you may want to remove from your Git project. 2. **Disable settings management.** You can tell DDEV to use a specific project type without creating settings files by either setting [`disable_settings_management`]() to `true` or running [`ddev config --disable-settings-management`](). 3. **Switch to the generic PHP project type.** If you don’t want DDEV’s CMS-specific settings, you can switch your project to the generic `php` type by editing [`type: php`]() in the project’s settings or running [`ddev config --project-type=php`](). DDEV will no longer create or tweak any settings files. You’ll lose any perks from the nginx configuration for the CMS, but you can always customize [nginx settings]() or [Apache settings]() separately. 4. **Un-set the `$IS_DDEV_PROJECT` environment variable.** This environment variable is set `true` by default in DDEV’s environment, and can be used to fence off DDEV-specific behavior. When it’s empty, the important parts of `settings.ddev.php` and `AdditionalSettings.php` (for TYPO3) are not executed. This means that DDEV’s `settings.ddev.php` won’t be invoked if it somehow ends up in a production environment or in a non-DDEV local development environment. > [!TIP] > > **Ignore `.ddev/.gitignore`** > > The `.ddev/.gitignore` file is created when you run `ddev start` and `disable_settings_management` is `false`. You should *not* check this file in, since it ignores itself and DDEV’s temporary and automatically-managed files. This makes it easier for teams to share the `.ddev` folder via Git, even if the `.ddev/.gitignore` file changes with different versions. ## CMS-Specific Help and Techniques ### Backdrop Specifics #### Configuration considerations By default, Backdrop stores both active and staging configuration in the filesystem. In this case, if you are updating the database (either from a live site, or reloading while working locally) it can be important to also refresh the active configuration directory to make sure your database structure is in line with what your configuration is describing. Backdrop also allows you to store your active configuration in the database ([as of Backdrop 1.28.0]()), which can be simpler while developing locally, especially if you are making use of DDEV’s snapshot feature. In this case, the database and the active configuration are effectively bundled and refreshing the database also refreshes the active configuration. ### Drupal Specifics #### Drupal Settings Files By default, DDEV will create settings files for your project that work out of the box. It creates a `sites/default/settings.ddev.php` and adds an include in `sites/default/settings.php` to bring that in. There are guards to prevent the `settings.ddev.php` from being active when the project is not running under DDEV, but it still should not be checked in and is gitignored. #### Database requirements for Drupal 9.5+ - Using MySQL or MariaDB, Drupal requires `SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED` and DDEV does this for you on [`ddev start`](). - Using PostgreSQL, Drupal requires the`pg_trm` extension. DDEV creates this extension automatically for you on `ddev start`. #### Twig Debugging With the default Drupal configuration, it’s very difficult to debug Twig templates; you need to use `development.services.yml` instead of `services.yml`. Add this line in your `settings.php` or `settings.local.php`. See discussion at [drupal.org]() and the Drupal documentation. ``` $settings['container_yamls'][] = DRUPAL_ROOT . '/sites/development.services.yml'; ``` #### Drush and Xdebug Drush 13+ disables Xdebug even when [Xdebug is enabled on DDEV](). As per [Drush docs]() you can: - Enable it for a single Drush command by running: `ddev drush --xdebug` - Set `DRUSH_ALLOW_XDEBUG=1` [environment variable](), allowing every Drush call to be run with Xdebug when the PHP extension is enabled. #### Multisite 1. Start with the [DDEV Drupal 8 Multisite Recipe](). 2. Update configuration files. 1. Update each `site/{site_name}/settings.php`: ``` /** * DDEV environments will have $databases (and other settings) set * by an auto-generated file. Make alterations here for this site * in a multisite environment. */ elseif (getenv('IS_DDEV_PROJECT') == 'true') { /** * Alter database settings and credentials for DDEV environment. * Includes loading the DDEV-generated `default/settings.ddev.php`. */ include $app_root . '/' . $site_path . '/settings.databases.ddev.inc'; } ``` 2. Add a `settings.databases.ddev.inc` in each `site/{site_name}/`: ``` /** * Fetch DDEV-generated database credentials and other settings. */ require $app_root . '/sites/default/settings.ddev.php'; /** * Alter default database for this site. `settings.ddev.php` will have * “reset” this to 'db'. */ $databases['default']['default']['database'] = 'site_name'; ``` 3. Update your [`web_environment`]() config option if you’re using site aliases: ``` web_environment: # Make DDEV Drush shell PIDs last for entire life of the container # so `ddev drush site:set @alias` persists for all Drush connections. # https://chrisfromredfin.dev/posts/drush-use-ddev/ - DRUSH_SHELL_PID=PERMANENT ``` ### MODX Specifics #### MODX Settings File On [`ddev start`](), DDEV manages MODX Revolution’s main configuration file, `core/config/config.inc.php`, for both MODX 2.x and 3.x. It writes the DDEV database credentials (database name, user, and password are all `db`; host is `db`) along with the MODX path and URL constants, so a project whose database has been imported or seeded connects and boots straight away, including the `/manager/` backend. The generated file carries the `#ddev-generated` signature. If you want to take over the file and manage it yourself, remove that signature line and DDEV will respect your version and won’t overwrite it. DDEV also leaves a config file that was written by the MODX installer (which has no signature) alone. The `config.core.php` stub files (at the web root and in `core/`, `manager/`, and `connectors/`) only define `MODX_CORE_PATH` and `MODX_CONFIG_KEY`; DDEV does not touch them. ### TYPO3 Specifics #### Settings Files On [`ddev start`](), DDEV creates a `config/system/additional.php` file with database configuration in it. #### Setup a Base Variant (since TYPO3 9.5) Since TYPO3 9.5 you have to setup a `Site Configuration` for each site you like to serve. To be able to browse the site on your local environment, you have to set up a `Base Variant` in your `Site Configuration` depending on your local context. In this example we assume a `Application Context` `Development/DDEV` which can be set in the DDEV’s `config.yaml`: ``` web_environment: - TYPO3_CONTEXT=Development/DDEV ``` This variable will be available after the project start or restart. Afterwards add a `Base Variant` to your `Site Configuration`: ``` baseVariants: - base: 'https://example.com.ddev.site/' condition: 'applicationContext == "Development/DDEV"' ``` See also [TYPO3 Documentation](). ### WordPress Specifics #### WP-CLI and changing the docroot in config.yaml [`ddev wp`]() defaults WP-CLI’s `path` to your project’s docroot, so WP-CLI still finds WordPress when the docroot is something other than the project root (`''`). If WordPress does not live at the docroot, override that default with a `wp-cli.yml` file in the project root directory. [Bedrock](), for example, serves from `web` but keeps WordPress in `web/wp`, so its `wp-cli.yml` contains: ``` path: web/wp ``` A `--path` flag, an [alias](), and `wp-cli.local.yml` all take precedence over the DDEV default in the same way. #### WordPress Environment Type By default, [`wp_get_environment_type()`]() returns `production`. However, you can override it in one of two ways: 1. Setting an environment variable, such as with the following [ddev command](): ``` ddev config --web-environment-add="WP_ENVIRONMENT_TYPE=local" ``` 2. Defining the `WP_ENVIRONMENT_TYPE` constant: To do this, modify your `wp-config-ddev.php` file by [removing]() the `#ddev-generated` line and adding this code snippet: ``` defined( 'WP_ENVIRONMENT_TYPE' ) || define( 'WP_ENVIRONMENT_TYPE', 'local' ); ``` # Commands You can tell DDEV what to do by running its commands. This page details each of the available commands and their options, or flags. > [!TIP] > > **Using TUI (Terminal UI)** > > Running `ddev` without any arguments will launch the [interactive dashboard]() in your terminal. > > To disable the dashboard and show the classic help text instead, set [`no_tui: true`]() in your global configuration (`$HOME/.ddev/global_config.yaml`), or set the environment variable `DDEV_NO_TUI=true`. Type `ddev help` or `ddev --help` or `ddev -h` to see this list in your terminal: ``` → ddev help Create and maintain a local web development environment. Docs: https://docs.ddev.com Support: https://docs.ddev.com/en/stable/users/support Usage: ddev [command] Available Commands: auth A collection of authentication commands blackfire Enable or disable blackfire.io profiling (global shell web container command) clean Removes items ddev has created composer Executes a composer command within the web container ... ``` Use [`ddev help`](<#help>) to learn more about a specific command, like this example for [`ddev describe`](<#describe>): ``` → ddev help describe Get a detailed description of a running ddev project. Describe provides basic information about a ddev project, including its name, location, url, and status. It also provides details for MySQL connections, and connection information for additional services like Mailpit. You can run 'ddev describe' from a project directory to describe that project, or you can specify a project to describe by running 'ddev describe '. Usage: ddev describe [projectname] [flags] Aliases: describe, status, st, desc Examples: ddev describe ddev describe ddev status ddev st Flags: -h, --help help for describe Global Flags: -j, --json-output If true, user-oriented output will be in JSON format. ``` ## Global Flags Two flags are available for every command: - `--help` or `-h`: Outputs more information about a command rather than executing it. - `--json-output` or `-j`: Format user-oriented output in JSON. --- ## `add-on` *Aliases: `addon`, `add-ons`, `addons`.* [Add-on]() commands. Environment variables: - `DDEV_GITHUB_TOKEN`: [GitHub token]() for `ddev add-on` requests to avoid rate limiting. No privileges required for public repositories, scopes are only needed for [private repositories](). ``` export DDEV_GITHUB_TOKEN= ddev add-on list ``` - `GH_TOKEN`: Alternative GitHub token variable (lower priority than `DDEV_GITHUB_TOKEN`). - `GITHUB_TOKEN`: Alternative GitHub token variable (lowest priority). ### `add-on get` *Alias: `add-on install`.* Download an add-on (service, provider, etc.). Dependencies declared in the add-on’s `install.yaml` are installed automatically unless `--skip-deps` is used. Flags: - `--skip-deps`: Skip installing add-on dependencies (default `false`) - `--project `: Specify a project to install the add-on into. Defaults to checking for a project in the current directory. - `--version `: Specify a version, branch name, or commit SHA to download - `--default-branch`: Install from the last commit in the default branch (default `false`) - `--pr `: Install from a pull request number - `--verbose`, `-v`: Output verbose error information with Bash `set -x` (default `false`) Note: The `--version`, `--default-branch`, and `--pr` flags are mutually exclusive. Example: ``` # Download the official Redis add-on ddev add-on get ddev/ddev-redis # Get debug info about `ddev add-on get` failure ddev add-on get ddev/ddev-redis --verbose # Download the official Redis add-on, version v1.0.4 ddev add-on get ddev/ddev-redis --version v1.0.4 # Download the official Redis add-on from the main branch ddev add-on get ddev/ddev-redis --version main # Download the official Redis add-on from a specific commit SHA ddev add-on get ddev/ddev-redis --version b50ac77 # Download the official Redis add-on from the latest commit in the default branch ddev add-on get ddev/ddev-redis --default-branch # Download the official Redis add-on from pull request #54 ddev add-on get ddev/ddev-redis --pr 54 # Download the Drupal Solr add-on from its v1.2.3 release tarball ddev add-on get https://github.com/ddev/ddev-drupal-solr/archive/refs/tags/v1.2.3.tar.gz # Download the Drupal Contrib add-on from its main branch tarball ddev add-on get https://github.com/ddev/ddev-drupal-contrib/tarball/main # Download the OpenSearch add-on from a pull request #15 tarball ddev add-on get https://github.com/ddev/ddev-opensearch/tarball/refs/pull/15/head # Copy an add-on available in another directory ddev add-on get /path/to/package # Copy an add-on from a tarball in another directory ddev add-on get /path/to/tarball.tar.gz # Download the official Redis add-on and install it into a project named "my-project" ddev add-on get ddev/ddev-redis --project my-project # Install an add-on without installing its dependencies ddev add-on get ddev/ddev-redis-insight --skip-deps ``` **Automatic Dependency Installation:** When an add-on declares dependencies in its `install.yaml`, DDEV will automatically install any missing dependencies before installing the add-on itself. Dependencies support the same formats as `ddev add-on get`: - GitHub repositories: `ddev/ddev-redis` - Direct URLs: `https://example.com/addon.tar.gz` DDEV will detect and prevent circular dependencies with clear error messages. In general, you can run `ddev add-on get` multiple times without doing any damage. Updating an add-on can be done by running `ddev add-on get `, or use `ddev add-on update` to update every installed add-on at once. If you have changed an add-on file and removed the `#ddev-generated` marker in the file, that file will not be touched and DDEV will let you know about it. > [!TIP] > > **How to install add-ons from private repositories?** > > See [Private Add-ons]() for details. ### `add-on update` Check installed add-ons for a newer release and install it. If no add-on names are provided, all installed add-ons are checked. Add-ons installed from a local directory or from a non-GitHub tarball URL can’t be checked automatically and are skipped. Flags: - `--dry-run`: Show which add-ons would be updated without installing anything (default `false`) - `--project `: Specify a project to update the add-ons for. Defaults to checking for a project in the current directory. - `--verbose`, `-v`: Output verbose error information with Bash `set -x` (default `false`) - `--yes`, `-y`: Skip confirmation prompt. Example: ``` # Check and update all installed add-ons ddev add-on update # Update a single installed add-on ddev add-on update redis ddev add-on update ddev/ddev-redis # See what would be updated without installing anything ddev add-on update --dry-run # Update add-ons for a specific project ddev add-on update --project my-project ``` ### `add-on remove` Remove an installed add-on. Accepts the full add-on name, the short name of the repository, or with owner/repository format. Flags: - `--project `: Specify a project to remove the add-on from. Defaults to checking for a project in the current directory. - `--verbose`, `-v`: Output verbose error information with Bash `set -x` (default `false`) Example: ``` ddev add-on remove redis ddev add-on remove ddev-redis ddev add-on remove ddev/ddev-redis ddev add-on remove ddev/ddev-redis --project my-project ``` ### `add-on list` List available or installed DDEV add-ons. Flags: - `--installed`: List installed add-ons - `--project `: Specify the project for which to list add-ons. Can only be used with the `--installed` flag. Defaults to checking for a project in the current directory. - `--wrap-table`, `-W`: Display table with wrapped text instead of truncating. Example: ``` # List all available add-ons ddev add-on list # List installed add-ons ddev add-on list --installed # List installed add-ons for a specific project ddev add-on list --installed --project my-project # List with full untruncated add-on names and descriptions ddev add-on list --wrap-table ``` ### `add-on search` Search available DDEV add-ons by name or description. Flags: - `--wrap-table`, `-W`: Display table with wrapped text instead of truncating. Example: ``` # Search for Redis-related add-ons ddev add-on search redis # Search for database-related add-ons ddev add-on search database # Search with multiple terms (all must be present) ddev add-on search redis web # Search with multiple terms using quotes (currently same behavior) ddev add-on search "redis insight" # Search with full untruncated output ddev add-on search redis --wrap-table ``` ## `aliases` Shows all aliases for each command in the current context (global or project). ``` # Print a list of all available command aliases ddev aliases ``` ## `artisan` *Alias: `art`.* Run the `artisan` command; available only in projects of type `laravel`, and only available if `artisan` is in the project root. ``` # Show all artisan subcommands ddev artisan list ``` ## `asterios` *Alias: `ast`.* Run the `asterios` command; available only in projects of type `asterios`, and only available if `asterios` is in the project root. ``` # Show all asterios subcommands ddev asterios list ``` ## `auth` Authentication commands. ### `auth ssh` Add [SSH key authentication]() to the `ddev-ssh-agent` container. The command can be executed multiple times to add more keys, or you can use the `-f` flag for several individual keys for example `ddev auth ssh -f /path/to/first_id_rsa -f /path/to/second_id_rsa`. If [`ssh_agent_upstream`]() is set, `ddev auth ssh` without flags lists the keys in that agent instead of adding keys. To add a key held in a secret manager or CI variable without writing it to a file, pipe it in with `-f -`. The key must not have a passphrase. On traditional Windows, where `ssh_agent_upstream` isn’t available, this is how to use a key stored in 1Password; see [Using an Existing SSH Agent](). Example: ``` # Add your SSH keys to the SSH agent container ddev auth ssh # Add your SSH keys from ~/custom/path/to/ssh directory ddev auth ssh -d ~/custom/path/to/ssh # Add your SSH keys from ~/.ssh/id_ed25519 and ~/.ssh/id_rsa files ddev auth ssh -f ~/.ssh/id_ed25519 -f ~/.ssh/id_rsa # Add a key from 1Password's CLI, or any command that prints a private key op read "op://Private/deploy key/private key?ssh-format=openssh" | ddev auth ssh -f - ``` Flags: - `--ssh-key-file`, `-f`: Path to SSH private key file, use the flag multiple times to add more keys, or `-` to read one key without a passphrase from stdin. - `--ssh-key-path`, `-d`: Path to directory with SSH private key(s), use the flag multiple times to add more directories. ## `blackfire` Enable or disable [Blackfire profiling]() (global shell web container command). ``` # Display Blackfire profiling status ddev blackfire status # Start Blackfire profiling ddev blackfire on # Stop Blackfire profiling ddev blackfire off ``` > [!TIP] > > There are synonyms for the `on` and `off` arguments that have the exact same effect: > > - `on`: `start`, `enable`, `true` > - `off`: `stop`, `disable`, `false` ## `cake` Run the `cake` command; available only in projects of type `cakephp`, and only available if `cake.php` is in bin folder. ``` # Show all cake subcommands ddev cake ``` ## `clean` Removes items DDEV has created. (See [Uninstalling DDEV]().) Flags: - `--all`, `-a`: Clean all DDEV projects. - `--dry-run`: Run the clean command without deleting. - `--yes`, `-y`: Skip confirmation prompt. Example: ``` # Preview cleaning all projects without actually removing anything ddev clean --dry-run --all # Clean all projects ddev clean --all # Clean my-project and my-other-project ddev clean my-project my-other-project ``` ## `composer` *Alias: `co`.* Executes a [Composer command]() within the web container. `ddev composer create-project` is a special command that is an adaptation of `composer create-project`. See [DDEV and Composer]() for more information. Example: ``` # Install Composer packages ddev composer install ``` Example of `ddev composer create-project`: ``` # Create a new Drupal project in the current directory ddev composer create-project drupal/recommended-project . ``` ## `config` Create or modify a DDEV project’s configuration in the current directory. By default, `ddev config` will not change configuration that already exists in your `.ddev/config.yaml`, it will only make changes you specify with flags. However, if you want to autodetect everything, `ddev config --update` will usually do everything you need. > [!TIP] > > **You can also set these via YAML!** > > These settings, plus a few more, can be set by editing stored [Config Options](). Example: ``` # Start interactive project configuration ddev config # Accept defaults on a new project. This is the same as hitting # on every question in `ddev config` ddev config --auto # Detect docroot, project type (except for `generic` type), # and expected defaults for an existing project ddev config --update # Configure a Drupal project with a `web` document root ddev config --docroot=web --project-type=drupal # Switch the project’s default `nginx-fpm` to `apache-fpm` ddev config --webserver-type=apache-fpm ``` Flags: - `--additional-fqdns`: Comma-delimited list of project FQDNs or `--additional-fqdns=""` to remove any configured FQDNs. - `--additional-hostnames`: Comma-delimited list of project hostnames or `--additional-hostnames=""` to remove any configured additional hostnames. - `--auto`: Automatically run config without prompting. - `--bind-all-interfaces`: Bind host ports on all interfaces, not only on the localhost network interface. - `--composer-root`: The relative path, from the project root, to the directory containing `composer.json`. (This is where all Composer-related commands are executed.) - `--composer-root-default`: Unset a `web` service Composer root directory override, the same as `--composer-root=""`. - `--composer-version`: Specify override for Composer version in the web container. This may be `""`, `"2"`, `"2.2"`, `"stable"`, `"preview"`, `"snapshot"`, or a specific version. - `--corepack-enable`: Whether to run `corepack enable` on Node.js configuration. - `--database`: Specify the database `type:version` to use (see [default]()). - `--db-image`: Set the `db` container image ([for advanced use only]()). - `--db-image-default`: Set the default `db` container image, the same as `--db-image=""`. - `--db-working-dir`: Override the default working directory for the `db` service. - `--db-working-dir-default`: Unset a `db` service working directory override, the same as `--db-working-dir=""`. - `--dbimage-extra-packages`: Comma-delimited list of Debian packages that should be added to `db` container when the project is started or `--dbimage-extra-packages=""` to remove previously configured packages. - `--ddev-version-constraint`: Specify a `ddev_version_constraint` to validate `ddev` against. - `--default-container-timeout`: Default time in seconds that DDEV waits for all containers to become ready on start (see [default]()). - `--disable-settings-management`: Prevent DDEV from creating or updating CMS settings files. - `--disable-upload-dirs-warning`: Disable warnings about `upload-dirs` not being set when using `--performance-mode=mutagen`. - `--docker-buildx-version`: Control which `docker-buildx` plugin to use (see [default]()). - `--docroot`: Provide the relative docroot of the project, like `docroot` or `htdocs` or `web`, defaults to empty, the current directory. - `--fail-on-hook-fail`: Decide whether `ddev start` should be interrupted by a failing hook. - `--host-db-port`: The `db` container’s localhost-bound port. - `--host-https-port`: The `web` container’s localhost-bound HTTPS port. - `--host-webserver-port`: The `web` container’s localhost-bound HTTP port. - `--mailpit-http-port`: Router port to be used for Mailpit HTTP access (see [default]()). - `--mailpit-https-port`: Router port to be used for Mailpit HTTPS access (see [default]()). - `--ngrok-args`: Provide extra args to `ngrok` in `ddev share` (deprecated: use [`share_provider_args`]() in `.ddev/config.yaml`). - `--no-project-mount`: Whether to skip mounting project code into the `web` container. - `--nodejs-root`: The relative path, from the project root, to the directory containing the Node.js version file (see [`nodejs_root`]()). - `--nodejs-version`: Specify the Node.js version to use (see [default]()). - `--omit-containers`: Comma-delimited list of container types that should not be started when the project is started. - `--performance-mode`: Performance optimization mode, possible values are `global`, `none`, `mutagen`. - `--performance-mode-reset`: Reset performance mode to global configuration. - `--php-version`: PHP version that will be enabled in the `web` container (see [default]()). - `--project-name`: Provide the project name of project to configure (normally the same as the last part of directory name). - `--project-tld`: Set the top-level domain to be used for projects (see [default]()). - `--project-type`: Provide [the project type]() of project to configure. This is autodetected and this flag is necessary only to override the detection. - `--router-http-port`: The router HTTP port for this project (see [default]()). - `--router-https-port`: The router HTTPS port for this project (see [default]()). - `--share-default-provider`: Set the default share provider for the project (`ngrok`, `cloudflared`, or custom). Can be overridden globally with `ddev config global --share-default-provider=`. - `--share-provider-args`: Provide extra args to the share provider in `ddev share`. - `--show-config-location`: Output the location of the `.ddev/config.yaml` file if it exists, or error that it doesn’t exist. - `--timezone`: Specify timezone for containers and PHP, like `Europe/London` or `America/Denver` or `GMT` or `UTC`. If unset, DDEV will attempt to derive it from the host system timezone. - `--update`: Update project settings based on detection and `project-type` overrides (except for `generic` type). - `--upload-dirs`: Set the project’s upload directories, the destination directories of the `ddev import-files` command, or `--upload-dirs=""` to remove previously configured values. - `--use-dns-when-possible`: Use DNS for hostname resolution instead of `/etc/hosts` when possible (default `true`). - `--web-environment`: Set the environment variables in the `web` container: `--web-environment="TYPO3_CONTEXT=Development,SOMEENV=someval"` or `--web-environment=""` to remove previously configured values. - `--web-environment-add`: Append environment variables to the `web` container: `--web-environment-add="TYPO3_CONTEXT=Development,SOMEENV=someval"` - `--web-image`: Set the `web` container image ([for advanced use only]()). - `--web-image-default`: Set the default `web` container image, the same as `--web-image=""`. - `--web-working-dir`: Override the default working directory for the `web` service. - `--web-working-dir-default`: Unset a `web` service working directory override, the same as `--web-working-dir=""`. - `--webimage-extra-packages`: Comma-delimited list of Debian packages that should be added to `web` container when the project is started or `--webimage-extra-packages=""` to remove any previously configured packages. - `--webserver-type`: Set the project’s desired web server type: `nginx-fpm`, `apache-fpm`, `generic` (see [default]()). - `--working-dir-defaults`: Unset all service working directory overrides. - `--xdebug-enabled`: Whether Xdebug is enabled in the `web` container. - `--xhprof-mode`: XHProf mode, possible values are `global`, `prepend`, `xhgui` (see [default]()). - `--xhprof-mode-reset`: Reset XHProf mode to global configuration. ### `config global` Change global configuration. ``` # Opt out of sharing anonymized usage information ddev config global --instrumentation-opt-in=false # Skip the SSH agent for all projects ddev config global --omit-containers=ddev-ssh-agent ``` - `--fail-on-hook-fail`: If true, `ddev start` will fail when a hook fails. - `--instrumentation-opt-in`: Whether to allow [instrumentation reporting]() with `--instrumentation-opt-in=true` (see [default]()). - `--internet-detection-timeout-ms`: Increase timeout when checking internet timeout, in milliseconds (see [default]()). - `--letsencrypt-email`: Email associated with Let’s Encrypt, `ddev global --letsencrypt-email=me@example.com`. - `--mailpit-http-port`: The default Mailpit HTTP port for all projects, can be overridden by project configuration (see [default]()). - `--mailpit-https-port`: The default Mailpit HTTPS port for all projects, can be overridden by project configuration (see [default]()). - `--no-bind-mounts`: If `true`, don’t use bind-mounts. Useful for environments like remote Docker where bind-mounts are impossible. - `--no-tui`: If `true`, disable the interactive dashboard and show the classic help text instead (see [default]()). - `--omit-containers`: For example, `--omit-containers=ddev-ssh-agent` or `--omit-containers=""`. - `--omit-project-name-by-default`: If `true`, `ddev config` will not write the `name` field to `.ddev/config.yaml` unless explicitly set with `--project-name` (see [default]()). - `--omit-snapshot-on-delete`: If `true`, `ddev delete` will not take a snapshot before deleting a project (see [default]()). - `--performance-mode`: Performance optimization mode, possible values are `none`, `mutagen`. - `--performance-mode-reset`: Reset performance optimization mode to operating system default (`none` for Linux and WSL2, `mutagen` for macOS and traditional Windows). - `--project-tld`: Set the default top-level domain to be used for all projects, can be overridden by project configuration (see [default]()). - `--router-bind-all-interfaces`: Bind host router ports on all interfaces, not only on the localhost network interface. - `--router-http-port`: The default router HTTP port for all projects, can be overridden by project configuration (see [default]()). - `--router-https-port`: The default router HTTPS port for all projects, can be overridden by project configuration (see [default]()). - `--simple-formatting`: If `true`, use simple formatting for tables and implicitly set `NO_COLOR=1`. - `--ssh-agent-upstream`: Make `ddev-ssh-agent` relay to an existing SSH agent: `host` for the host’s agent, or the path to an agent socket; empty uses DDEV’s own agent (see [default]()). - `--table-style`: Table style for `ddev list` and `ddev describe`, possible values are `default`, `bold`, `bright` (see [default]()). - `--traefik-monitor-port`: Can be used to change the Traefik monitor port in case of port conflicts, for example `ddev config global --traefik-monitor-port=11999` (see [default]()). - `--use-hardened-images`: If `true`, use more secure ‘hardened’ images for an actual internet deployment. - `--use-letsencrypt`: Enables experimental Let’s Encrypt integration, `ddev config global --use-letsencrypt` or `ddev config global --use-letsencrypt=false`. - `--web-environment`: Set the environment variables in the `web` container: `--web-environment="TYPO3_CONTEXT=Development,SOMEENV=someval"` - `--web-environment-add`: Append environment variables to the `web` container: `--web-environment-add="TYPO3_CONTEXT=Development,SOMEENV=someval"` - `--wsl2-no-windows-hosts-mgt`: WSL2 only; make DDEV ignore Windows-side hosts file ([for advanced use only]()). - `--xdebug-ide-location`: For less usual IDE locations specify where the IDE is running for Xdebug to reach it ([for advanced use only]()). - `--xhprof-mode`: XHProf mode, possible values are `prepend`, `xhgui` (see [default]()). - `--xhprof-mode-reset`: Reset XHProf mode to default. ## `console` Run the `console` command; available only in projects of type `symfony` and `shopware6`, and only available if `bin/console` exists. ``` # Show all Symfony console subcommands ddev console list ``` ## `craft` Run a [Craft CMS command]() inside the web container (global shell web container command). Example: ``` # Run pending Craft migrations and apply pending project config changes ddev craft up ``` ## `dbeaver` Open [DBeaver]() with the current project’s database (global shell host container command). This command is only available if `DBeaver.app` is installed as `/Applications/DBeaver.app` for macOS, if `dbeaver.exe` is installed to all users as `C:/Program Files/dbeaver/dbeaver.exe` for WSL2 and Windows, and if `dbeaver` (or another binary like `dbeaver-ce`) available inside `/usr/bin` for Linux (Flatpak and snap support included). Example: ``` # Open the current project’s database in DBeaver ddev dbeaver ``` ## `delete` Remove all information, including the database, for an existing project. Flags: - `--all`, `-a`: Delete all projects. - `--clean-containers`: Clean up all DDEV Docker containers not required by this version of DDEV. (default `true`) - `--omit-snapshot`, `-O`: Omit/skip database snapshot. - `--yes`, `-y`: Skip confirmation prompt. Example: ``` # Delete my-project and my-other-project ddev delete my-project my-other-project # Delete the current project without taking a snapshot or confirming ddev delete --omit-snapshot --yes ``` ### `delete images` With `--all`, it deletes all `ddev/ddev-*` Docker images. Flags: - `--all`, `-a`: If set, deletes all Docker images created by DDEV. - `--yes`, `-y`: Skip confirmation prompt. Example: ``` # Delete images ddev delete images # Delete images and skip confirmation ddev delete images -y # Delete all DDEV-created images ddev delete images --all ``` ## `describe` *Aliases: `status`, `st`, `desc`.* Get a detailed description of a running DDEV project. Example: ``` # Display details for the current project ddev describe # Display details for my-project ddev describe my-project ``` ## `dotenv` Commands for managing the contents of `.env` files. See [Environment Variables]() for which filename reaches which service, the order the files override each other in, and where to keep a secret. ### `dotenv get` Get the value of an environment variable from a `.env` file. Provide the path relative to the project root when specifying the file. Example: ``` # Get APP_KEY from $DDEV_APPROOT/.env ddev dotenv get .env --app-key # Get ENV_KEY from $DDEV_APPROOT/.ddev/.env.web ddev dotenv get .ddev/.env.web --env-key # Get REDIS_TAG from $DDEV_APPROOT/.ddev/.env.redis ddev dotenv get .ddev/.env.redis --redis-tag # Get API_KEY from the gitignored $DDEV_APPROOT/.ddev/.env.web.local ddev dotenv get .ddev/.env.web.local --api-key ``` ### `dotenv global get` Get the value of an environment variable from a global `.env` file. Name the file as `.ddev/`, the same way as in a project; it is read from the [global DDEV directory](). Example: ``` # Get API_URL from the global .env.web ddev dotenv global get .ddev/.env.web --api-url # Get API_KEY from the gitignored global .env.web.local ddev dotenv global get .ddev/.env.web.local --api-key ``` ### `dotenv global set` *Alias: `dotenv global add`.* Create or update a global `.env` file, which applies to every project, with values specified via long flags from the command line. Flags in the format `--env-key=value` will be converted to environment variable names like `ENV_KEY="value"`. Name the file as `.ddev/`, the same way as in a project; it is written to the [global DDEV directory](). Example: ``` # Set API_URL for the web service of every project ddev dotenv global set .ddev/.env.web --api-url=https://example.com # Set two variables for the db service of every project ddev dotenv global set .ddev/.env.db --extra value --another-key "extra value" # Set API_KEY in a gitignored file, for the web service of every project ddev dotenv global set .ddev/.env.web.local --api-key=secret ``` ### `dotenv set` *Alias: `dotenv add`.* Create or update a `.env` file with values specified via long flags from the command line. Flags in the format `--env-key=value` will be converted to environment variable names like `ENV_KEY="value"`. Provide the path relative to the project root when specifying the file. Example: ``` # Set APP_KEY in the application's own $DDEV_APPROOT/.env ddev dotenv set .env --app-key=value # Set two variables for the web service ddev dotenv set .ddev/.env.web --extra value --another-key "extra value" # Set REDIS_TAG for the redis service ddev dotenv set .ddev/.env.redis --redis-tag 7-bookworm # Set API_URL for the web service, in a file labeled for one add-on ddev dotenv set .ddev/.env.web.myaddon --api-url=https://example.com # Set API_KEY in a gitignored file, for the web service ddev dotenv set .ddev/.env.web.local --api-key=secret ``` ## `dr` Run the Drupal `dr` CLI; available only in projects of type `drupal11` and `drupal12`, and only if `dr` is available in the project (Drupal 11.4+). ``` # Show dr status ddev dr system:status ``` ## `drush` Run the `drush` command; available only in projects of type `drupal*` and `backdrop`, and only available if `drush` is in the project. On projects of type `drupal`, `drush` should be installed in the project itself, (`ddev composer require drush/drush`). On projects of type `drupal7` `drush` 8 is provided by DDEV. ``` # Show drush status/configuration ddev drush st ``` ## `exec` *Alias: `.`.* [Execute a shell command in the container]() for a service. Uses the web service by default. To run your command in a different project, run `ddev exec --project `. To run your command in a different service container, run `ddev exec --service `. Use the `--raw` flag if you’d like to run a raw, uninterpreted command in a container. Flags: - `--dir`, `-d`: Define the execution directory within the container. - `--raw`: Use raw exec (do not interpret with Bash inside container). (default `true`) - `--project`, `-p`: Specify a project where to run the command. Defaults to the project in the current directory. - `--service`, `-s`: Define the service to connect to. (e.g. `web`, `db`) (default `"web"`) - `--quiet`, `-q`: Suppress detailed error message. - `--user`, `-u`: Defines the user to run shell as. Example: ``` # List the web container's docroot contents ddev exec ls /var/www/html # List the web container's docroot contents for a specific project ddev exec --project my-other-project ls /var/www/html # List the web container's vendor directory contents ddev exec --dir /var/www/html/vendor ls # Output a long, recursive list of the files in the web container ddev exec --raw -- ls -lR # Suppress detailed error message: # "Failed to execute command `exit 1`: exit status 1" ddev exec -q "exit 1" # List the db container's /root directory contents as root user ddev exec -s db -u root ls -la /root ``` ## `export-db` Dump a database to a file or to stdout. With `--file` and no `--compression`, the compression type comes from the file extension: `.gz`, `.zst`, `.xz`, `.bz2`, or `.sql`/`.mysql` for none. Any other extension, and output to stdout, uses gzip. Flags: - `--compression`: Compression type: `gzip`, `zstd`, `xz`, `bzip2`, or `none`. `zstd` is not available with MariaDB 5.5. - `--database`, `-d`: Target database to export from (default `"db"`) - `--file`, `-f`: Path to a SQL dump file to export to The older `--gzip`, `--gzip=false`, `--xz`, and `--bzip2` flags still work. Example: ``` # Dump and compress the current project’s database to `/tmp/db.sql.gz` ddev export-db --file=/tmp/db.sql.gz # Dump the current project’s database with zstd compression ddev export-db --file=/tmp/db.sql.zst # Dump the current project’s database, without compressing it, to `/tmp/db.sql` ddev export-db --file /tmp/db.sql # Dump and compress the current project’s `foo` database instead of `db` ddev export-db --database=foo --file=/tmp/db.sql.gz # Output the current project’s database and use `>` to write to `/tmp/db.sql.gz` ddev export-db > /tmp/db.sql.gz # Output the current project’s database with zstd compression ddev export-db --compression=zstd > /tmp/db.sql.zst # Dump my-project’s database, without compressing it, to `/tmp/my-project.sql` ddev export-db my-project --file=/tmp/my-project.sql ``` ## `heidisql` Open [HeidiSQL]() with the current project’s database (global shell host container command). This command is available on Windows, WSL2, and Linux. Requirements by platform: - **Windows**: HeidiSQL installed at `C:\Program Files\HeidiSQL\Heidisql.exe` - **WSL2**: HeidiSQL accessible via `/mnt/c/Program Files/HeidiSQL/heidisql.exe` - **Linux**: HeidiSQL available at `/usr/bin/heidisql` or at `/usr/local/bin/heidisql` Example: ``` # Open the current project's database in HeidiSQL ddev heidisql ``` ## `help` Help about any command. Example: ``` # Illuminate the virtues of the `describe` command ddev help describe ``` ## `hostname` Manage your hostfile entries. Flags: - `--remove`, `-r`: Remove the provided hostname - ip correlation. - `--remove-inactive`, `-R`: Remove hostnames of inactive projects. Example: ``` ddev hostname somesite.example.com 127.0.0.1 ``` `ddev hostname` runs a special `ddev-hostname` or `ddev-hostname.exe` executable to elevate privileges. The extra executable is installed/updated by DDEV’s normal installation process or by the Windows installation process. On WSL2, `ddev-hostname.exe` is provided by the `ddev-wsl2` package and by the Windows installer. Install the package with `sudo apt-get update && sudo apt-get install -y ddev-wsl2` or the equivalent for your Linux system. ## `import-db` [Import a SQL file]() into the project. Flags: - `--database`, `-d`: Target database to import into (default `"db"`) - `--extract-path`: Path to extract within the archive - `--file`, `-f`: Path to a SQL dump in `.sql`, `.tar`, `.tar.gz`, `.tar.bz2`, `.tar.xz`, `.tar.zst`, `.tgz`, or `.zip` format - `--no-drop`: Do not drop the database before importing - `--no-progress`: Do not output progress Example: ``` # Start the interactive import utility ddev import-db # Import the `.tarballs/db.sql` dump to the project database ddev import-db --file=.tarballs/db.sql # Import the compressed `.tarballs/db.sql.gz` dump to the project database ddev import-db --file=.tarballs/db.sql.gz # Import the compressed `.tarballs/db.sql.gz` dump to a `other_db` database ddev import-db --database=additional_db --file=.tarballs/db.sql.gz # Import the `db.sql` dump to the project database ddev import-db < db.sql # Import the `db.sql` dump to the `my-project` default database ddev import-db my-project < db.sql # Uncompress `db.sql.gz` and pipe the result to the `import-db` command gzip -dc db.sql.gz | ddev import-db ``` ## `import-files` Pull the uploaded files directory of an existing project to the default public upload directory of your project. More usage information and a description of the Tar or ZIP archive is in the [usage section](). Flags: - `--extract-path`: Path to extract within the archive. - `--source`, `-s`: Path to the source directory or source archive in `.tar`, `.tar.gz`, `.tar.bz2`, `.tar.xz`, `.tar.zst`, `.tgz`, or `.zip` format. - `--target`, `-t`: Target upload dir, defaults to the first upload dir. Example: ``` # Extract+import `/path/to/files.tar.gz` to the project’s first upload directory ddev import-files --source=/path/to/files.tar.gz # Import `/path/to/dir` contents to the project’s first upload directory ddev import-files --source=/path/to/dir # Import `.tarballs/files.tar.xz` contents to the project’s `../private` upload directory ddev import-files --source=.tarballs/files.tar.xz --target=../private # Import `/path/to/dir` contents to the project’s `sites/default/files` upload directory ddev import-files -s=.tarballs/files.tar.gz -t=sites/default/files ``` ## `launch` Launch a browser with the current site (global shell host container command). If the current project is not running, it will be started first. Flags: - `--mailpit`, `-m`: Open Mailpit. - `--print-url`: Print the URL instead of opening a browser. The output is the resolved URL itself, with no prefix, so it can be used directly in scripts. Useful where no browser is available (SSH sessions, containers, CI). - `--qr`: Print the URL and its QR code instead of opening a browser. Only useful for a URL a phone can reach, which `*.ddev.site` is not. The environment variable `DDEV_LAUNCH_PRINT_URL=true` behaves like the `--print-url` flag. Unlike the flag it is inherited by nested `ddev launch` invocations (for example from custom commands or add-ons that call `ddev launch` internally), so wrappers and external tools can capture the URL. Unlike `DDEV_DEBUG=true` or `DDEV_VERBOSE=true`, which print the URL with a `FULLURL` prefix and enable global debug logging, it prints only the URL. > [!TIP] > > **How to disable HTTP redirect to HTTPS?** > > Recommendations for: > > - [Google Chrome]() > - [Mozilla Firefox]() > - [Safari]() Example: ``` # Open your project’s base URL in the default browser ddev launch # Open Mailpit in the default browser ddev launch --mailpit # Print your project’s base URL instead of opening a browser ddev launch --print-url # Print a QR code for a URL, to scan with a phone ddev launch --qr https://your.example.com # Open your project’s base URL appended with `temp/phpinfo.php` ddev launch temp/phpinfo.php # Open the full URL (any website) in the default browser ddev launch https://your.ddev.site # Open your project’s base URL using a specific port ddev launch :3000 ``` ## `list` *Aliases: `l`, `ls`.* List projects. Flags: - `--active-only`, `-A`: If set, only currently active projects will be displayed. - `--continuous`: If set, project information will be emitted until the command is stopped. - `--continuous-sleep-interval`, `-I`: Time in seconds between `ddev list --continuous` output lists. (default `1`) - `--type`, `-t`: Show only projects of this type (e.g. `drupal`, `wordpress`, `php`). - `--wrap-table`, `-W`: Display table with wrapped text if required. Example: ``` # List all projects ddev list # List all running projects ddev list --active-only # List all WordPress projects ddev list --type wordpress ``` ## `logs` Get the logs from your running services. Flags: - `--follow`, `-f`: Follow the logs in real time. - `--service`, `-s`: Defines the service to retrieve logs from (e.g. `web`, `db`). (default `"web"`) - `--tail`: How many lines to show. - `--time`, `-t`: Add timestamps to logs. Example: ``` # Display recent logs from the current project’s web server ddev logs # Stream logs from the current project’s web server in real time ddev logs -f # Display recent logs from the current project’s database server ddev logs -s db # Display recent logs from my-project’s database server ddev logs -s db my-project ``` ## `magento` Run the `magento` command; available only in projects of type `magento2`, and only works if `bin/magento` is in the project. ``` # Show all magento subcommands ddev magento list ``` ## `maho` Run the `maho` command; available only in projects of type `maho`, and only works if the `maho` script is in the project root (it is copied there by Composer on install). ``` # Show all maho subcommands ddev maho list # Flush the cache ddev maho cache:flush ``` ## `mailpit` Launch a browser with mailpit for the current project (global shell host container command). Example: ``` # Open Mailpit in the default browser ddev mailpit ``` ## `mariadb` Run MariaDB client in the database container (global shell db container command). This is only available on projects that use the `mariadb` database type. (This is the same as the `mysql` command, but MariaDB is changing the name of their CLI client.) Example: ``` # Run the database container’s MariaDB client as root user ddev mariadb # Run the database container’s MariaDB client as db user ddev mariadb -udb -pdb # Pipe the `SHOW TABLES;` command to the MariaDB client to see a list of tables echo 'SHOW TABLES;' | ddev mariadb ``` ## `mutagen` Commands for [Mutagen]() status and sync, etc. ### `mutagen logs` Show Mutagen logs for debugging. Flags: - `--verbose`: Show full Mutagen logs. Example: ``` # Stream Mutagen’s logs in real time ddev mutagen logs # Stream Mutagen’s more detailed logs in real time ddev mutagen logs --verbose ``` ### `mutagen monitor` Monitor Mutagen status. Example: ``` # Start Mutagen’s sync process and monitor its status in real time ddev mutagen sync && ddev mutagen monitor ``` ### `mutagen reset` Stops a project and removes the Mutagen Docker volume. ``` # Reset Mutagen data for the current project ddev mutagen reset # Reset Mutagen data for my-project ddev mutagen reset my-project ``` ### `mutagen status` *Alias: `mutagen st`.* Shows Mutagen sync status. Flags: - `--verbose`, `-l`: Extended/verbose output for Mutagen status. Example: ``` # Display Mutagen sync status for the current project ddev mutagen status # Display Mutagen sync status for my-project ddev mutagen status my-project ``` ### `mutagen sync` Explicit sync for Mutagen. Flags: - `--verbose`: Extended/verbose output for Mutagen status. Example: ``` # Initiate Mutagen sync for the current project ddev mutagen sync # Initiate Mutagen sync for my-project ddev mutagen sync my-project ``` ### `mutagen version` Display the version of the Mutagen binary and the location of its components. Example: ``` # Print Mutagen details ddev mutagen version ``` ## `mysql` Run MySQL client in the database container (global shell db container command). This is only available on projects that use the `mysql` or `mariadb` database types. Example: ``` # Run the database container’s MySQL client as root user ddev mysql # Run the database container’s MySQL client as db user ddev mysql -udb -pdb # Pipe the `SHOW TABLES;` command to the MySQL client to see a list of tables echo 'SHOW TABLES;' | ddev mysql ``` ## `npm` Run [`npm`]() inside the web container (global shell web container command). Example: ``` # Install JavaScript packages using `npm` ddev npm install # Update JavaScript packages using `npm` ddev npm update ``` ## `npx` Run [`npx`]() inside the web container (global shell web container command). Example: ``` # Initialize Biome in the project ddev npx @biomejs/biome init # Create a new Next.js app ddev npx create-next-app@latest ``` ## `php` Run `php` inside the web container (global shell web container command). Example: ``` # Output the web container’s PHP version ddev php --version ``` ## `poweroff` *Alias: `powerdown`.* Completely stop all projects and containers. > [!TIP] > > This is the equivalent of running `ddev stop -a --stop-ssh-agent`. Example: ``` # Stop all projects and containers ddev poweroff ``` ## `psql` Run PostgreSQL client in the database container (global shell db container command). This is only available on projects that use the `postgres` database type. Example: ``` # List available databases ddev psql -l # List tables in the default 'db' database echo "\dt;" | ddev psql ``` ## `pull` Pull files and database using a configured [provider plugin](). Flags: - `--environment=ENV1=val1,ENV2=val2` - `--skip-confirmation`, `-y`: Skip confirmation step. - `--skip-db`: Skip pulling database archive. - `--skip-files`: Skip pulling file archive. - `--skip-import`: Download archive(s) without importing them. Example: ``` # Pull a backup from the configured Upsun project to use locally ddev pull upsun # Pull from Pantheon without confirming ddev pull pantheon -y # Pull the Upsun database only without confirming ddev pull upsun --skip-files -y # Pull the Acquia files only without confirming ddev pull acquia --skip-db -y # Download archives from Upsun without importing them ddev pull upsun --skip-import # Pull from Upsun overriding environment and token on the command line ddev pull upsun --environment="PLATFORM_ENVIRONMENT=main,UPSUN_CLI_TOKEN=abcdeyourtoken" # Pull from Pantheon overriding environment and token on the command line ddev pull pantheon --environment="DDEV_PANTHEON_ENVIRONMENT=dev,TERMINUS_MACHINE_TOKEN=abcdeyourtoken" # Pull from Lagoon overriding project and environment on the command line ddev pull lagoon --environment="LAGOON_PROJECT=your-project,LAGOON_ENVIRONMENT=main" ``` ## `push` Push files and database using a configured [provider plugin](). Flags: - `--environment=ENV1=val1,ENV2=val2` - `--skip-confirmation`, `-y`: Skip confirmation step. - `--skip-db`: Skip pushing database archive. - `--skip-files`: Skip pushing file archive. Example: ``` # Push local files and database to the configured Upsun project ddev push upsun # Push to Pantheon without confirming ddev push pantheon -y # Push database only to Upsun without confirming ddev push upsun --skip-files -y # Push files only to Acquia without confirming ddev push acquia --skip-db -y # Push to Upsun overriding environment and token on the command line ddev push upsun --environment="PLATFORM_ENVIRONMENT=main,UPSUN_CLI_TOKEN=abcdeyourtoken" # Push to Pantheon overriding environment and token on the command line ddev push pantheon --environment="DDEV_PANTHEON_ENVIRONMENT=dev,TERMINUS_MACHINE_TOKEN=abcdeyourtoken" # Push to Lagoon overriding project and environment on the command line ddev push lagoon --environment="LAGOON_PROJECT=your-project,LAGOON_ENVIRONMENT=main" ``` ## `querious` Open [Querious]() with the current project’s MariaDB or MySQL database (global shell host container command). This is only available if `Querious.app` is installed as `/Applications/Querious.app`, and only for projects with `mysql` or `mariadb` databases. Example: ``` # Open the current project’s database in Querious ddev querious ``` ## `restart` Restart one or several projects. With `--service`, only the named services get new containers, with their images built as `ddev start` builds them, and the rest of the project keeps running. This also brings back a service that is unhealthy or has crashed. A service in an [optional profile]() has to be started first, with `ddev start --profiles` or `ddev restart --profiles`. Restarting `web` restarts the whole project, with the optional services that were started. Flags: - `--all`, `-a`: Restart all projects. - `--no-cache`: Rebuild custom Docker image layers without cache. - `--omit-snapshot`, `-O`: With `--reset-database`, skip the snapshot of the database being removed. - `--profiles`: Start optional comma-separated Docker Compose profiles. - `--reset-database`: Remove the project’s existing database and start over with a new one. - `--seed-snapshot`: Seed a brand-new database volume from this snapshot name or file, instead of the stock starter database. - `--service`, `-s`: Restart only these comma-separated services. - `--skip-confirmation`, `-y`: Skip any confirmation steps. Example: ``` # Restart the current project ddev restart # Restart the current project without using Docker cache ddev restart --no-cache # Restart my-project and my-other-project ddev restart my-project my-other-project # Restart all projects ddev restart --all # Restart the current project with the busybox profile, without Docker cache ddev restart --profiles=busybox --no-cache # Restart only the db service ddev restart --service=db # Restart only the solr and redis services, without Docker cache ddev restart -s solr,redis --no-cache # Throw away the current database and restart with a new, empty one ddev restart --reset-database # Reset and initialize with external snapshot without confirmation ddev restart --reset-database --seed-snapshot=/tmp/newsnapshot-mariadb_11.8.zst -y ``` ## `sake` Run the `sake` command, only available for Silverstripe CMS projects and if the `sake` command is available in the `vendor/bin` folder. Common commands: - Build database: `ddev sake dev/build` (or `ddev sake db:build` from Silverstripe CMS 6 onwards) - List of available tasks: `ddev sake dev/tasks` (or `ddev sake tasks` from Silverstripe CMS 6 onwards) ## `self-upgrade` Output instructions for updating or upgrading DDEV. The command doesn’t perform the upgrade, but tries to provide instructions relevant to your installation. Example: ``` → ddev self-upgrade DDEV appears to have been installed with install_ddev.sh, you can run that script again to update. curl -fsSL https://ddev.com/install.sh | bash ``` ## `sequelace` Open [SequelAce]() with the current project’s database (global shell host container command). This command is only available if `Sequel Ace.app` is installed as `/Applications/Sequel Ace.app`, and only for projects with `mysql` or `mariadb` databases. Example: ``` # Open the current project’s database in SequelAce ddev sequelace ``` ## `share` [Share the current project]() on the internet via a tunnel provider like ngrok or cloudflared. The tunnel URL is also printed as a QR code, which you can scan to open it on a phone or tablet. DDEV supports multiple share providers: - **ngrok** (default) - Requires an [ngrok.com]() account - **cloudflared** - Free, no account required. Requires [cloudflared]() to be installed - **Custom providers** - Add your own providers in `.ddev/share-providers/` Flags: - `--provider`: Share provider to use (ngrok, cloudflared, or custom). - `--provider-args`: Arguments to pass to the share provider (overrides config file settings). - `--ngrok-args`: (Deprecated) Use `--provider-args` instead. The default provider can be configured globally with `ddev config global --share-default-provider=` or per-project with `ddev config --share-default-provider=`. See [`share_default_provider`]() for more details. Provider-specific arguments can be configured in `.ddev/config.yaml` with [`share_provider_args`](). Examples: ``` # Share the current project with the default provider (ngrok) ddev share # Share with cloudflared ddev share --provider=cloudflared # Share with cloudflared using a custom domain (named tunnel) ddev share --provider=cloudflared --provider-args="--tunnel my-tunnel --hostname mysite.example.com" # Share with ngrok, using domain `foo.ngrok-free.app` ddev share --provider-args="--domain foo.ngrok-free.app" # Share the current project using ngrok's basic-auth argument ddev share --provider-args="--basic-auth username:pass1234" # Share my-project with ngrok ddev share my-project ``` ## `snapshot` Create a database snapshot for one or more projects. This uses `xtrabackup` or `mariadb-backup` to create a database snapshot in the `.ddev/db_snapshots` directory. These are compatible with server backups using the same tools and can be restored with the [`snapshot restore`](<#snapshot-restore>) command. See [Snapshotting and Restoring a Database]() for more detail, or [Database Management]() for more on working with databases in general. Flags: - `--all`, `-a`: Snapshot all projects. (Will start stopped or paused projects.) - `--cleanup`, `-C`: Cleanup snapshots. - `--list`, `-l`: List snapshots. - `--name`, `-n`: Provide a name for the snapshot. - `--uncompressed`: Write the snapshot as an uncompressed mariadb-backup/xtrabackup stream instead of compressing it. This skips decompression on restore, but the file can be roughly as large as the database’s datadir, many times bigger than a compressed snapshot. Not available for PostgreSQL projects. See [Snapshots]() for when this unusual trade-off makes sense. - `--yes`, `-y`: Skip confirmation prompt. `ddev snapshot --list` also shows each snapshot’s compression: `zstd`, `gzip`, or `none` for an uncompressed snapshot. If the project is a Git worktree and a sibling worktree of the same project has snapshots of its own, those are listed too, with a `Worktree` column naming which one each came from. Example: ``` # Take a database snapshot for the current project ddev snapshot # Take a database snapshot for the current project, named `my_snapshot_name` ddev snapshot --name my_snapshot_name # Take a snapshot for the current project, cleaning up existing snapshots ddev snapshot --cleanup # Delete a single snapshot by name without deleting all snapshots ddev snapshot --cleanup --name my_snapshot_name # Take a snapshot for the current project, cleaning existing snapshots and skipping prompt ddev snapshot --cleanup -y # List the current project’s snapshots ddev snapshot --list # Take a snapshot for each project ddev snapshot --all # Take an uncompressed snapshot, trading disk space for a faster restore ddev snapshot --uncompressed ``` ### `snapshot restore` Restores a database snapshot, either by name from the `.ddev/db_snapshots` directory or by the path to a snapshot file elsewhere on disk. If the project is a Git worktree, snapshots from the same project checked out in other worktrees of the same repository are available too, whether picked interactively, restored by name, or via `--latest`. Flags: - `--force`, `-f`: Restore a snapshot made by a different version of the same database server, which may not succeed. - `--latest`: Use the latest snapshot. Example: ``` # Restore the most recent snapshot ddev snapshot restore --latest # Restore the previously-taken `my_snapshot_name` snapshot ddev snapshot restore my_snapshot_name # Restore a snapshot taken on an older version of the same database server ddev snapshot restore --force my_snapshot_name # Restore a snapshot file from outside the project ddev snapshot restore ~/tmp/mysnapshot-mariadb_11.8.zst ``` A snapshot normally has to match the project’s configured database type and version exactly. `--force` lifts that for a different version of the same server, which the underlying restore doesn’t itself check for; the database may fail to come up, in which case [`ddev start --reset-database`]() starts over. Restoring across database types (a MySQL snapshot into MariaDB, say) is still refused. ## `spark` Run the `spark` command; available only in projects of type `codeigniter`, and only available if `spark` is in the project root. ``` # Show all spark subcommands ddev spark list ``` ## `ssh` Starts a shell session in a service container. Uses the web service by default. Flags: - `--dir`, `-d`: Defines the destination directory within the container. - `--service`, `-s`: Defines the service to connect to. (default `"web"`) - `--user`, `-u`: Defines the user to run shell as. Example: ``` # SSH into the current project’s web container ddev ssh # SSH into the current project’s database container ddev ssh -s db # SSH into the current project’s database container as root user ddev ssh -s db -u root # SSH into the web container for my-project ddev ssh my-project # SSH into the docroot of the current project’s web container ddev ssh -d /var/www/html ``` ## `start` *Alias: `add`*. Start a DDEV project. If you run `ddev start` in a directory that has no DDEV project but looks like a web project, DDEV shows the configuration `ddev config --auto` would create (name, URL, project type, docroot, PHP version, database, and web server) and asks whether to use it, customize it with the `ddev config` questions, or cancel. DDEV recognizes a directory as a web project when it detects a supported project type, or finds a `composer.json`, a `package.json`, or an `index.php` or `index.html` at the root or in a common docroot such as `web` or `public`. Without a terminal to ask in, as in CI or a script, or with `--skip-confirmation` (`-y`), `ddev start` doesn’t configure anything; use `ddev config --auto && ddev start` there instead. Flags: - `--all`, `-a`: Start all projects. - `--no-cache`: Rebuild custom Docker image layers without cache. - `--omit-snapshot`, `-O`: With `--reset-database`, skip the snapshot of the database being removed. - `--profiles`: Start optional comma-separated Docker Compose profiles. - `--reset-database`: Remove the project’s existing database and start over with a new one. - `--seed-snapshot`: Seed a brand-new database volume from this snapshot name or file, instead of the stock starter database. - `--skip-confirmation`, `-y`: Skip any confirmation steps. Example: ``` # Start the current project ddev start # Start the current project without using Docker cache ddev start --no-cache # Start my-project and my-other-project ddev start my-project my-other-project # Start all projects ddev start --all # Throw away the current database and start over with a new, empty one ddev start --reset-database # Start over with a database seeded from a snapshot instead ddev start --reset-database --seed-snapshot=my_snapshot_name ``` See [Seeding a Fresh Database from a Snapshot]() and [Starting Over with a New Database](). ## `stop` *Aliases: `rm`, `remove`.* Stop and remove the containers of a project. Does not lose or harm anything unless you add `--remove-data`. Flags: - `--all`, `-a`: Stop and remove all running or container-stopped projects and remove from global projects list. - `--omit-snapshot`, `-O`: Omit/skip database snapshot. - `--remove-data`, `-R`: Remove stored project data (MySQL, logs, etc.). - `--snapshot`, `-S`: Create database snapshot. - `--stop-ssh-agent`: Stop the `ddev-ssh-agent` container. - `--unlist`, `-U`: Remove the project from global project list, so it won’t appear in [`ddev list`](<#list>) until started again. Example: ``` # Stop the current project ddev stop # Stop my-project, my-other-project, and my-third-project ddev stop my-project my-other-project my-third-project # Stop all projects ddev stop --all # Stop all projects and the `ddev-ssh-agent` container ddev stop --all --stop-ssh-agent # Stop all projects and remove their data ddev stop --remove-data ``` ## `tableplus` Open [TablePlus]() with the current project’s database (global shell host container command). This command is only available if `TablePlus.app` is installed as `/Applications/TablePlus.app`. Example: ``` # Open the current project’s database in TablePlus ddev tableplus ``` ## `tablepro` Open [TablePro]() with the current project’s database (global shell host container command). This command is only available if `TablePro.app` is installed as `/Applications/TablePro.app`. Example: ``` # Open the current project’s database in TablePro ddev tablepro ``` ## `tui` Launch the [interactive TUI dashboard](). Example: ``` # Launch DDEV TUI ddev tui ``` ## `typo3` Run the `typo3` command; available only in projects of type `typo3`, and only works if `typo3` is in the `$PATH` inside the container; normally it’s in `vendor/bin/typo3` so will be found. ``` # Show typo3 site configuration ddev typo3 site:show ``` ## `utility` *Aliases: `ut`, `debug`, `d`,`dbg`.* A collection of utility and debugging commands, often useful for [troubleshooting](). ### `utility addon-update-checker` Fetch and run the DDEV add-on update checker script from [https://ddev.com/s/addon-update-checker.sh](). This is a tool for add-on developers to verify their add-on’s scripts and tooling are up to date with the [`ddev-addon-template`](). If the target directory contains `install.yaml`, the checker runs there. Otherwise, it scans immediate subdirectories for `install.yaml` and runs the checker in each one, which is useful when working in a workspace with multiple add-ons checked out alongside each other. Flags: - `--dir`, `-d`: Directory of the add-on to check, or a workspace containing multiple add-on directories (defaults to current directory). Example: ``` # Run in the current add-on directory ddev utility addon-update-checker # Run in a specific add-on directory ddev utility addon-update-checker -d /path/to/my-addon # Run across all add-ons in a workspace ddev utility addon-update-checker -d /path/to/my-addons-workspace ``` ### `utility cd` Uses shell built-in `cd` to change to a project directory. For example, `ddevcd some-project` will change directories to the project root of the project named `some-project`. Note that this command can’t work until you make a small addition to your `.bashrc`, `.zshrc`, or `config.fish`. ``` # To see the explanation of what you need to do ddev utility cd # Where some-project is a project from the `ddev list` ddevcd some-project ``` ### `utility check-custom-config` Display custom configuration files in the current project’s `.ddev` directory. By default, shows only files that would warn on startup: user-created files and files with an unexpected `#ddev-generated` marker. Recognized add-on files and files with a `#ddev-silent-no-warn` marker are excluded. Flags: - `--all`: Show all custom configuration, annotating files with: - `(add-on )` for files installed by a named add-on - `(#ddev-generated)` for add-on files that also carry a `#ddev-generated` marker - `(unexpected #ddev-generated)` for files with a `#ddev-generated` marker not recognized as a DDEV or add-on file - `(#ddev-silent-no-warn)` for files silenced with a `#ddev-silent-no-warn` marker ``` # Show only files that would warn on startup ddev utility check-custom-config # Show all files including add-on, unexpected #ddev-generated, and silenced files ddev utility check-custom-config --all ``` ### `utility check-db-match` Verify that the database in the db server matches the configured [type and version](). Example: ``` # Check whether project’s running database matches configuration ddev utility check-db-match ``` ### `utility compose-config` Prints the current project’s docker-compose configuration. Example: ``` # Print docker-compose config for the current project ddev utility compose-config # Print docker-compose config for `my-project` ddev utility compose-config my-project ``` ### `utility configyaml` Prints the project [`config.*.yaml`]() usage. **Arguments:** `[project_name]` **Flags:** - `--full-yaml`: Output complete processed YAML configuration instead of individual fields - `--omit-keys=`: Comma-separated list of keys to omit from output (e.g., `web_environment`) **Examples:** ``` # Print config for the current project ddev utility configyaml # Print config specifically for `my-project` ddev utility configyaml my-project # Print complete YAML configuration ddev utility configyaml --full-yaml # Hide sensitive environment variables ddev utility configyaml --omit-keys=web_environment # Combine flags: full YAML output without sensitive keys ddev utility configyaml --full-yaml --omit-keys=web_environment ``` ### `utility delete-volume` Delete a Docker volume belonging to the current project, identified by either its short docker-compose name (e.g. `solr`) or its full Docker volume name (e.g. `ddev-myproject_solr`). If no name is given, pick one from an interactive list of the project’s volumes. If the project is running, you’ll be asked to stop it first, since a container-mounted volume can’t be removed. Shared volumes used by every project, like `ddev-global-cache`, can’t be deleted with this command. Flags: - `--yes`, `-y`: Skip confirmation prompts. Example: ``` # Choose a volume to delete from an interactive list ddev utility delete-volume # Delete the "solr" volume for the current project, prompting for confirmation ddev utility delete-volume solr # Delete by full Docker volume name, skipping confirmation ddev utility delete-volume ddev-myproject_solr --yes ``` ### `utility diagnose` Run quick diagnostics on your DDEV installation and current project. This command provides concise, actionable output for common troubleshooting scenarios. The command checks: - Docker environment and connectivity - Network configuration and DNS resolution - HTTPS/mkcert setup - Current project health (if in a project directory) For comprehensive output suitable for issue reports, use [`ddev utility test`](<#utility-test>) instead. **Environment variables:** - `DDEV_DIAGNOSE_FULL=true`: Include test project creation in diagnostics (slower but more thorough) **Examples:** ``` # Run diagnostics in a project directory ddev utility diagnose # Run with full diagnostics including test project creation DDEV_DIAGNOSE_FULL=true ddev utility diagnose # Run from anywhere (checks Docker and environment only) ddev utility diagnose ``` ### `utility dockercheck` Diagnose DDEV Docker provider setup. Example: ``` # Output contextual details for the Docker provider ddev utility dockercheck ``` ### `utility download-ddev` Download the `ddev` and `ddev-hostname` binaries built by CI or a release, for testing a specific build without replacing your installed DDEV. It only downloads the binaries into a directory (`~/tmp/ddev-download-ddev/` by default, so a download can’t accidentally end up on `$PATH` or as a stray file in a Git checkout, and each build stays separate from the ones downloaded before it) and prints how to use them in the current shell; it does not modify your installed DDEV. Flags: - `--pr `: Download the build from a pull request number - `--branch `: Download the build from a branch name - `--commit `: Download the build from a commit SHA, full or abbreviated - `--tag `: Download a release tag, for example `v1.25.3` - `--stable`: Download the latest stable release (default `false`) - `--head`: Download the latest main build (default `false`) - `--output `, `-o `: Output directory, used as given (default: `~/tmp/ddev-download-ddev/`, where `` is the build, for example `pr-8611`, `v1.25.3`, or `main`) - `--os `: OS override: `macos`, `linux`, or `windows` (default: current OS) - `--arch `: Architecture override: `amd64` or `arm64` (default: current architecture) - `--owner `: GitHub owner/org (default `ddev`; for PR builds this is the base repository, not a fork) - `--repo `: GitHub repository (default `ddev`) Note: Exactly one source flag (`--pr`, `--branch`, `--commit`, `--tag`, `--stable`, or `--head`) is required, and they are mutually exclusive. Releases and `main` builds are signed; PR and other branch builds are not. No source needs a GitHub token, but a [GitHub token](<#add-on>) avoids the low anonymous rate limit and downloads CI builds from GitHub directly rather than through nightly.link. If the GitHub API lookup fails, `--pr` falls back to the build links posted on the pull request, and `--head` to nightly.link’s latest `main` build, which is sometimes out of date. Examples: ``` # Download this PR's build for your current OS and architecture into ~/tmp/ddev-download-ddev/pr-8611 ddev utility download-ddev --pr 8611 # Download the latest main build into ~/tmp/head ddev utility download-ddev --head --output ~/tmp/head # Download a specific released version ddev utility download-ddev --tag v1.25.3 # Download the latest stable release ddev utility download-ddev --stable # Cross-download a macOS arm64 build of a branch ddev utility download-ddev --branch 20250101_feature --os macos --arch arm64 --output ~/tmp/ddev-macos-arm64 ``` ### `utility download-images` Download the basic Docker images required by DDEV. This can be useful on a new machine to prevent `ddev start` or other commands having to download the various images. Example: ``` # Download DDEV’s basic Docker images ddev utility download-images # Download DDEV’s Docker images for `my-project` ddev utility download-images my-project # Download DDEV’s Docker images for all projects ddev utility download-images --all ``` ### `utility fix-commands` Refreshes [custom command]() definitions without running [`ddev start`](<#start>). Example: ``` # Refresh the current project’s custom commands ddev utility fix-commands ``` ### `utility get-volume-db-version` Get the database type and version found in the `ddev-dbserver` database volume, which may not be the same as the configured database [type and version](). Example: ``` # Print the database volume's engine and version ddev utility get-volume-db-version ``` ### `utility gob-decode` Decode and display the contents of Go gob-encoded binary files used by DDEV, such as `.remote-config` files (remote configuration cache), `.amplitude.cache` files (analytics event cache), `.sponsorship-data` files (contributor sponsorship information), and `.addon-data` files (add-on registry cache). The decoder automatically detects the file type and uses the appropriate structure. The output is displayed as formatted JSON for readability. *(Hidden - show hidden utility commands with `ddev utility --show-hidden`)* Example: ``` # Decode a remote config file ddev utility gob-decode $HOME/.ddev/.remote-config # Decode a sponsorship data file ddev utility gob-decode $HOME/.ddev/.sponsorship-data # Decode an add-on registry cache file ddev utility gob-decode $HOME/.ddev/.addon-data # Decode an amplitude cache file ddev utility gob-decode $HOME/.ddev/.amplitude.cache # Decode any gob file with path expansion ddev utility gob-decode ~/path/to/file.gob ``` ### `utility hyperlink` Print a URL, or the URLs in piped text, as terminal hyperlinks when supported. Useful in [custom host commands](). *(Hidden - show hidden utility commands with `ddev utility --show-hidden`)* Example: ``` # Print a URL as a link ddev utility hyperlink https://docs.ddev.com # Link the URLs in piped text echo "See https://docs.ddev.com" | ddev utility hyperlink ``` ### `utility match-constraint` Check if the currently installed ddev matches the specified [version constraint](). Example: ``` if ddev utility match-constraint "< 1.25" >/dev/null 2>&1; then # do something for ddev versions below 1.25 ... else # do something for ddev versions 1.25+ ... fi ``` > [!TIP] You can also configure a [ddev version constraint per project](). ### `utility message-conditions` Show message conditions of this version of DDEV. *(Hidden - show hidden utility commands with `ddev utility --show-hidden`)* Example: ``` # Show message conditions for the current DDEV version ddev utility message-conditions ``` ### `utility migrate-database` Migrate a MySQL or MariaDB database to a different `dbtype:dbversion`. Works only with MySQL and MariaDB, not with PostgreSQL. It will export your database, create a snapshot, destroy your current database, and import into the new database type. It only migrates the ‘db’ database. It will update the database version in your project’s `config.yaml` file. Example: ``` # Migrate the current project's database to MariaDB 10.7 ddev utility migrate-database mariadb:10.7 ``` ### `utility mutagen` Allows access to any [Mutagen command](). Example: ``` # Run Mutagen's `sync list` command ddev utility mutagen sync list ``` ### `utility mutagen-diagnose` Diagnose Mutagen sync configuration and performance issues. This command analyzes your Mutagen setup and provides actionable recommendations for improving sync performance. The command checks: - Volume sizes and disk usage (warns if \>5GB, critical if \>10GB) - Upload directories configuration - Sync session status and problems - Large files being synced that could impact performance (\>50MB) - Ignore pattern configuration **Flags:** - `--all`, `-a`: Show all Mutagen volumes system-wide instead of just the current project **Examples:** ``` # Diagnose the current project's Mutagen configuration ddev utility mutagen-diagnose # Show all Mutagen volumes across all projects ddev utility mutagen-diagnose --all ``` See the [Mutagen troubleshooting documentation]() for more details. ### `utility port-diagnose` Identify processes occupying ports needed by DDEV. When run inside a project directory, checks that project’s configured ports (HTTP, HTTPS, Mailpit, XHGui). When run outside a project, checks the default ports 80 and 443. On WSL2, both the Linux and Windows sides are checked. Some port conflicts are only visible with elevated privileges (e.g. `docker-proxy` under rootful Docker CE). The tool will ask for permission before running any `sudo` command, showing the exact command it intends to run. Use `--allow-sudo` to pre-approve this without an interactive prompt. Flags: - `--allow-sudo`: Permit `sudo` use for elevated port detection without an interactive prompt. Example: ``` # Check ports for the current project (or defaults 80/443 outside a project) ddev utility port-diagnose # Allow sudo for elevated detection without interactive prompt ddev utility port-diagnose --allow-sudo ``` See also: [Troubleshooting port conflicts](). ### `utility rebuild` *Alias: `utility refresh`.* Rebuilds the project’s Docker cache with verbose output and restarts the project or the specified services. Services that have a container get a new one with the rebuilt image. Rebuilding `web` restarts the whole project, with the optional services that were started. Flags: - `--all`, `-a`: Rebuild all services and restart the project. - `--cache`: Keep Docker cache. - `--service`, `-s`: Rebuild these comma-separated services and restart them. (default `web`) Example: ``` # Rebuild the current project's web service without cache ddev utility rebuild # Rebuild the current project's web service with cache ddev utility rebuild --cache # Rebuild the current project's db service without cache ddev utility rebuild --service db # Rebuild the current project's solr and redis services without cache ddev utility rebuild -s solr,redis # Rebuild the current project's all services without cache ddev utility rebuild --all ``` ### `utility remote-data` Download and display remote configuration and sponsorship data used by DDEV from GitHub repositories. The downloaded content is displayed as formatted JSON to stdout. Optionally updates the local cached storage file (enabled by default). *(Hidden - show hidden utility commands with `ddev utility --show-hidden`)* Flags: - `--type`, `-t`: Type of data to download: `remote-config` (default), `sponsorship-data`, or `addon-data`. - `--update-storage`: Update local cached storage file (default `true`). Examples: ``` # Download remote config (default type) ddev utility remote-data # Download sponsorship data without updating local storage ddev utility remote-data --type=sponsorship-data --update-storage=false # Download add-on registry data ddev utility remote-data --type=addon-data ``` ### `utility test` Run diagnostics using the embedded [test script](). Example: ``` # Run DDEV's diagnostic suite ddev utility test ``` ### `utility testcleanup` Removes all diagnostic projects created with `ddev utility test`. Example: ``` # Remove all DDEV's diagnostic projects ddev utility testcleanup ``` ### `utility tls-diagnose` Diagnose TLS/HTTPS certificate trust issues. Checks mkcert installation and `CAROOT` configuration, OS trust store installation, certificate file validity, and live HTTPS connectivity when a project is running. On WSL2, also checks WSL2-specific CA sharing requirements: whether `$CAROOT` points to the Windows filesystem, whether `CAROOT` is in `$WSLENV`, whether the Windows certificate store contains the mkcert CA, and whether Windows-side and WSL2-side CA fingerprints match. Example: ``` # Run from any directory for mkcert, trust store, and cert file checks ddev utility tls-diagnose # Run from a running project directory to also check live HTTPS connectivity cd my-project && ddev utility tls-diagnose ``` See also: [Browser Shows Certificate Error for DDEV Sites](). ### `utility xdebug-diagnose` Diagnose Xdebug configuration (experimental) Example: ``` # Run quick check ddev utility xdebug-diagnose # Run more complete interactive check ddev utility xdebug-diagnose --interactive ``` ## `version` Print DDEV and component versions. Example: ``` # Print DDEV and platform version details ddev version ``` > [!TIP] > > `ddev --version` is a more concise command that only outputs the DDEV version without component versions. ## `wp` Run the [WP-CLI `wp` command](); available only in projects of type `wordpress` and `wp-bedrock`. ``` # Install WordPress site using `wp core install` ddev wp core install --url='$DDEV_PRIMARY_URL' --title='New-WordPress' --admin_user=admin --admin_email=admin@example.com --prompt=admin_password ``` > [!TIP] > > See [WordPress Specifics]() for more information. ## `xdebug` Enable or disable [Xdebug]() (global shell web container command). - The `on` argument is equivalent to `enable` and `true`. - The `off` argument is equivalent to `disable` and `false`. ``` # Display whether Xdebug is running ddev xdebug status # Display detailed Xdebug diagnostic information ddev xdebug info # Turn Xdebug on ddev xdebug # Turn Xdebug on ddev xdebug on # Turn Xdebug off ddev xdebug off # Toggle Xdebug on and off ddev xdebug toggle ``` The `ddev xdebug info` command displays detailed diagnostic information from Xdebug’s `xdebug_info()` function, including enabled features, optional features, diagnostic log, step debugging status, and all Xdebug configuration directives. This may be useful for troubleshooting Xdebug configuration issues. `xdebug info` is only supported on Xdebug 3+. ## `xhgui` Enable or disable [XHGui]() `ddev xhgui` only is useful if [xhprof\_mode]() is set to `xhgui`. - The `on` argument is equivalent to `enable` and `true`. - The `off` argument is equivalent to `disable` and `false`. ``` # Display whether XHGui is enabled and running ddev xhgui status # Turn XHGui on ddev xhgui # Launch the XHGui UI ddev xhgui launch # Turn XHGui on ddev xhgui on # Turn XHGui off ddev xhgui off ``` ## `xhprof` Enable or disable [Xhprof]() (global shell web container command). - The `on` argument is equivalent to `enable` and `true`. - The `off` argument is equivalent to `disable` and `false`. ``` # Display whether Xhprof is running ddev xhprof status # Turn Xhprof on ddev xhprof # Turn Xhprof on ddev xhprof on # Turn Xhprof off ddev xhprof off ``` ## `yarn` Run [`yarn` commands]() inside the web container in the root of the project (global shell host container command). > [!TIP] > > Use `--cwd` for another directory, or you can change directories to the desired directory and `ddev yarn` will act on the same relative directory inside the container. > [!TIP] > > If you want to define your Yarn version on a per project basis, set `corepack_enable: true` in `.ddev/config.yaml` or `ddev config --corepack-enable` Example: ``` # Use Yarn to install JavaScript packages ddev yarn install # Use Yarn to add the Lerna package ddev yarn add lerna # Use yarn in a relative directory cd web/core && ddev yarn add lerna # Use Yarn to add the Lerna package from the `web/core` directory ddev yarn --cwd web/core add lerna # Use latest yarn or specified yarn ddev config --corepack-enable && ddev restart ddev yarn set version stable ddev yarn --version ``` # Database Management DDEV provides lots of flexibility for managing your databases between your local, staging and production environments. You may commonly use the [`ddev import-db`]() and [`ddev export-db`]() commands, but there are plenty of other adaptable ways to work with your databases. If your project does *not* require a database, you can exclude it with the the [`omit_containers` configuration option](). > [!TIP] > > You can run `ddev [command] --help` for more info on many of the topics below. ## Database Imports Import a database with one command, from one of the following file formats: **`.sql`, `.sql.gz`, `.sql.bz2`, `.sql.xz`, `.sql.zst`, `.mysql`, `.mysql.gz`, `.tar`, `.tar.gz`, `.tar.zst`, and `.zip`**. Here’s an example of a database import using DDEV: ``` ddev import-db --file=dumpfile.sql.gz ``` You can also: - Use [`ddev mysql`]() or `ddev psql` or the `mysql` and `psql` commands inside the `web` and `db` containers. - Use a [database client](<#database-clients>) or [database GUI](<#database-guis>) to import and browse data. ## Database Backends and Defaults You can use a [variety of different database types](), including MariaDB (5.5–10.8, 11.4, 11.8, 12.3), MySQL (5.5–8.0, 8.4, 9.7), and PostgreSQL (9–18). If you want to *change* database type, you need to export your database, run [`ddev delete`]() to remove the project (and its existing database), change to a new database type, run [`ddev start`]() again, and [import your data](). (For very old database types see [Using DDEV to spin up a legacy PHP application]().) DDEV creates a default database named `db` and default permissions for the `db` user with password `db`, and it’s on the (inside Docker) hostname `db`. ## Extra Databases You can easily create and populate additional databases. For example, `ddev import-db --database=backend --file=backend.sql.gz` will create the database named `backend` with permissions for that same `db` user and import from the `backend.sql.gz` dumpfile. You can export in the same way: `ddev export-db -f mysite.sql.gz` will export your default database (`db`). The compression type comes from the file extension (`.gz`, `.zst`, `.xz`, `.bz2`, or `.sql` for none), or set it with `--compression`. `ddev export-db --database=backend -f backend-export.sql.gz` will dump the database named `backend`. ## Snapshots Snapshots let you easily save the entire status of all of your databases, which can be great when you’re working incrementally on migrations or updates and want to save state so you can start right back where you were. Snapshots can be named for easier reference later on. For example, [`ddev snapshot --name=two-dbs`]() would make a snapshot named `two-dbs` in the `.ddev/db_snapshots` directory. It includes the entire state of the db server, so in the case of our two databases above, both databases and the system level `mysql` or `postgres` database will all be snapshotted. Then if you want to delete everything with `ddev delete -O` (omitting the snapshot since we have one already), and then [`ddev start`]() again, we can `ddev snapshot restore two-dbs` and we’ll be right back where we were. Use the [`ddev snapshot restore`]() command to interactively choose among snapshots, or append `--latest` to restore the most recent snapshot: `ddev snapshot restore --latest`. It also accepts the path to a snapshot file outside the project: `ddev snapshot restore ~/tmp/mysnapshot-mariadb_11.8.zst`. Snapshots are stored as compressed (zstd, or gzip on very old database versions) files in the project’s `.ddev/db_snapshots` directory, and any or all snapshots can be removed with the `ddev snapshot --cleanup` command or by manually deleting the files when you want to save disk space or have no further use for them. ### Sharing Snapshots Between Git Worktrees If a project is a [Git worktree](), `ddev snapshot restore` also offers snapshots belonging to the same project checked out in the repository’s other worktrees, so a snapshot taken in one working copy doesn’t have to be manually copied to restore it in another. This works whether you pick interactively, restore by name, or use `--latest`, and `ddev snapshot --list` shows the other worktrees’ snapshots too, each labeled with the worktree it came from: ``` # In ~/repo/web ddev snapshot --name=before-migration # In ~/repo-feature-branch/web, a worktree of the same repository ddev snapshot restore # 'before-migration' from ~/repo/web is offered alongside this project's own snapshots ddev snapshot restore before-migration # also works directly by name, without the interactive picker ``` Only worktrees of the same repository are considered — a separate clone at the same relative path is not, even if it happens to have the same project name. If more than one sibling worktree has a snapshot with the same name, the most recently created one is used, without asking which. This is a convenience on top of the path-based restore above: `ddev snapshot restore ` and `ddev start --seed-snapshot=` already work with a snapshot from anywhere on disk, worktree or not. ### Uncompressed Snapshots `ddev snapshot`, `ddev snapshot restore`, and the reserved `seed` snapshot all compress the database backup by default, which is the right trade-off for almost everyone. Decompression still costs real time, though — routinely 30-40% of a restore or first `ddev start`, and it competes for the same CPU cores the database restore itself needs right after. `--uncompressed` skips it: ``` ddev snapshot --uncompressed ``` This is an unusual, deliberate trade-off, not a general recommendation: an uncompressed snapshot can be roughly as large as the database’s uncompressed datadir — many times bigger than the same snapshot compressed with zstd. It’s worth it mainly when disk space is cheap relative to CPU time, for example a low-core-count host, or a seed that’s read straight off fast local disk and never crosses a network (so compression buys nothing on the transfer side either). `ddev snapshot --list` shows each snapshot’s compression (`zstd`, `gzip`, or `none`) so you can tell which kind you’re looking at. Restoring an uncompressed snapshot with `ddev snapshot restore` needs no extra flag — the file’s extension (`.mbstream`/`.xbstream` instead of `.zst`/`.gz`) already tells DDEV there’s nothing to decompress. Not available for PostgreSQL projects. The reserved `seed` snapshot honors the same flag, and a `dbimage` built with a baked-in seed (see [Seeding a Custom Starter Database]()) can use an uncompressed seed the same way. ### Seeding a Fresh Database from a Snapshot The first time a project’s database volume is created — a brand-new project, or after `ddev delete` and [`ddev start`]() — DDEV can restore a snapshot into it instead of using its normal empty starter database. This is a direct restore of the database files, much quicker than importing a SQL dump on every fresh start. `seed` is a reserved snapshot name used for this. If a snapshot named `seed` exists in `.ddev/db_snapshots`, it’s used automatically, with nothing to remember at start time: ``` ddev snapshot --name=seed ddev delete -Oy ddev start ``` To use a different snapshot for one start, name it with `--seed-snapshot`, which takes either a snapshot name from `.ddev/db_snapshots` or the path to a snapshot file elsewhere on disk: ``` ddev start --seed-snapshot=large-dataset ddev start --seed-snapshot=~/datasets/production-mariadb_11.8.zst ``` `--seed-snapshot` applies to that command only and is never written to `config.yaml`. It only seeds a brand-new database volume; if the project already has a database, DDEV says so rather than silently ignoring the flag. Use [`ddev start --reset-database`](<#starting-over-with-a-new-database>) to throw the existing database away first, or [`ddev snapshot restore`]() to restore over it. A snapshot can only seed the database type and version it was made from, which is why the standard `-_.{zst,gz}` suffix `ddev snapshot` gives it has to stay on the filename. You can choose to commit the resulting `seed-*` file in `.ddev/db_snapshots` to your repository if you want teammates and CI to get the same starting dataset on their first `ddev start`, but it may be large and you may want to use another distribution technique. A `seed` snapshot is listed as [custom configuration](), so `ddev start` and [`ddev utility check-custom-config`]() both show it. When `ddev start` actually seeds a fresh database volume, it says so, naming the snapshot and its size and warning that a large one may take a while: ``` Initializing new database volume from the 'seed' snapshot /path/to/project/.ddev/db_snapshots/seed-mariadb_11.8.zst (2.2 GiB)... A large database may take a while; the 600s timeout is set by default_container_timeout. ``` ### Starting Over with a New Database `ddev start --reset-database` removes the project’s database and starts over with a new one. It snapshots the existing database first, unless you add `--omit-snapshot`/`-O`, and asks for confirmation unless you add `--skip-confirmation`/`-y`. The same flags work on [`ddev restart`](). Use it when you want a clean database, or when you’ve changed `database:` in `config.yaml` and `ddev start` refuses because the existing database was created by a different database server. The snapshot it takes is made with the database version that’s actually in the volume, so you can still restore it after you put the configuration back. ``` ddev config --database=postgres:17 ddev start --reset-database ``` To combine it with a seed, so the new database starts out with a dataset of your choosing rather than empty: ``` ddev start --reset-database --seed-snapshot=large-dataset ``` ## Database Clients The `ddev mysql` and `ddev psql` commands give you direct access to the `mysql` and `psql` clients in the database container, which can be useful for quickly running commands while you work. You might run `ddev mysql` to use interactive commands like `DROP DATABASE backend;` or `SHOW TABLES;`, or do things like `echo "SHOW TABLES;" | ddev mysql` or `ddev mysql -udb -pdb` to run with `db` user privileges. The `web` and `db` containers are each ready with MySQL/PostgreSQL clients, so you can [`ddev ssh`]() or `ddev ssh -s db` and use `mysql` or `psql`. ## `mysqldump` and `pg_dump` The `web` and `db` containers come with `mysqldump`. You could run [`ddev ssh`]() to enter the web container, for example, then `mkdir /var/www/html/.tarballs` and run `mysqldump db >/var/www/html/.tarballs/db.sql` or run `mysqldump db | gzip >/var/www/html/.tarballs/db.sql.gz` to create database dumps. Because `/var/www/html` is mounted into the container from your project root, the `.tarballs` directory will also show up in the root of the project on your host machine. The PostgreSQL database container includes normal `pg` commands like `pg_dump`. ## Database GUIs If you’d like to use a GUI database client, you’ll need the right connection details and there may even be a command to launch it for you: - phpMyAdmin, formerly built into DDEV core, can be installed by running `ddev add-on get ddev/ddev-phpmyadmin`. - Adminer can be installed with `ddev add-on get ddev/ddev-adminer` - The [`ddev describe`]() command displays the `Host:` details you’ll need to connect to the `db` container externally, for example if you’re using an on-host database browser like SequelAce. - macOS users can use [`ddev sequelace`]() to launch the free [Sequel Ace]() database browser, [`ddev tableplus`]() to launch [TablePlus](), [`ddev tablepro`]() to launch [TablePro](), [`ddev querious`]() to launch [Querious](), [`ddev dbeaver`]() to launch [DBeaver](). (Each must be installed for the command to exist.) - WSL2 and Linux users can use [`ddev dbeaver`]() to launch [DBeaver](). (Must be installed for the command to exist.) - PhpStorm (and all JetBrains tools) have a nice database browser. (If you use the [DDEV Integration plugin]() this is all done for you.) - Choose a static [`host_db_port`]() setting for your project. For example `host_db_port: 59002` (each project’s database port should be different if you’re running more than one project at a time). Use [`ddev start`]() for it to take effect. - Use the “database” tool to create a source from “localhost”, with the proper type `mysql` or `postgresql` and the port you chose, username `db` + password `db`. - Explore away! - VS Code or any of its forks (Cursor, Windsurf, etc.) has [DevDb](), an extension that lets you access your database directly inside the editor. - There’s a sample custom command that will run the free MySQL Workbench on macOS, Windows or Linux. To use it, run: - `cp $HOME/.ddev/commands/host/mysqlworkbench.example $HOME/.ddev/commands/host/mysqlworkbench` - `ddev mysqlworkbench` ## Database Query Examples You can query, update, or alter databases in DDEV like you would do on a regular server, using `ddev mysql`, `ddev mariadb`, or `ddev psql` or using those command-line tools inside the web or DB containers. Some examples are given below. - Create a new empty database named `newdatabase`: ``` ddev mysql -e 'CREATE DATABASE newdatabase; GRANT ALL ON newdatabase.* TO "db"@"%";' ``` - Show tables whose name begins with `node` : ``` ddev mysql -e 'SHOW TABLES LIKE "node%";' ``` - Use `ddev mysql` or `ddev mariadb` and issue interactive queries: ``` ddev mariadb Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 28 Server version: 10.11.10-MariaDB-ubu2204-log mariadb.org binary distribution Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [db]> SELECT * FROM node WHERE type="article"; +-----+------+---------+--------------------------------------+----------+ | nid | vid | type | uuid | langcode | +-----+------+---------+--------------------------------------+----------+ | 11 | 56 | article | 8af917ea-b150-4006-aeb9-877b53ebf289 | en | | 12 | 54 | article | 27a53763-9fd8-4813-853b-b476f0a73849 | en | +-----+------+---------+--------------------------------------+----------+ 2 rows in set (0.007 sec) ``` # Built-in Developer Tools Run [`ddev describe`]() to see the project information and services available for your project and how to access them. ## Command-line Tools in the Containers Hundreds of useful developer tools are included inside the containers and can be used via [`ddev exec`]() or [`ddev ssh`](). Some of those include: - MySQL client (`mysql`) - Command-line interface for interacting with MySQL/MariaDB. - PostgreSQL client (`psql`) - Command-line tool for PostgreSQL. - [PHIVE]() - Command-line tool for “PHAR Installation and Verification Environment”. - `npm` and `yarn` (these also have `ddev` shortcuts like [`ddev npm`]() and [`ddev yarn`]()). - `node` (for front-end build tools like [Vite](), see the [Vite Integration]() documentation) - `sqlite3` DDEV also installs some tools only for the project types that need them, such as [Drush](), [WP-CLI](), [n98-magerun](), and [n98-magerun2](). See [Project-Specific Utilities]() for the full list. These tools can be accessed for single commands using [`ddev exec `]() or [`ddev ssh`]() for an interactive `bash` or `sh` session. You can also add tools that are not provided by default using [`webimage_extra_packages` or a custom Dockerfile](). ## DDEV and Composer DDEV provides a built-in command to simplify use of PHP’s dependency manager, [Composer](), without requiring it to be installed on the host machine. Generally, executing any Composer command through DDEV is as simple as prepending the command with `ddev`. DDEV will execute the command at the project root in the web container, passing (almost) all arguments and flags to Composer. To execute Composer in other directories within the container, use `ddev ssh` or `ddev exec -d `. For example: - `ddev composer help` runs Composer’s help command to learn more about what’s available. - `ddev composer require ` tells Composer to require a specific PHP package for the current project. Additionally, Composer can be used to initialize new projects with `ddev composer create-project`. This command supports limited argument and flag options, and will install a new project to the composer root (normally `/var/www/html`). The package and version arguments are required: `ddev composer create-project [options] [--] [ [ []]]` For example: `ddev composer create-project --no-dev typo3/cms-base-distribution . "^9"` When using `ddev composer create-project` your project should be essentially empty or the command will refuse to run, to avoid loss of your files. `ddev composer create-project` does these things in this order: 1. `composer create-project --no-plugins --no-scripts --no-install` clones a bare Composer package without any additional actions. 2. `composer run-script post-root-package-install` runs if `--no-scripts` is not given as a flag to `ddev composer create-project`. 3. `composer install` runs if `--no-install` is not given as a flag to `ddev composer create-project`. 4. `composer run-script post-create-project-cmd` runs if `--no-scripts` is not given as a flag to `ddev composer create-project`. All flags that you provide to `ddev composer create-project` are checked for validity. All invalid flags will be ignored. If they are valid for `composer create-project`, they will be also passed to `composer run-script` and `composer install`, but only if these commands support these flags. To execute a fully-featured `composer create-project` command, you can execute the command from within the container after executing [`ddev ssh`](), or pass the full command to [`ddev exec`](), like so: `ddev exec composer create-project ...` **composer.json Location**: It’s most common for `composer.json` to be in the project root, but you can specify an alternate Composer root using DDEV’s `composer_root` option in `.ddev/config.yaml`, or `ddev config --composer-root `. The `composer_root` value is the *relative* path from the project root to the directory containing `composer.json`. If yours is at `docroot/composer.json`, for example, the `composer_root` value should be `docroot`. > [!TIP] > > **How to debug Composer itself?** > > To use Xdebug with Composer, [see instructions](). ### Composer Limitations > [!WARNING] > > **Container changes do not persist** > > Changes made outside your project directory are lost when the container restarts. Some Composer commands make such changes, see below. For other cases, see [Persisting Changes Across Restarts](). #### Composer Global Packages Do Not Persist If you run `ddev composer global require` (or `composer global require` inside the web container), global packages will be installed at the home directory within the container (`$HOME/.composer`) and will disappear when the container restarts. To keep them, [store `~/.composer` in `ddev-global-cache`](), or install the tools with a [custom Dockerfile](). #### Composer Self-Update Changes Do Not Persist Running `ddev composer self-update` (or `composer self-update` inside the web container) will update Composer within the container, but the change will be lost when the container restarts. Don’t try to keep `/usr/local/bin/composer` across restarts, because then `composer_version` and DDEV upgrades can’t update it. The Composer version is cached after the first image build and automatically updates only when DDEV is upgraded. If you need to update Composer: 1. Check the [`composer_version`]() setting in `.ddev/config.yaml` and set it to your desired version: - `composer_version: ""` (empty) - equivalent to `composer self-update --stable` - `composer_version: "2"` (v2) - equivalent to `composer self-update --2` - `composer_version: "2.2"` (v2.2 LTS) - equivalent to `composer self-update --2.2` - `composer_version: "2.9.3"` (specific) - equivalent to `composer self-update 2.9.3`. This is the most consistent option for teams, as it ensures all members use the exact same version regardless of when they first build the image, avoiding inconsistencies in dependency resolution. 2. [Restart]() to install it: ``` ddev restart ``` To get a newer release without changing the value, for example with `""` or `"2"`, restart with `--no-cache` or [rebuild](). This rebuilds the whole image and takes longer: ``` ddev restart --no-cache # or ddev utility rebuild ``` ### Composer from `vendor/bin/composer` If your `composer.json` specifies `composer/composer`, the version in `vendor/bin/composer` can be run with: ``` ddev exec vendor/bin/composer --version ``` If a custom [`composer_root`]() is set, use: ``` ddev exec '$DDEV_COMPOSER_ROOT/vendor/bin/composer --version' ``` ### Windows OS (Traditional Windows) and `ddev composer` DDEV attempts to help with Composer and some configurations of Docker Desktop for Windows that introduce complex filesystem workarounds. Use [`ddev composer`]() (Composer inside the container) instead of using `composer` on the host side, because it uses the right version of PHP and all its extensions for your project: - On some older configurations of Docker Desktop for Windows, symlinks are created in the container as “simulated symlinks”, or XSym files. These special text files behave as symlinks inside the container (on CIFS filesystem), but appear as simple text files on the Windows host. (On the CIFS filesystem used by Docker for Windows, inside the container, there is no capability to create real symlinks even though Windows now has this capability.) - DDEV attempts to clean up for this situation. Since Windows 10/11+ (in developer mode) can create real symlinks, DDEV scans your repository after a `ddev composer` command and attempts to convert XSym files into real symlinks. On older versions of Windows 10, it can only do this if your Windows 10 workstation is set to “Developer Mode”. - To enable developer mode on Windows 10/11+, search for “developer” in settings: ![Finding developer mode]() ![Setting developer mode]() ## Email Capture and Review (Mailpit) [Mailpit]() is a mail catcher that’s configured to capture and display emails sent by PHP in the development environment. After your project is started, access the Mailpit web interface at `https://mysite.ddev.site:8026`, or run [`ddev mailpit`]() to launch it in your default browser. Mailpit will **not** intercept emails if your application is configured to use SMTP or a third-party ESP integration. For Drupal 9+, `settings.ddev.php` automatically overrides [Symfony Mailer]() and [Symfony Mailer Lite]() transport configurations to use Mailpit; for other SMTP modules (like [SMTP]()), update your application’s SMTP server configuration to use `localhost` and Mailpit’s port `1025`. For Laravel projects, Mailpit will capture Swift messages via SMTP. Update your `.env` to use Mailpit with the following settings: ``` MAIL_MAILER=smtp MAIL_HOST=localhost MAIL_PORT=1025 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null ``` > [!NOTE] > > **Mailpit auto-tagging behavior** > > Mailpit automatically extracts [tags]() from email addresses using *plus addressing*. For example, an email sent to `address+1@example.com` will be tagged with `1` (the part after `+`). This can be useful for filtering, but may be unexpected if you’re not aware of it. > > If this behavior is distracting, you can disable it by setting the following [environment variable](): > > ``` > MP_TAGS_DISABLE=plus-addresses > ``` > > The easiest way is to set it globally for all DDEV projects: > > ``` > ddev config global --web-environment-add=MP_TAGS_DISABLE=plus-addresses > ``` ## Using Development Tools on the Host Machine It’s possible in many cases to use development tools installed on your host machine on a project provisioned by DDEV. Tools that interact with files and require no database connection, such as Git or Composer, can be run from the host machine against the codebase for a DDEV project with no additional configuration necessary. ### Database Connections from the Host If you need to connect to your project’s database from your workstation, run [`ddev describe`]() to show the database connection information, like `Host: localhost:49156`. Each project’s database port is unique, and randomly chosen from available ports on your system when you run [`ddev start`](). You can force this port to be the same on every [`ddev start`]() by setting [`host_db_port`]() in the project’s `.ddev/config.yaml`. For example, `host_db_port: "49156"` or `ddev config --host-db-port=49156`. This value needs to be different on each running DDEV project, and unless it is set, the database port will change on every `ddev start`. You can use this port with various tools that need a direct port, like `mysql` or `psql` clients, but it’s usually easiest to use [`ddev mysql`](), `ddev psql`, `ddev sequelace`, [`ddev tableplus`](), [`ddev tablepro`](), etc, which set everything up for you. (If you use PhpStorm and its integrated database browser, use the [DDEV Integration Plugin]() to manage all of this for you.) ## DDEV and Terminus [Terminus]() is a command-line tool providing advanced interaction with [Pantheon]() services. `terminus` is available inside the project’s container, allowing users to get information from, or issue commands to their Pantheon-hosted sites. This is an especially helpful feature for Windows users since Terminus is only officially supported on Unix-based systems. > [!TIP] > > **How to change Terminus version?** > > See [How to Downgrade Terminus in DDEV’s Web Container and Customize Other Bundled Tools](). To use Terminus, you’ll first need to: 1. Use a machine token. (See [Pantheon provider discussion]().) 2. Run [`ddev ssh`]() to tunnel into your container. 3. Issue a command using the keyword `terminus`. For help using Terminus, try `terminus list` to get a list of possible commands. Terminus also allows you to issue [Drush](), [WP-CLI](), and [Composer]() commands to your Pantheon server. These are all usable from within the container as well, but will require authentication via either your Pantheon password or an SSH key. To use your host machine’s SSH key, you can use the `ddev auth ssh` command [described here](). # Opt-In Usage Information When you start DDEV for the first time or install a new release, you’ll be asked whether to send usage and error information to DDEV’s developers. > [!TIP] > > **See [DDEV Live Usage Statistics]()** Regardless of your choice, you can change this at any time using one of these approaches: - `ddev config global --instrumentation-opt-in=false` (or `=true`), which sets [`instrumentation_opt_in`]() in `$HOME/.ddev/global_config.yaml` (see [global configuration directory]()) - `export DDEV_NO_INSTRUMENTATION=true` to disable it regardless of the config file setting Usage information is also never collected in automated workflows where `CI=true` is set in the environment. If you choose to share diagnostics, it helps us tremendously in our effort to improve the tool. DDEV tracks three kinds of events: `Identify` (about your machine), `Command` (which command you ran), and `Project` (your project’s configuration). Here’s an example of what we might see: ``` // Sent once per machine, to identify the environment DDEV runs in. client.Track(&analytics.Track{ DeviceId: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", AppVersion: "v1.25.3", Platform: "arm64", OSName: "darwin", Language: "en_US.UTF-8", ProductID: "ddev cli", Event: "Identify", Properties: map[string]interface{}{ "DDEV Environment": "darwin", "Docker Platform": "docker-desktop", "Docker Version": "27.3.1", "Timezone": "PST", // "User" is only sent if you've set `instrumentation_user` yourself "User": "jane@example.com", // "WSL Distro" is only sent when DDEV runs inside WSL2 "WSL Distro": "Ubuntu", }, }) // Sent on every command run. client.Track(&analytics.Track{ DeviceId: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", AppVersion: "v1.25.3", Platform: "arm64", OSName: "darwin", Language: "en_US.UTF-8", ProductID: "ddev cli", Event: "Command", Properties: map[string]interface{}{ // Positional arguments are sent as-is for built-in commands. // For your own custom commands the command name is replaced // with "custom-command" and the arguments are dropped entirely. "Arguments": []string{}, "Called As": "start", "Command Name": "start", "Command Path": "ddev start", }, }) // Sent when a project starts, describing its configuration. client.Track(&analytics.Track{ DeviceId: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", AppVersion: "v1.25.3", Platform: "arm64", OSName: "darwin", Language: "en_US.UTF-8", ProductID: "ddev cli", Event: "Project", Properties: map[string]interface{}{ "ID": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210", "Performance Mode": "mutagen", "PHP Version": "8.5", "Project Type": "php", "Webserver Type": "nginx-fpm", "XHProf Mode": "xhgui", "Add-ons": []string{"redis"}, "Add-on Repositories": []string{"ddev/ddev-redis"}, "Bind All Interfaces": false, "CI": false, "Containers": []string{"db", "redis", "web"}, "Containers Omitted": []string{}, "Corepack Enable": true, "Database Type": "mariadb", "Database Version": "11.8", "DB Image Extra Packages": []string{"netcat"}, "DDEV Version Constraint": ">= 1.25.3", "Disable Settings Management": false, "Fail On Hook Fail": false, "No Project Mount": false, "Nodejs Version": "24", "Router": "traefik", "Router Disabled": false, "WebExtraDaemonsDetails": []string{"{\"Name\":\"vite\",\"Command\":\"npm run dev\",\"Directory\":\"/var/www/html\"}"}, "WebExtraDaemonsNames": []string{"vite"}, "WebExtraExposedPortsDetails": []string{"{\"Name\":\"vite\",\"WebContainerPort\":5173,\"HTTPPort\":5172,\"HTTPSPort\":5173}"}, "WebExtraExposedPortsNames": []string{"vite"}, "Webimage Extra Packages": []string{"php${DDEV_PHP_VERSION}-tidy"}, }, }) ``` If you have any issues or concerns with it, we’d like to know. # FAQ Frequently-asked questions organized into high-level functionality, investigating issues, daily usage, and connecting with our community. ## Features & Requirements ### What operating systems will DDEV work with? DDEV works nearly anywhere Docker will run, including macOS, WSL2, Windows 10/11 Pro/Enterprise and Home, and every Linux variant we’ve ever tried. It also runs in many Linux-like environments, like ChromeOS (in Linux machine). DDEV works the same on each of these platforms since the important work is done inside identical Docker containers. This means that a team using diverse environments can share everything without trouble. ### Does DDEV change or deploy my code? You are responsible for your code and its deployment. DDEV does not alter any code or fix any bugs in it. DDEV *does* add DDEV-specific settings for some CMSes if the [settings management]() is enabled. These items are excluded by `.gitignore` so they won’t affect a deployed project, but in most cases they would do no harm if deployed, because they check to see if they’re running in DDEV context. ### Why do I have to type `ddev` in front of so many commands? When you are using commands like `ddev composer`, `ddev drush`, `ddev npm`, or `ddev yarn`, you are telling DDEV to execute that very command inside the web container. That is where the exact tool for the exact environment required by your project lives. It’s possible to execute `composer install` without prepending `ddev` in your project folder, but often you won’t have the same PHP version on your host computer as your project is configured to use inside the container, or perhaps you’ll have a different version of `composer` even. This can lead into workarounds like having to use `composer --ignore-platform-reqs` or even introducing incompatibilities into your project. With tools like `ddev composer` you are able to run several projects at the same time, each with different configurations, but when you use the tool inside the container, you get the exact configuration for the project you’ve configured. You can run any tool inside the web container with `ddev exec`, but many commands like `ddev composer` have two-word shortcuts. However, if you prefer not to use `ddev` in front of commands, you can `ddev ssh` and execute the commands inside the web container. ### Where is my database stored in my DDEV project? The MariaDB, MySQL, or PostgreSQL database for your project lives in a Docker volume, which means it does not appear in your DDEV project’s filesystem, and is not checked in. This configuration is for performance and portability reasons, but it means that if you change Docker providers or do a factory reset on your Docker provider, you will lose databases. By default many Docker providers do not keep Docker volumes where they are backed up by normal backup solutions. Remember to keep backups using `ddev export-db` or `ddev snapshot`. See [How can I migrate from one Docker provider to another](<#how-can-i-migrate-from-one-docker-provider-to-another>). ### What Docker providers can I use? We have automated testing and support for a staggering range of Docker providers. | Docker Provider | Support Level | | --- | --- | | OrbStack (macOS) | officially tested and supported on macOS | | Docker Desktop for Mac | officially tested and supported on Apple Silicon | | Docker Desktop for Windows | officially tested and supported on WSL2 and traditional Windows | | Colima (macOS) | officially tested and supported | | Docker-ce (Linux/WSL2) | officially supported with automated tests on WSL2/Ubuntu. Recommended. | | Rancher Desktop (macOS) | officially tested and supported on macOS | #### Minimally Supported The following environments do not have automated test coverage and are only minimally supported: - Docker Desktop for Linux. Although Docker Desktop for Linux appears to work successfully in limited manual testing, we recommend the documented [docker-ce]() installation instead. - Rancher Desktop for Windows works in limited manual testing, but does not have automated testing. This is the only open-source Docker provider for traditional Windows. ### How can I migrate from one Docker provider to another? There are many Docker providers on DDEV’s supported platforms. For example, on macOS people use Docker Desktop and OrbStack along with Colima and Rancher Desktop. On Windows WSL2, people may use Docker Desktop or Docker CE inside WSL2. In all cases, if you want to switch between Docker providers, save your database and make sure the Docker providers don’t interfere with each other: 1. Save away your projects’ databases. You can run `ddev snapshot --all` to make snapshots of all *registered* projects (that show up in `ddev list`). If you prefer a different way of saving database dumps, that works too! 2. Stop the Docker provider you’re moving from. For example, exit Docker Desktop. 3. Start the Docker provider you’re moving to. 4. Start projects and restore their databases. For example, you could run `ddev snapshot restore --latest` to load a snapshot taken in step one. ### Do I need to install PHP, Composer, nginx, or Node.js/npm on my workstation? No. Tools like PHP, Composer, nginx, and Node.js/npm live inside DDEV’s Docker containers, so you only need to [install Docker]() and [install DDEV](). For most users we recommend that you do *not* install PHP or composer on your workstation, so you get in the habit of using `ddev composer`, which will use the configured composer and PHP versions for your project, which can be different for each project. See [DDEV and Composer](). ### Do I lose data when I run `ddev poweroff`, `ddev stop`, or `ddev restart`? No. Your code continues to live on your workstation, and your database is safely stored on a Docker volume—both unaffected by these commands. ### How can I connect to my database? The answer depends on where you’re connecting *from*. The [`ddev describe`]() command includes database connection details in a row like this: ``` │ db │ OK │ InDocker: ddev-mysite-db:3306 │ mariadb:10.3 │ │ │ │ Host: localhost:63161 │ User/Pass: 'db/db' │ │ │ │ │ or 'root/root' │ ``` Inside your project container, where the app itself is running, the database hostname is `db` (**not** `127.0.0.1`) and the port is the default for your database engine—`3306` for MySQL/MariaDB, `5432` for PostgreSQL. Outside your project’s web container, for example a database GUI on your workstation, the hostname is `localhost` and the port is unique to that project. In the example above, it’s `63161`. The username, password, and database are each `db` regardless of how you connect. ### Can I use additional databases with DDEV? Yes, you can create additional databases and manually do whatever you need on them. They’re created automatically if you use `ddev import-db` with the `--database` option. In this example, `extradb.sql.gz` is extracted and imported to a newly-created database named `extradb`: ``` ddev import-db --database=extradb --file=.tarballs/extradb.sql.gz ``` You can use [`ddev mysql`]() or `ddev psql` to execute queries, or use the MySQL/PostgreSQL clients within `ddev ssh` or `ddev ssh -s db`. See the [Database Management]() page. ### Can different projects communicate with each other? Yes, this is commonly required for situations like Drupal migrations or server-side API calls between projects. See [Inter-Project Communication]() for detailed information about database and HTTP/S communication between projects. ### Can I run DDEV with other development environments at the same time? Yes, as long as they’re configured to use different ports. It doesn’t matter whether your other environments use Docker or not, it should only be a matter of avoiding port conflicts. It’s probably easiest, however, to shut down one before using the other. For example, if you use Lando for one project, do a `lando poweroff` before using DDEV, and then run [`ddev poweroff`]() before using Lando again. If you run nginx or Apache locally, stop them before using DDEV. The [troubleshooting]() section goes into more detail about identifying and resolving port conflicts. ### Can AI assistants read the DDEV docs? Yes. The docs are also published as Markdown for AI assistants and coding agents: - [`llms.txt`]() lists every page, with a link to its Markdown version. - [`llms-full.txt`]() contains all pages in one file. ## Performance & Troubleshooting ### How can I get the best performance? Docker’s normal mounting can be slow on macOS and traditional Windows. DDEV provides its Mutagen feature to work around this, giving fantastic webserving performance. See the [Performance]() section for how Mutagen works. It’s enabled by default on macOS and Windows. ### How can I troubleshoot what’s going wrong? See these sections: - [General troubleshooting]() - [Xdebug troubleshooting]() - [Docker troubleshooting]() - [Traefik troubleshooting]() - [Mutagen troubleshooting]() ### How can I check that Docker is working? See the [troubleshooting section]() on the Docker Installation page. ### Why do I get a 403 or 404 on my project after `ddev launch`? Most likely because the docroot is misconfigured, or there’s no `index.php` or `index.html` in it. Open your `.ddev/config.yaml` file and check the [`docroot`]() value, which should be a relative path to the directory containing your project’s `index.php`. ### Why do I see nginx headers when I’ve set `webserver_type: apache-fpm`? Apache runs in the web container, but when you use the `https://*.ddev.site` URL, it goes through `ddev-router`, which is an nginx reverse proxy. That’s why you see nginx headers even though your web container’s using Apache. Read more in [this Stack Overflow answer](). ### Why are my Apache HTTP → HTTPS redirects stuck in an infinite loop? It’s common to set up HTTP-to-TLS redirects in an `.htaccess` file, which leads to issues with the DDEV proxy setup. The TLS endpoint of a DDEV project is always the `ddev-router` container and requests are forwarded through plain HTTP to the project’s web server. This results in endless redirects, so you need to change the root `.htaccess` file for Apache correctly handles these requests for your local development environment with DDEV. The following snippet should work for most scenarios—even outside of DDEV—and could replace an existing redirect: ``` # http:// -> https:// plain or behind proxy for Apache 2.2 and 2.4 # behind proxy RewriteCond %{HTTP:X-FORWARDED-PROTO} ^http$ RewriteRule (.*) https://%{HTTP_HOST}/$1 [R=301,L] # plain RewriteCond %{HTTP:X-FORWARDED-PROTO} ^$ RewriteCond %{REQUEST_SCHEME} ^http$ [NC,OR] RewriteCond %{HTTPS} off RewriteRule (.*) https://%{HTTP_HOST}/$1 [R=301,L] ``` ### My browser redirects `http` URLs to `https` Several browsers want you to use `https`, so they will automatically redirect you to the `https` version of a site. This may not be what you want, and things may break on redirect. For example, the Apache SOLR web UI often doesn’t work with `https`, and when it redirects it things might break. To solve this for your browser, see: - [Google Chrome]() - [Mozilla Firefox]() - [Safari]() ### Why is `ddev-webserver` such a huge Docker image? When you update DDEV you’ll see it pull a `ddev-webserver` image which is almost half a gigabyte compressed, and this can be an inconvenient thing to wait for when you’re doing an upgrade, especially if you have a slow internet connection. The reason that `ddev-webserver` is so big is that it’s built for your daily requirements for a local development environment. It lets you switch PHP versions or switch between `nginx` and `apache` web servers with a simple `ddev restart`, rather than a lengthy build process. It lets you use Xdebug with a simple `ddev xdebug on`. It has many, many features and tools that make it easy for you as a developer, but that one would not include in a production image. ## Workflow ### How can I update/upgrade DDEV? See **[Upgrading DDEV]()** for your operating system and installation technique. You can use the [`ddev self-upgrade`]() command for quick instructions tailored to your installation. ### How can I install a specific version of DDEV? #### Debian, Ubuntu, or WSL2 For Debian/Ubuntu/WSL2 with DDEV installed via apt, you can run `sudo apt-get update && sudo apt-get install ddev=`, for example `sudo apt-get install ddev=1.23.4` to run a previous, older version of DDEV. #### Homebrew Homebrew itself abandoned the ability to specify versions a few years ago, but you can still install an older release by checking out its formula from the tap’s own Git history, then pinning it so `brew upgrade` doesn’t move it: ``` # downgrade to v1.23.5 cd "$(brew --repository ddev/ddev)" git checkout "$(git rev-list -n 1 --grep='v1.23.5' HEAD)" HOMEBREW_NO_AUTO_UPDATE=1 brew reinstall ddev/ddev/ddev brew pin ddev/ddev/ddev git switch master ``` To go back to the latest release: ``` brew unpin ddev/ddev/ddev brew update && brew upgrade ddev/ddev/ddev ``` If you want the very latest, unreleased version of DDEV instead, install from `HEAD`: ``` # switch to HEAD brew uninstall --force ddev brew install ddev/ddev/ddev --HEAD # go back to the latest release brew uninstall --force ddev brew install ddev/ddev/ddev ``` Your projects are not affected by these changes. #### Any platform On any platform you can download the binaries for a specific release with [`ddev utility download-ddev`](), for example `ddev utility download-ddev --tag v1.25.3`, which writes `ddev` and `ddev-hostname` to `~/tmp/ddev-download-ddev/v1.25.3` so you can try that version before moving them into your `$PATH`. ### Why do I have an old DDEV? You may have installed DDEV several times using different techniques. Use `which -a ddev` to find all installed binaries. For example, you could install a DDEV in WSL2 with Homebrew, forget about it for a while, install it manually, and then install it again with `apt`: ``` $ which -a ddev /home/linuxbrew/.linuxbrew/bin/ddev # installed with Homebrew /usr/local/bin/ddev # installed manually with install_ddev.sh script /usr/bin/ddev # installed with apt or yum/rpm /bin/ddev # don't touch it, it's a link to /usr/bin/ddev ``` You can check each binary version by its full path (`/usr/bin/ddev --version`) to find old versions. Remove them preferably in the same way you installed them, i.e. `/home/linuxbrew/.linuxbrew/bin/ddev` should be removed with Homebrew: `brew uninstall ddev`. A manually installed DDEV can be removed by deleting the `ddev` binary. Restart the terminal (or run `hash -r`) after uninstalling other versions of DDEV for the changes to take effect. If you see duplicates in the `which -a ddev` output, it means that some directories are added to your `$PATH` more than once. You can either ignore this or remove the extra directory from your `$PATH`. ### Should I check in the `.ddev` directory? How about add-ons? Most teams check in the project `.ddev` directory. That way all team members will have the exact same configuration for the project, even if they’re on different operating systems or architectures or Docker providers. DDEV [add-ons]() are installed via the `.ddev` directory, so checking things in will get them as well, and that’s also recommended practice. Do *not* alter or check in the `.ddev/.gitignore` as it is automatically generated to DDEV and does its best to figure out what files you “own” (like the `.ddev/config.yaml`) and which files DDEV “owns”, so do not have to be committed. ### How can I back up or restore all project databases? You can back up all projects that show in `ddev list` with `ddev snapshot -a`. This only snapshots projects displayed in `ddev list`; any projects not shown there will need to be started so they’re be registered in `ddev list`. ### How can I share my local project with someone? See [Sharing Your Project](). ### How do I make DDEV match my production environment? You can change the major PHP version and choose between nginx+fpm (default) and Apache+fpm and choose the MariaDB/MySQL/PostgreSQL version add [extra services like Solr and Memcached](). You won’t be able to make every detail match your production server, but with database server type and version, PHP version and web server type you’ll be close. The [lightly maintained rfay/ddev-php-patch-build add-on]() may allow you to use a specific PHP patch version. ### How do I completely destroy a project? Use [`ddev delete `]() to destroy a project. By default, a [`ddev snapshot`]() of your database is taken, but you can skip this using `ddev delete --omit-snapshot` or `ddev delete --omit-snapshot -y`. See `ddev delete -h` for options. It’s up to you to then delete the code directory. ### What if I don’t like the settings files or gitignores DDEV creates? You have several options: - Use the [`disable_settings_management: true`]() option in the project’s `.ddev/config.yaml` file. This disables DDEV from updating CMS-related settings files. - Use the more generic “php” project type rather than a CMS-specific one; it basically means “don’t try to create settings files for me”. The “php” type works great for experienced developers. - Take over the settings file or `.gitignore` by deleting the line `#ddev-generated` in it, then check in the file. If that line is removed, DDEV will not try to replace or change the file. (If you don’t want DDEV to warn you about customized files, add `#ddev-silent-no-warn` to the file.) ### How can I change a project’s name? Delete it and migrate it to a new project with your preferred name: 1. Export the project’s database: `ddev export-db --file=/path/to/db.sql.gz`. 2. Delete the project: `ddev delete `. (This takes a snapshot by default for safety.) 3. Remove the `.ddev/traefik/config` directory, `rm -r .ddev/traefik/config`, since it may have name-based configuration. 4. Rename the project: `ddev config --project-name=`. 5. Start the new project with `ddev start`. 6. Import the database dump from step one: `ddev import-db --file=/path/to/db.sql.gz`. ### How can I move a project to another directory? Run [`ddev stop --unlist`](), then move the directory, then run [`ddev start`]() in the new directory. ### How can I move a project to another workstation? Take a snapshot, move the project files, and restore the snapshot in a new project on the target workstation: 1. `ddev start && ddev snapshot`. 2. `ddev stop --unlist`. 3. Move the project directory to another computer any way you want. 4. On the new computer, run `ddev start && ddev snapshot restore --latest`. 5. Optionally, on the old computer, run `ddev delete --omit-snapshot` to remove its copy of the database. ### How can I move a project from traditional Windows to WSL2? WSL2 has intolerably slow access to the Traditional Windows NTFS/CIFS filesystem, but the WSL2 filesystem is blazingly fast. It’s really important to move your project to the WSL2 native filesystem. This is exactly the same as moving a project from one computer to another described above. Make a backup of the database, `ddev snapshot && ddev stop --unlist`, and then copy your project to the new WSL2 location. For example, if your project is at `C:\Users\you\sites\someproject` you could do this in your WSL2 distro: ``` cd $HOME mkdir -p sites && cd sites cp -r "/mnt/c/you/sites/someproject" . cd someproject ddev start ddev snapshot restore --latest ``` ### Why does DDEV want to edit `/etc/hosts`? If you see “The hostname is not currently resolvable” and you can successfully `ping `, it may be that DNS resolution is slow. DDEV doesn’t have control over your computer’s name resolution, so it doesn’t have any way to influence how your browser gets an IP address from a hostname. It knows you have to be connected to the internet to do that, and uses a test DNS lookup of `.ddev.site` as a way to guess whether you’re connected to the internet. If it’s unable to do a name lookup, or if the hostname associated with your project is not `*.ddev.site`, it will try to create entries in `/etc/hosts`, since it’s assuming you can’t look up your project’s hostname(s) via DNS. If your internet (and name resolution) is actually working, but DNS is slow, run `ddev config global --internet-detection-timeout-ms=3000` to set the timeout to 3 seconds (or higher). See [this GitHub issue]() for more. (If DNS rebinding is disallowed on your network/router, this won’t be solvable without network/router changes. Help in [comment 1]() and [comment 2]().) For more detailed troubleshooting information, please see the [troubleshooting section](). ### How can I configure a project with the defaults without hitting RETURN a bunch of times? Use `ddev config --auto` to set the docroot and project type based on the discovered code. If anything in `.ddev/config.yaml` is wrong, you can edit that directly or use [`ddev config`]() commands to update settings. You can also use `ddev config --update` if the code is already in place and it will autodetect the project type and other needed settings. ### I want to use the same code with various project names If you remove the `name` line from your project’s `.ddev/config.yaml`, then the project will take the name of the directory it is in. This allows you to have multiple check-outs of the same project, each with different names. It can also allow team members to have the same project run using different hostnames and URLs. ### What happens when one DDEV project is nested inside another? Avoid nesting one DDEV project inside another; it only causes confusion. It usually happens by accident, with a Git submodule or dependency that ships its own `.ddev/config.yaml`. DDEV only uses a project that shows up in `ddev list`, so inside an unregistered nested project it warns and uses the registered project around it instead. To use the nested project, run `ddev start` in its directory and confirm the prompt, which adds it to `ddev list`. ## Getting Involved ### How do I get support? See the [support options](), including [Discord]() and the [issue queue](). ### How can I contribute to DDEV? We love and welcome contributions of knowledge, support, docs, and code: - Submit an issue or pull request to the [main repository](). - Add your external resource to [awesome-ddev](). - Help others in [Discord](). - Contribute financially by [sponsoring DDEV](). - Get involved with DDEV governance and the [Advisory Group](). ### How do financial contributions support DDEV? Thanks for asking! Contributions made by [sponsoring DDEV]() go to the [DDEV Foundation]() and are used for infrastructure and supporting development. # Usage This section covers day-to-day DDEV usage, reference material, and common support details. ## Common Tasks - [Using the `ddev` Command]() - [Interactive Dashboard]() - [Database Management]() - [Managing CMS Settings]() - [Managing Projects]() - [Troubleshooting]() - [Using DDEV Offline]() - [Upgrading DDEV]() ## Reference - [How DDEV Works]() - [Commands]() - [Built-in Developer Tools]() - [Diagnostics]() - [Uninstalling]() ## Getting Help - [FAQ]() - [Support]() - [Code of Conduct]() ## Configuration Files The [`ddev config`]() and `ddev start` commands attempt to create a CMS-specific settings file pre-populated with DDEV credentials. If you don’t want DDEV to do this, set the [`disable_settings_management`]() config option to `true`. For **Craft CMS** DDEV settings are added to the `.env` file. For **Drupal** and **Backdrop**, DDEV settings are written to a DDEV-managed file, `settings.ddev.php`. The `ddev config` command will ensure these settings are included in your `settings.php` through the following steps: - Write DDEV settings to `settings.ddev.php`. - If no `settings.php` file exists, create one that includes `settings.ddev.php`. - If a `settings.php` file already exists, ensure that it includes `settings.ddev.php`, modifying `settings.php` to write the include if necessary. For **Magento 1**, DDEV settings go into `app/etc/local.xml`. In **Magento 2**, DDEV settings go into `app/etc/env.php`. For **Maho**, DDEV settings go into `app/etc/local.xml`. For **TYPO3**, DDEV settings are written to `config/system/additional.php`. If `additional.php` exists and is not managed by DDEV, it will not be modified. For **WordPress**, DDEV settings are written to a DDEV-managed file, `wp-config-ddev.php`. The `ddev config` command will attempt to write settings through the following steps: - Write DDEV settings to `wp-config-ddev.php`. - If no `wp-config.php` exists, create one that include `wp-config-ddev.php`. - If a DDEV-managed `wp-config.php` exists, create one that includes `wp-config.php`. - If a user-managed `wp-config.php` exists, instruct the user on how to modify it to include DDEV settings. You’ll know DDEV is managing a settings file when you see a comment containing `#ddev-generated` like the one below. Remove the comment and DDEV will not attempt to overwrite it. If you’re letting DDEV create its settings file, we recommended leaving this comment so DDEV can continue to manage it, and make any needed changes in another settings file. ``` /** #ddev-generated: Automatically generated Drupal settings.php file. ddev manages this file and may delete or overwrite the file unless this comment is removed. */ ``` If you’re providing the `settings.php` or `wp-config.php` and DDEV is creating `settings.ddev.php` (or `wp-config-local.php`, `AdditionalConfig.php`, or similar), the main settings file must explicitly include the appropriate DDEV-generated settings file. Any changes you need should be included somewhere that loads after DDEV’s settings file, for example in Drupal’s `settings.php` *after* `settings.ddev.php` is included. (See [Adding Configuration](<#adding-configuration>) below). ### Adding Configuration **Drupal and Backdrop**: In `settings.php`, enable loading `settings.local.php` after `settings.ddev.php` is included—creating a new one if it doesn’t already exist—and make changes there. Wrap with `if (getenv('IS_DDEV_PROJECT') == 'true')` as needed. **WordPress**: Load a `wp-config-local.php` after `wp-config-ddev.php`, and make changes there. Wrap with `if (getenv('IS_DDEV_PROJECT') == 'true')` as needed. ## Listing Project Information > [!TIP] > > **TUI (Terminal UI) Alternative** > > You can also view and manage all your projects from the [interactive dashboard]() by running `ddev` with no arguments. Run [`ddev list`]() or `ddev list --active-only` current projects. ``` ➜ ddev list NAME STATUS LOCATION URL TYPE d10 stopped ~/workspace/d10 drupal10 drup running (ok) ~/workspace/drup https://drup.ddev.site drupal my-cakephp-app stopped ~/workspace/my-cakephp-app cakephp Router healthy ~/.ddev http://127.0.0.1:10999 traefik ``` ``` ➜ ddev list --active-only NAME STATUS LOCATION URL TYPE drup running (ok) ~/workspace/drup https://drup.ddev.site drupal Router healthy ~/.ddev http://127.0.0.1:10999 traefik ``` You can also see more detailed information about a project by running [`ddev describe`]() from its working directory. You can also run `ddev describe [project-name]` from any location to see the detailed information for a running project. ``` $ ddev describe drup ┌──────────────────────────────────────────────────────────────────────────────────┐ │ Project: drup ~/workspace/drup https://drup.ddev.site │ │ Docker platform: orbstack │ │ Router: traefik │ ├──────────┬──────┬───────────────────────────────────────────┬────────────────────┤ │ SERVICE │ STAT │ URL/PORT │ INFO │ ├──────────┼──────┼───────────────────────────────────────────┼────────────────────┤ │ web │ OK │ https://drup.ddev.site │ drupal PHP8.4 │ │ │ │ InDocker: web:8025,443,80 │ nginx-fpm │ │ │ │ Host: 127.0.0.1:33016,33017 │ docroot:'web' │ │ │ │ │ Perf mode: mutagen │ │ │ │ │ NodeJS:20 │ ├──────────┼──────┼───────────────────────────────────────────┼────────────────────┤ │ db │ OK │ InDocker: db:3306 │ mariadb:11.8 │ │ │ │ Host: 127.0.0.1:33018 │ User/Pass: 'db/db' │ │ │ │ │ or 'root/root' │ ├──────────┼──────┼───────────────────────────────────────────┼────────────────────┤ │ Mailpit │ │ Mailpit: https://drup.ddev.site:8026 │ │ │ │ │ `ddev mailpit` │ │ ├──────────┼──────┼───────────────────────────────────────────┼────────────────────┤ │ All URLs │ │ https://drup.ddev.site, │ │ │ │ │ https://127.0.0.1:33016, │ │ │ │ │ http://drup.ddev.site, │ │ │ │ │ http://127.0.0.1:33017 │ │ └──────────┴──────┴───────────────────────────────────────────┴────────────────────┘ ``` ## Removing Projects There are two ways to remove a project from DDEV’s listing. The first, the [`ddev delete`]() command, is destructive. It removes the project from DDEV’s list, deletes its database, and removes the hostname entry from the hosts file: `ddev delete ` or `ddev delete --omit-snapshot ` If you don’t want the project to show up in [`ddev list`]() anymore, use [`ddev stop`]()—which is nondestructive—to unlist the project until the next time you run [`ddev start`]() or [`ddev config`](): ``` ddev stop --unlist ``` ## Importing Assets for An Existing Project An important aspect of local web development is the ability to have a precise local recreation of the project you’re working on, including up-to-date database contents and static assets like uploaded images and files. DDEV provides two commands to help with importing assets to your local environment. ### Importing a Database The [`ddev import-db`]() command imports the database for a project. Running this command will prompt you to specify the location of your database import. By default `ddev import-db` empties the default `db` database, then loads the provided dump file. Most people use it with command flags, like `ddev import-db --file=.tarballs/db.sql.gz`, but it can also prompt for the location of the dump if you only use `ddev import-db`: ``` ddev import-db Provide the path to the database you wish to import. Import path: ~/Downloads/db.sql Importing database... Successfully imported database for drupal8 ``` #### Supported Database Import File Types Database imports can be any of the following file types: - Raw SQL Dump (`.sql`) - Gzipped SQL Dump (`.sql.gz`) - `bzip2` SQL Dump (`.sql.bz2`) - Xz’d SQL Dump (`.sql.xz`) - Zstandard SQL Dump (`.sql.zst`) - Tarball Archive (`.tar`, `.tar.gz`, `.tgz`, `.tar.bz2`, `.tar.xz`, `.tar.zst`) - ZIP Archive (`.zip`) - stdin If a Tarball Archive or ZIP Archive is provided for the import, you’ll be prompted to specify a path within the archive to use for the import asset. The specified path should provide a raw SQL dump (`.sql`). In the following example, the database we want to import is named `data.sql` and resides at the top level of the archive: ``` ddev import-db Provide the path to the database you wish to import. Import path: ~/Downloads/site-backup.tar.gz You provided an archive. Do you want to extract from a specific path in your archive? You may leave this blank if you wish to use the full archive contents Archive extraction path: data.sql Importing database... A settings file already exists for your application, so ddev did not generate one. Run 'ddev describe' to find the database credentials for this application. Successfully imported database for drupal8 ``` #### Non-Interactive Usage If you want to use the [`import-db`]() command without answering prompts, you can use the `--file` flag to provide the path to the import asset. If you’re importing an archive and wish to specify the path within the archive to extract, you can use the `--extract-path` flag in conjunction with the `--file` flag. Examples: ``` ddev import-db --file=/tmp/mydb.sql.gz gzip -dc /tmp/mydb.sql.gz | ddev import-db ddev import-db ` to import to a non-default database (other than the default `db` database). This will create the database if it doesn’t already exist. - Use `ddev import-db --no-drop` to import without first emptying the database. - If a database already exists and the import does not specify dropping tables, the contents of the imported dumpfile will be *added* to the database. Most full database dumps do a table drop and create before loading, but if yours does not, you can drop all tables with `ddev stop --remove-data` before importing. - If imports are stalling or failing, make sure you have plenty of unused space (see [\#3360]()). DDEV has no problems importing large (2G+) databases, but importing requires lots of space. DDEV will show a warning on startup if unused space is getting low. ## Inter-Project Communication ### Access another project’s database To connect the `web` container to the `db` container of a different project, use `ddev--db` as the hostname. Example: two projects, `backend` and `frontend`. In the `frontend` project: ``` ddev exec mysql -h ddev-backend-db ``` > [!NOTE] > > **Database host reset after restart** > > DDEV manages certain [CMS settings](). If you change the database host in `.env`, it may reset to `db` after `ddev restart`. > > Disable this behavior by setting [`disable_settings_management`]() to `true` or running [`ddev config --disable-settings-management`](). ### Access another project via HTTP/S Example: two projects, `backend` and `frontend`. From v1.24.10+, DDEV supports direct HTTP/S calls between projects: ``` # From frontend web container to backend web container ddev exec curl https://backend.ddev.site # Or using internal Docker hostname ddev exec curl https://ddev-backend-web ``` > [!NOTE] > > **Using HTTPS from a non-web container** > > HTTP works without extra setup. For HTTPS, the calling container must trust the `ddev-webserver` certificate authority (CA). > > ``` > $ ddev exec -s some-service curl https://backend.ddev.site > curl: (60) SSL certificate problem: unable to get local issuer certificate > More details: https://curl.se/docs/sslcerts.html > ``` > > See [Third-party services may need to trust `ddev-webserver`](). ### Access another service in the same or different project - **Same project**: containers reach each other by service name. No project name prefix is required. - **Different project**: containers reach each other with `ddev--`. Example in a single project with `web` and `opensearch` services: ``` # From web container to opensearch ddev exec curl http://opensearch:9200 # From opensearch container to web ddev exec -s opensearch curl http://web ``` Example with two projects, `frontend` and `backend` (`backend` has `opensearch` service): ``` # From frontend web container to backend opensearch ddev exec curl http://ddev-backend-opensearch:9200 ``` ### Access services on the host machine To connect from a container to a service on the host, use `host.docker.internal` as the hostname. ``` # Test connection to host service on port 8000 ddev exec telnet host.docker.internal 8000 # Call API on host ddev exec curl http://host.docker.internal:8000/some/api/endpoint ``` > [!NOTE] > > **Host service binding** > > Services on the host must listen on all interfaces (`0.0.0.0`), not only `127.0.0.1` (`localhost`). # Special Network Configurations > [!TIP] > > **The networking configurations described here are not typical** > > Don’t spend time with this page unless you know you have a global TLS trust or proxy configuration issue. There are a few networking situations which occasionally cause trouble for some users, especially in corporate environments. These typically fall into two categories: 1. **TLS interception** (e.g., Zscaler, GlobalProtect) causes SSL verification errors in `docker pull` or inside containers. 2. **Proxy-only internet access**, which blocks Docker or container tools unless proxy settings are configured. In these situations, multiple layers may require configuration: - **Docker Engine**: Trust external registries - **Docker CLI**: Use system or explicit proxy settings - **Containers (e.g., DDEV web)**: Trust HTTPS inside the container - **Package Managers (e.g., apt)**: If used inside containers, may also need proxy/CA configuration ## Corporate Packet-inspection VPNs (including Zscaler and Global Protect) > [!NOTE] > > **Your IT Department or Vendor May Have Easier Ways to Solve These Problems** > > In many cases, your IT department or VPN vendor may be able to whitelist certain internet resources, and you may not have to do extensive configuration. Check with them (if possible) before you start making configuration changes. Packet-inspecting VPNs like **Zscaler**, **GlobalProtect**, and similar products intercept HTTPS traffic using a corporate-controlled TLS Certificate Authority (CA). These systems act as a “man-in-the-middle” proxy, decrypting and re-encrypting HTTPS traffic. As a result, systems and applications that are not explicitly configured to trust the corporate CA will experience SSL/TLS verification errors. This creates two separate problems in Docker-based workflows: | Layer | Problem | Solution | | --- | --- | --- | | Docker Engine | `docker pull` fails with certificate errors when connecting to Docker registries like `hub.docker.com` | Configure Docker Engine to trust the corporate CA | | Inside Containers | Tools like `curl` or `composer` inside containers fail to connect to the internet | Install the corporate CA in the container image | ### 🧩 Docker Engine SSL Trust (for `docker pull`) The Docker Provider itself must trust the corporate CA to pull images from remote registries. The method of adding this trust varies by platform and Docker engine. Often, though, the easiest way to solve this particular problem is for your IT department or VPN vendor to whitelist your registry (usually `registry-1.docker.io`) so you don’t have to deal with this problem in the first place. If you can’t do that, a variety of solutions are provided below. #### macOS - **Docker Desktop**, **Orbstack**, and **Rancher Desktop** automatically use the macOS system keychain, so you likely don’t need to configure SSL trust. - **Colima**: Colima copies `~/.docker/certs.d` into the VM at startup. To trust a CA, place it in `~/.docker/certs.d/` **before** starting Colima. - **Lima**: Copy the CA certificate into `/etc/docker/certs.d/` using `limactl shell default`. #### Windows Install the trusted certificate in the system: 1. Search “Settings” for “Manage Computer Certificates”, which runs `certlm`. 2. Navigate to “Certificates - Local Computer” -\> “Trusted Root Certification” -\> “Certificates”. 3. Right-click -\> “All tasks” -\> “Import” to import the `crt` file. 4. **Docker Desktop**: Uses the Windows system certificate store. If the CA is trusted by the system, Docker will trust it too. 5. **WSL2 with Docker Desktop**: Behaves like Docker Desktop (Windows trust store). 6. **WSL2 with `docker-ce`**: Requires manual installation of the CA cert as with native Linux. #### Linux For native Linux or WSL2 with `docker-ce`: ``` sudo mkdir -p /etc/docker/certs.d/ sudo cp mycorp-ca.crt /etc/docker/certs.d/ sudo systemctl restart docker ``` #### ✅ **Test Docker Engine Trust** To test: ``` docker pull alpine # ✅ Should succeed if CA is trusted # ❌ If not trusted: x509: certificate signed by unknown authority ``` If it works without SSL errors, the CA is trusted properly. See [Docker Engine certificate configuration]() for more background. --- ### 📦 Container-Level SSL Trust (for `curl`, `composer`, `Node.js`, etc.) Applications running inside containers do **not** inherit trust from the host system. If the container makes outbound HTTPS connections, you must install the corporate CA inside the container image. The standard approach: 1. Export the corporate CA certificate (`.crt`) as described in the section below. Use the name of your `crt` file, not `mycorp-ca.crt`. 2. Place the `.crt` file in both your `.ddev/web-build` and `.ddev/db-build` directories. 3. Add a `.ddev/web-build/pre.Dockerfile.vpn` and `.ddev/db-build/pre.Dockerfile.vpn` like this: ``` COPY mycorp-ca.crt /usr/local/share/ca-certificates/ RUN update-ca-certificates ``` > **Apply to all projects** > > Put these Dockerfiles in `$HOME/.ddev/web-build/` and `$HOME/.ddev/db-build/` to apply them to every project, see [Global Dockerfiles](). 4. If you’re using Node.js/npm make it trust both the DDEV `mkcert` CA and your corporate CA by combining the two into a single file and then making the environment variable `NODE_EXTRA_CA_CERTS` point to that file. Add a post-start hook to concatenate the required files into the needed `/usr/local/share/ca-certificates/node_ca_certs.pem`. For example, in a `.ddev/config.vpn.yaml` add `post-start` hook: ``` web_environment: - NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/node_ca_certs.pem hooks: post-start: - exec: "cat /mnt/ddev-global-cache/mkcert/rootCA.pem /usr/local/share/ca-certificates/mycorp-ca.crt > /usr/local/share/ca-certificates/node_ca_certs.pem" ``` > **Apply to all projects** > > The same `web_environment` and `hooks` can go in `$HOME/.ddev/global_config.yaml` to apply to every project, see [Global Hooks](). On machines that are not behind the proxy the certificate is missing and the `cat` would report a failed hook, so guard it: > > ``` > hooks: > post-start: > - exec: "if test -f /usr/local/share/ca-certificates/mycorp-ca.crt; then cat /mnt/ddev-global-cache/mkcert/rootCA.pem /usr/local/share/ca-certificates/mycorp-ca.crt > /usr/local/share/ca-certificates/node_ca_certs.pem; fi" > ``` 5. Run: ``` ddev restart ddev exec curl -I https://www.google.com # ✅ Expect: HTTP/2 200 # ❌ If not trusted: curl: (60) SSL certificate problem ddev npm install --verbose # ✅ Expect: Successful install # ❌ If not trusted: attempt 2 failed with SELF_SIGNED_CERT_IN_CHAIN ddev yarn install # ✅ Expect: Successful install # ❌ If not trusted: code: 'SELF_SIGNED_CERT_IN_CHAIN' ``` This method works across all OS platforms and all Docker providers, because you’re explicitly modifying the container’s certificate store. > [!NOTE] > > **Clear or recreate buildx contexts** > > During the testing process, we found that an old `docker buildx` context can make all of the `ddev start` builds fail in this situation. We had to remove buildx contexts (`docker buildx ls` and `docker buildx rm ` may help, or removing the `~/.docker/buildx` directory.) --- ### 🔍 Where to Get the Corporate CA Certificate #### Option 1: Ask Your IT Department Request the “TLS root certificate” or “SSL inspection CA” used by your company’s VPN or proxy. #### Option 2: Extract from Your Own System - **macOS**: Use Keychain Access to export the cert from the “System” keychain. - **Windows**: Use `certmgr.msc` and export the cert from “Trusted Root Certification Authorities”. - **Linux**: Locate and copy certs from `/etc/ssl/certs/` or use Firefox + `certutil`. You can also visit a site like `https://www.google.com` in Chrome or Firefox, inspect the certificate chain, and export the root CA manually ([example method]()). ### Converting Exported PEM or CER Files to CRT When you export a CA certificate from your system (e.g., Keychain Access on macOS or certmgr on Windows), the file may be saved with a `.pem` or `.cer` extension. These are usually the same format as `.crt`: a base64-encoded X.509 certificate. For Docker or Linux tools that expect a `.crt` file, you can simply rename the exported file: ``` mv your-cert.pem your-cert.crt # or mv your-cert.cer your-cert.crt ``` To confirm it is the correct format, the file should begin with: ``` -----BEGIN CERTIFICATE----- ``` If your file contains this header, renaming it to `.crt` is sufficient for use with Docker and container trust configurations. ### Corporate Packet-inspection VPN Resources - [Network Test Environments: Packet-Inspection VPN Simulation]() - [Adding Self-signed Registry Certs to Docker & Docker for Mac]() - [Docker Docs: How Do I Add TLS Certificates]() ### VPN Trust Summary by Provider | Provider | Engine Trust Needed? | Auto-Inherits Host Trust? | Notes | | --- | --- | --- | --- | | Docker Desktop | No | ✅ | Uses macOS/Windows system keychain | | Orbstack | No | ✅ | Fully integrated with macOS keychain | | Colima | Yes | 🚫 | Requires pre-start copy of certs | | Rancher Desktop | Yes (dockerd) | Partial | Depends on Lima config and backend | | WSL2 + docker-ce | Yes | 🚫 | Must configure trust inside WSL | ### Cloudflare WARP (Zero Trust) If you are using [Cloudflare WARP]() (Zero Trust), you might find that `*.ddev.site` domains (or other domains you might use that resolve to 127.0.0.1) do not resolve because WARP intercepts and routes all traffic through its network, bypassing your local loopback. To fix this, configure **Split Tunneling** in your Cloudflare dashboard (or local settings): 1. Log in to your Cloudflare Zero Trust dashboard. 2. Go to **Settings** \> **WARP Client** \> **Device Settings**. 3. Locate your profile and click **Edit**. 4. Under **Split Tunnels**, ensure the mode is set to **Exclude**. 5. Add the following **Selector** entries: **Domains:** - `*.ddev.site` - `ddev.site` - Any other domain you use with DDEV which resolves to `127.0.0.1` **IP Addresses:** - `127.0.0.1/8` (Localhost) - `172.16.0.0/12` (Standard Docker range) > [!NOTE] > > The `172.16.0.0/12` range might overlap with your internal corporate networks. If you lose access to corporate resources: > > - Identify the specific Docker ranges you need using a CIDR calculator and only exclude those. > - Or [reconfigure Docker’s default IP ranges]() to use a different range and exclude that one instead. ## Corporate or Internet Provider Proxy Some network environments, including some corporate networks, require a “proxy” system be used to access the outside network. In these environments, most systems do not have direct access to the public internet, but instead must use a configured proxy host to access the public internet. A proxy is a system that receives HTTP and HTTPS traffic and then sends and receives traffic on behalf of the client that requests it. In most environments, the proxy will be configured at a system level. For example, on macOS, it can be configured at `Settings -> Wi-Fi -> Connection -> Details -> Proxies`. On Ubuntu it’s at `Settings -> Network -> Network Proxy`. In each of these situations the configuration required is essentially this: - HTTP Proxy or “Web Proxy (HTTP)” - HTTPS Proxy or “Secure web proxy (HTTPS)” - “Ignore Hosts” or “Bypass proxy settings for these hosts” Given a proxy with the hostname `yourproxy.intranet` with the IP address `192.168.1.254` and a port of `8888`, you would usually configure the HTTP and HTTPS Proxies as `yourproxy.intranet` with port `8888`. But it’s usually important to tell your system *not* to proxy some hostnames and IP addresses, including `localhost`, `*.ddev.site`, `127.0.0.1`, and `::1`. These exclusions ensure that local development domains (such as `*.ddev.site`) and local network addresses (`127.0.0.1`, `::1`) are not mistakenly routed through the proxy, which could disrupt DDEV’s functionality. System configuration in many systems results in environment variables like these examples: - `HTTP_PROXY=http://yourproxy.intranet:8888` - `HTTPS_PROXY=http://yourproxy.intranet:8888` - `NO_PROXY=localhost,127.0.0.1,::1,*.ddev.site` If they are not set automatically, they can be set manually in your `.bash_profile` or similar configuration file. ### Configuring Docker Daemon for Proxy See the [Docker documentation]() to configure the Docker daemon for access to the Docker registry (for actions like `docker pull`). For example, `/etc/docker/daemon.json` might be: ``` { "proxies": { "http-proxy": "http://squid.host-only:3128", "https-proxy": "http://squid.host-only:3128", "no-proxy": "localhost,127.0.0.0/8" } } ``` After configuration, ``` sudo systemctl restart docker ``` When this is working, you should be able to successfully `docker pull alpine`. ### Configuring Docker Client for Proxy See the [Docker documentation]() for how to configure the Docker client to pass proxy variables during container build (`ddev start`) and runtime (`ddev exec`). For example, `~/.docker/config.json` might be: ``` { "proxies": { "default": { "httpProxy": "http://squid.host-only:3128", "httpsProxy": "http://squid.host-only:3128", "noProxy": "localhost,127.0.0.0/8" } } } ``` After configuration, restart the DDEV project if it is already running. ### Proxy Resources - [HTTP Proxy Test Environments]() - [Configuring Rancher Desktop Proxy]() - [Colima proxy setup]() - [Linux Docker Daemon Proxy Configuration]() - [Linux Docker Client Proxy Configuration]() - [Colima Proxy Configuration]() ## WSL2 VirtioProxy Mode (Netskope and Similar VPNs) Some corporate VPNs, notably **Netskope**, intercept network traffic in a way that is incompatible with the default WSL2 NAT networking mode. The WSL2 `VirtioProxy` networking mode can work around this, and is the primary reason users end up in VirtioProxy mode. VirtioProxy mode is experimental and poorly documented by Microsoft. On some machines it works well; on others, the WSL2 distro has no internet access at all, which means tools like Composer and npm will not function. DDEV treats this mode as best-effort. See the [WSL2 VirtioProxy system requirements]() for setup instructions. **Known limitations of VirtioProxy mode:** - On some machines, the WSL2 distro has no internet access at all (Composer, npm, etc. will not work). - The WSL2 distro and Docker containers cannot make outgoing connections to the Windows host. This means a Windows-side IDE cannot receive Xdebug connections. Use an IDE inside WSL2 via WSLg and set `ddev config global --xdebug-ide-location=wsl2`. - Accessing DDEV sites from a Windows browser works normally. If you are not required to use VirtioProxy, the [Traditional Windows]() approach (Docker Desktop with WSL2 backend) avoids these limitations entirely. ## Restrictive DNS servers, especially Fritz!Box (FritzBox) routers The normal use of DDEV involves project URLs (and hostnames) like `*.ddev.site`. So a project with the name `mytypo3` will have the default hostname `mytypo3.ddev.site` and the default URL `https://mytypo3.ddev.site`. The way this works is that `*.ddev.site` is a Domain Name System (DNS) entry which always resolves to `127.0.0.1`, or `localhost`. There are a few DNS servers, mostly local Fritz!Box routers, which do not allow a DNS lookup to result in `127.0.0.1`. In this situation, DDEV will ask you to use superuser (`sudo`) privileges to add the hostname to the system `hosts` file, often `/etc/hosts` or `C:\Windows\system32\drivers\etc\hosts`. **This is not the preferred behavior, as DDEV does not want to edit your system files.** Instead, if DDEV is asking you to do this and add hostnames, it’s best to solve the underlying problem by adding configuration to the DNS server (often Fritz!Box router) or by using a less-restrictive DNS server like the Cloudflare `1.1.1.1` public DNS server. Full details are at [Fritz!Box Routers and DDEV](). These options are explained in the [Troubleshooting - DNS Rebinding]() section of the documentation. # Using DDEV Offline > [!WARNING] > > **Prepare all projects and pull Docker images before going offline** > > All necessary Docker images must be downloaded and built prior to working offline. > > Run the following commands: > > ``` > # Download all images for all projects > ddev utility download-images --all > > # Start all projects to ensure images are built > ddev start --all > > # Stop all projects > ddev poweroff > ``` DDEV attempts to work smoothly offline, and you shouldn’t have to do anything to make it work. However, it cannot pull needed Docker images when offline if a new Docker image is required, so you’ll want to make sure that you try a [`ddev start`]() before going offline to make sure everything has been pulled. `ddev start` builds project images every time. If that build fails and Docker Hub can’t be reached, DDEV warns and starts with the images from the last successful build, so changes to a Dockerfile made while offline take effect on the next online `ddev start`. When Docker Hub can be reached, a failed build stops `ddev start` as usual. If you have a project running when you’re online (using DNS for name resolution) and you then go offline, do a [`ddev restart`]() to get the hostname added into `/etc/hosts` for name resolution. You have some general options as well: In `.ddev/config.yaml`, [`use_dns_when_possible: false`]() will make DDEV never try to use DNS for resolution, instead adding hostnames to `/etc/hosts`. You can also use `ddev config --use-dns-when-possible=false` to set this configuration option. You can also set up a local DNS server like [dnsmasq]() (Linux and macOS, `brew install dnsmasq`) or ([unbound]() or many others on Windows) in your own host environment that serves the `project_tld` that you choose, and DNS resolution will work fine. You’ll likely want a wildcard A record pointing to `127.0.0.1` on most DDEV installations. If you use dnsmasq, you must configure it to allow DNS rebinding. To understand what’s going on with name resolution and your browser and offline use, we recommend [Hostnames and Wildcards and DDEV Oh My! on ddev.com](). > [!NOTE] > > **Administrative Privileges Required** > > If you `ddev start` when offline, DDEV will try to add `.ddev.site` to your `/etc/hosts` file. You must have administrative privileges to edit the hosts file on any operating system. > [!NOTE] > > **Oddities of using `buildx` `docker-container` driver** > > This is an unusual situation, mostly encountered by DDEV developers who have been pushing images, but you may not be able to work offline if your `docker buildx inspect` shows the `docker-container` driver, and you’ll need to switch to a builder that has the `docker` driver. `docker buildx ls` will show available drivers, or you can switch to one with `docker buildx create --name docker-driver --driver docker --use`. This is reported in [docker buildx issue](). # Troubleshooting Things might go wrong! In addition to this page, consider checking [the DDEV issue queue]() and [other support options](), as well as [Docker troubleshooting suggestions]() and [Mutagen troubleshooting suggestions](). ## General Troubleshooting Strategies - Please use the [current stable version of DDEV]() and of your Docker provider before going too far or asking for support. - Run [`ddev utility diagnose`]() to get a quick assessment of your DDEV installation and current project. This provides concise, actionable diagnostics for common issues. - Start by running [`ddev poweroff`]() to make sure all containers can start fresh. - Try a trivial project to see if the problem is something about your project or if it’s something about DDEV and your environment. See the simple instructions at [ddev/trivial-project](). - Temporarily disable firewalls, VPNs, tunnels, network proxies, and virus checkers while you’re troubleshooting. - Temporarily disable any proxies you’ve established in Docker’s settings. - Check to see if you’re out of disk space. On macOS, make sure that your Docker provider has adequate disk space allocated. (DDEV will normally warn you about problems in this situation.) - On macOS, if you have a particular heavyweight project or are encountering `kill` statements in `ddev logs`, increase your memory allocation in your Docker provider. (Most projects are fine with 5-6GB allocated.) - On macOS your Docker provider limits the amount of disk space available to Docker. Make sure that you increase it if you’re seeing disk space problems. - Please make sure that your project is in a subdirectory of your home directory and has normal ownership and privileges. For example, `ls -ld .` in your project directory should show you as owner of the directory and with write privileges. - Use [`ddev utility dockercheck`]() and [`ddev utility test`]() to help sort out Docker problems. - Make sure you do not have disk space problems on your computer. This can be especially tricky on WSL2, where you need to check both the main Windows disk space and WSL2 disk space as well. - On macOS, check to make sure your Docker provider is not out of disk space. In *Settings* (or *Preferences*) → *Resources* → *Disk image size* there should be ample space left; try not to let usage exceed 80% because the reported number can be unreliable. If it says zero used, something is wrong. - If you have customizations like PHP overrides, nginx or Apache overrides, MySQL/PostgreSQL overrides, custom services, or `config.yaml` changes, please back them out while troubleshooting, see [`ddev utility check-custom-config`](). It’s important to have the simplest possible environment while troubleshooting. - Restart Docker. Consider a Docker factory reset in serious cases, which will destroy any databases you’ve loaded. See [Docker Troubleshooting]() for more. - Try the simplest possible DDEV project (like [`ddev utility test`]() does): ``` ddev poweroff mkdir ~/tmp/testddev cd ~/tmp/testddev ddev config --auto printf " index.php ddev start ``` If that starts up fine, there may be an issue specifically with the project you’re trying to start. > [!TIP] > > **Using DDEV with Other Development Environments** > > DDEV uses your system’s port 80 and 443 by default when projects are running. If you’re using another local development environment (like Lando or Docksal or a native setup), you can either stop the other environment or configure DDEV to use different ports. See [troubleshooting]() for more detailed problem-solving. It’s easiest to stop the other environment when you want to use DDEV, and stop DDEV when you want to use the other environment. ### Debug Environment Variables Two environment variables meant for DDEV development may also be useful for broader troubleshooting: `DDEV_DEBUG` and `DDEV_VERBOSE`. When enabled, they’ll output more information when DDEV is executing a command. `DDEV_VERBOSE` can be particularly helpful debugging Dockerfile problems because it outputs complete information about the Dockerfile build stage within the `ddev start` command. You can set either one in your current session by running `export DDEV_DEBUG=true` and `export DDEV_VERBOSE=true`. Logs from a container that fails to start are printed with `DDEV_DEBUG=true` or `CI=true`. ## Front-End Development Tools ### Vite Issues If you’re experiencing issues with Vite development servers (like “Bad Gateway” errors, CORS problems, or HMR not working), see the comprehensive [Vite Integration troubleshooting section]() which covers: - Configuration of `web_extra_exposed_ports` - CORS and origin settings - Port conflicts and HMR issues - Asset loading problems ## Web Server Ports Already Occupied On `ddev start` you may see a message like this: ``` Port 443 is not available, using 30001 instead, see https://ddev.com/s/port-conflict ``` This means that DDEV has detected that it can’t use the expected port (`443`) in this example, usually because another application is using it. (It can also mean the port was occupied when the `ddev-router` container was created, and DDEV keeps using the substitute port until the router is recreated, for example by `ddev poweroff && ddev start`.) If this is OK, you don’t need to take any action. Most users want to use the default ports though (`80` and `443`) so you may want to figure out what the conflict is and solve it (usually by stopping the competing application). `ddev utility port-diagnose`, run from your project directory, can likely explain what’s going on. It checks each port your project needs, identifies the blocking process by name and PID, and suggests how to stop it: ``` $ ddev utility port-diagnose Port diagnostics for project: my-project Port 80 (router HTTP): IN USE by apache2 (PID 1234, cmd=/usr/sbin/apache2 -k start) [Linux] sudo systemctl stop apache2 && sudo systemctl disable apache2 Port 443 (router HTTPS): Available ``` On WSL2, it checks both Linux/WSL2 and Windows. If a port conflict is held by a root-owned process (such as `docker-proxy` under rootful Docker CE), the tool will ask for permission before running any `sudo` command, showing the exact command it intends to run. You can pre-approve this with `--allow-sudo`: ``` ddev utility port-diagnose --allow-sudo ``` The `--allow-sudo` flag is useful in scripts or when you know elevated detection will be needed and want to avoid the interactive prompt. When running non-interactively (CI, pipes) without `--allow-sudo`, sudo escalation is skipped. If port-diagnose doesn’t resolve it, the methods below explain how to stop the competing application or configure DDEV to use different ports. If you do get messages like: > ERROR: for ddev-router Cannot start service ddev-router: Ports are not available: listen tcp 127.0.0.1:XX: bind: An attempt was made to access a socket in a way forbidden by its access permissions. or > Error response from daemon: Ports are not available: exposing port TCP 127.0.0.1:8025 -\> 0.0.0.0:0: listen tcp 127.0.0.1:8025: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. it means some other process is using a needed port. Stop all Docker containers that might be using the port by running `ddev poweroff && docker rm -f $(docker ps -aq)`, then restart Docker. ### Method 1: Stop the competing application Consider `lando poweroff` for Lando, or `fin system stop` for Docksal, or stop MAMP using GUI interface or [`stop.sh`](). Probably the most common conflicting application is Apache running locally. It can often be stopped gracefully (but temporarily) with: ``` sudo apachectl stop ``` or ``` sudo systemctl stop apache2 && sudo systemctl disable apache2 ``` **Common tools that use port 80 and port 443:** Most people will want to use ports 80 and 443, the default HTTP and HTTPS ports for their projects, and these work fine whenever some other process is not using them. All of the DDEV projects on a given computer can use ports 80 and 443 at the same time. However, if you are not getting the default ports, here are some of the other common processes that could be using ports 80/443 and methods to stop them. - macOS content filtering: Under “Screen Time” → “Choose Screen Time content and privacy settings”, turn off “Content and Privacy” and then reboot. This has been a common issue with macOS Sonoma. - macOS or Linux Homebrew: Look for active processes by running `brew services` and temporarily running `brew services stop` individually to see if it has any impact on the conflict. - MAMP (macOS): Stop MAMP. - Apache: Temporarily stop with `sudo apachectl stop`, permanent stop depends on your environment. On Debian/Ubuntu: `sudo systemctl disable apache2 && sudo systemctl stop apache2` - nginx (macOS Homebrew): `sudo brew services stop nginx` or `sudo launchctl stop homebrew.mxcl.nginx`. - nginx (Ubuntu): `sudo systemctl stop nginx` or `sudo service nginx stop`. - VPNKit (macOS): You likely have a Docker container bound to port 80. Do you have containers up for Lando or another Docker-based development environment? If so, stop the other environment. - Lando: If you’ve previously used Lando, try running `lando poweroff`. - IIS on Windows (can affect WSL2). You’ll have to disable it in the Windows settings. You can also probe a port with `curl` to get a hint about which application is responding: `curl -I localhost` or `curl -I -k https://localhost:443`. ### Method 2: Fix port conflicts by configuring your project to use different ports To configure DDEV to use non-conflicting ports, remove router port configuration from the project and set it globally to different values. This will work for most people: ``` ddev config global --router-http-port=8080 --router-https-port=8443 ddev config --router-http-port="" --router-https-port="" ddev restart ``` This changes all projects’ HTTP URLs to `http://yoursite.ddev.site:8080` and the HTTPS URLs to `https://yoursite.ddev.site:8443`. We welcome your [suggestions]() based on other issues you’ve run into and your troubleshooting technique. ### Security Software Interfering with Port Detection If you see messages like: ``` Unable to check port availability Assuming ports are available, see https://ddev.com/s/port-conflict ``` DDEV will rely on Docker to report actual port conflicts. This can happen when security software intercepts localhost traffic. ### Debugging Port Issues on WSL2 On WSL2 the port may be occupied either on the traditional Windows side or within the WSL2 distro. `ddev utility port-diagnose` checks both sides automatically. The ports are shared between Windows and WSL2, so a conflict on either side blocks DDEV. ## Database Container Fails to Start Use `ddev logs -s db` to see what’s wrong. The most common cause of the database container not coming up is changing the database type or version in the project configuration, so the database server daemon is unable to start using an existing configuration for a different type or version. To solve this: - Change the configuration in `.ddev/config.yaml` back to the original configuration. - Export the database with [`ddev export-db`](). - Delete the project with [`ddev delete`](), or remove just the database volume with [`ddev utility delete-volume database`](), which stops the project first if it’s running. - Update `.ddev/config.yaml` to use the new [database type or version](). - Start the project and import the database from your export. ## “web service unhealthy” or “web service starting” or Exited Use `ddev logs` to see what’s wrong. The most common cause of the web container being unhealthy is a user-defined `.ddev/nginx-full/nginx-site.conf` or `.ddev/apache/apache-site.conf`. Please rename these to `` during testing. To figure out what’s wrong with it after you’ve identified that as the problem, use `ddev logs` and review the error. Changes to `.ddev/nginx-site.conf` and `.ddev/apache/apache-site.conf` take effect only when you do a `ddev restart` or the equivalent. ## No Input File Specified (404) or Forbidden (403) If you get a 404 with “No input file specified” (nginx) or a 403 with “Forbidden” (Apache) when you visit your project, it usually means that no `index.php` or `index.html` is being found in the docroot. This can result from: - Misconfigured docroot: If the docroot isn’t where the web server thinks it is, then the web server won’t find `index.php`. Look at your `.ddev/config.yaml` to verify it has a docroot containing `index.php`. It should be a relative path. - Missing `index.php`: There may not be an `index.php` or `index.html` in your project. `ddev logs` shows the exact reason the web server gave for a 403. To browse a directory that has no index file at all, turn directory listing on yourself, with `Options +Indexes` in an `.htaccess` file (Apache) or `autoindex on;` in a `.ddev/nginx/*.conf` file (nginx). ## `ddev start` Fails and Logs Contain “failed (28: No space left on device)” - Docker File Space If `ddev start` fails, it’s most often because the `web` or `db` container fails to start. In this case, the error message from `ddev start` says something like “Failed to start : db container failed: log=, err=container exited, please use ‘ddev logs -s db’ to find out why it failed”. You can`ddev logs -s db` to find out what happened. If you see any variant of “no space left on device” in the logs when using Docker Desktop, it means you have to increase or clean up Docker’s file space. Increase the “Disk image size” setting under “Resources” in Docker’s Preferences: ![Docker disk space]() If you see “no space left on device” on Linux, it most likely means your filesystem is full. ## `ddev start` Fails with “container failed to become ready” A container fails to become ready when its health check is failing. This can happen to any of the containers, and you can usually find the issue with a `docker inspect` command. > [!TIP] > > You may need to install [jq]() for these examples (`brew install jq`), or remove the `| jq` from the command and read the raw JSON output. For the `web` container: ``` docker inspect --format "{{json .State.Health }}" ddev--web | jq ``` For `ddev-router`: ``` docker inspect --format "{{json .State.Health }}" ddev-router ``` For `ddev-ssh-agent`: ``` docker inspect --format "{{json .State.Health }}" ddev-ssh-agent ``` Don’t forget to check logs using `ddev logs` for the `web` container, and `ddev logs -s db` for the `db` container! For `ddev-router` and `ddev-ssh-agent`: `docker logs ddev-router` and `docker logs ddev-ssh-agent`. ## `ddev start` Fails with “Failed to start \[project name\]: No such container: ddev-router” Deleting the images and re-pulling them generally solves this problem. Try running the following commands from the host machine: ``` ddev poweroff docker rm -f $(docker ps -aq) docker rmi -f $(docker images -q) ``` You should then be able to start your DDEV machine. ## Waiting for Another DDEV Process DDEV commands take turns while they set up what all projects share: the `ddev-router`, the `ddev-ssh-agent`, and the `ddev_default` network. If another `ddev` command is in the middle of that, you’ll see something like: ``` Waiting for 'ddev start' in ~/workspace/d11 (pid 41233, running 3s), which is doing ddev-router setup. Press Ctrl-C to cancel. ``` This usually clears within seconds. The other command may be running in another terminal, an IDE integration, a script, or a hook. If it doesn’t clear: - Look at the process with `ps -p 41233` (on Windows, `tasklist /fi "PID eq 41233"`), and stop it if it’s stuck. - Press Ctrl-C to cancel the waiting command; that is safe. - After two minutes DDEV gives up waiting and continues anyway, warning that the two commands may conflict, and doesn’t wait on that process again for the rest of the command. If something then fails, run `ddev poweroff` and try again. The lock is the file `~/.ddev/.global.lock`, which the operating system releases when the process holding it exits, so you never need to delete it. ## `ddev --version` shows an old version If you have installed or upgraded DDEV to the latest version, but when you check the actual version with `ddev --version`, it shows an older version, please refer to [Why do I have an old DDEV?]() ## Trouble Building Dockerfiles The additional `.ddev/web-build/Dockerfile` capability in DDEV is wonderful, but it can be hard to figure out what to put in there. The best approach for any significant Dockerfile is to `ddev ssh` and `sudo -s` and then one at a time, do the things that you plan to do with a `RUN` command in the Dockerfile. For example, if your Dockerfile were ``` RUN npm install --global forever ``` You could test it with `ddev ssh`, `sudo -s`, and then `npm install --global forever`. The error messages you get will be more informative than messages that come when the Dockerfile is processed. You can also see the output from the full Docker build using either: ``` ddev utility rebuild ``` or (see [global configuration directory]() for `docker-compose` location): ``` $HOME/.ddev/bin/docker-compose -f .ddev/.ddev-docker-compose-full.yaml --progress=plain build --no-cache ``` ### Docker build fails `apt-get update`, perhaps “SSL certificate problem: self-signed certificate” The Docker build environment (where all projects have a little bit happening) is very sensitive to problems with `apt-get update` or with TLS certificate authentication. If you ware seeing problems with `apt-get update` failing, some of these strategies may help: - **WSL2**: On WSL2 it’s a known issue that the WSL2 environment time can get out of sync with the real time. This is an [ongoing problem]() with WSL2, and can be fixed with various workarounds. One good workaround is to install `ntpdate` and `sudo ntpdate pool.ntp.org` to sync the time. The time in WSL2 can get out of sync due to laptop sleeping or other causes. A reboot also fixes it. - **VPN**: If you are on a packet-inspection VPN, it often causes problems with validation of certificates on internet sites. In that situation you’ll need to get the CA updates required and install them with a custom Dockerfile, as described on [Stack Overflow](). - **Other Docker Build**: The Dockerfile build environment is different from the host-side build and different from what you get with `ddev ssh`. If you’re having trouble with it it may be caused by name resolution or IP connectivity problems, most often caused by a firewall or VPN. Turn off your firewall temporarily and VPN. A good debugging technique would be to do a simple `.ddev/web-build/Dockerfile` that does `RUN curl -I https://www.google.com` and then use `ddev utility rebuild` to see the result. If it gets a 200 result, then your name resolution and internet connectivity are working in the Docker build environment. ## DDEV Starts but Browser Can’t Access URL You may see one of these messages in your browser: - `403` Forbidden - *\[url\] server IP address could not be found* - *We can’t connect to the server at \[url\]* If you get the `403 Forbidden` it’s almost always because your [docroot is set wrong](). You should have something like `docroot: web` or `docroot: ""` or `docroot: docroot` with the relative path to the directory where your `index.php` lives in the project. **Name resolution**: Most people use `*.ddev.site` URLs, which work great most of the time but require internet access. `*.ddev.site` is a wildcard DNS entry that always returns the IP address 127.0.0.1 (localhost). If you’re not connected to the internet, however, or if various other name resolution issues fail, this name resolution won’t work. While DDEV can create a web server and a Docker network infrastructure for a project, it doesn’t have control of your computer’s name resolution, so its backup technique to make a hostname resolvable by the browser is to add an entry to the hosts file (`/etc/hosts` on Linux and macOS, `C:\Windows\system32\drivers\etc\hosts` on traditional Windows). - If you’re not connected to the internet, your browser will not be able to look up `*.ddev.site` hostnames. DDEV works fine offline, but for your browser to look up names they’ll have to be resolved in a different way. - DDEV assumes that hostnames can be resolved within 3 seconds. That assumption is not valid on all networks or computers, so you can increase the amount of time it waits for resolution. Increasing to 5 seconds, for example: `ddev config global --internet-detection-timeout-ms=5000`. - If DDEV detects that it can’t look up one of the hostnames assigned to your project for that or other reasons, it will try to add that to the hosts file on your computer, which requires administrative privileges (sudo or Windows UAC). - This technique may not work on Windows WSL2, see below. ### Router Ports Not Reachable If `ddev start` warns that nothing answers on the router’s host ports, the router is running but your Docker provider isn’t forwarding its ports to your computer. Restarting the Docker provider often fixes it. With Lima or Colima on Linux, ports below 1024, such as 80 and 443, can’t be forwarded unless your system allows it. Either allow them with `echo 'net.ipv4.ip_unprivileged_port_start=0' | sudo tee -a /etc/sysctl.d/60-rootless.conf && sudo sysctl --system` and restart Lima or Colima, or move the router to higher ports with `ddev config global --router-http-port=8080 --router-https-port=8443`. ### DNS Rebinding Prohibited (Mostly on Fritzbox Routers) You may see one of several messages: - *Cannot resolve* - *unknown host* - *No address associated with hostname* Some DNS servers prevent the use of DNS records that resolve to `localhost` (127.0.0.1) because in uncontrolled environments this may be used as a form of attack called [DNS Rebinding](). Since `*.ddev.site` resolves to 127.0.0.1, they may refuse to resolve, and your browser may be unable to look up a hostname, and give you messages like “ server IP address could not be found” or “We can’t connect to the server at ”. You verify this is your problem by running `ping -c 1 dkkd.ddev.site`. If you get “No address associated with hostname” or something of that type, your computer is unable to look up `*.ddev.site`. In this case, you can take any one of the following approaches: 1. Reconfigure your router to allow DNS Rebinding. Many Fritz!Box routers have added default DNS Rebinding disallowal, and they can be reconfigured to allow it. Easy instructions are at [Fritz!Box Routers and DDEV](). If you have the local dnsmasq DNS server it may also be configured to disallow DNS rebinding, but it’s a simple change to a configuration directive to allow it. 2. Most computers can use more relaxed DNS resolution if they are not on corporate intranets that have non-internet DNS. So for example, the computer can be set to use 8.8.8.8 (Google) or 1.1.1.1 (Cloudflare) for DNS name resolution, see [this article](). 3. If you have control of the router, you can usually change its DHCP settings to choose a public, relaxed DNS server as in [\#2]( "GitHub Issue: ddev/ddev #2"). 4. You can live with DDEV trying to edit the `/etc/hosts` file, which it only has to do when a new name is added to a project. An extensive discussion of this class of problem is on [ddev.com](). ## Browser Shows Certificate Error for DDEV Sites If your browser shows a certificate warning such as `NET::ERR_CERT_AUTHORITY_INVALID`, `SEC_ERROR_UNKNOWN_ISSUER`, or “Your connection is not private” when visiting a DDEV HTTPS URL, the mkcert certificate authority (CA) is not trusted by your browser or OS. Run [`ddev utility tls-diagnose`]() to identify the specific problem: ``` ddev utility tls-diagnose ``` The command checks mkcert installation, OS trust store, certificate files, and live HTTPS connectivity, and prints actionable fix instructions for each issue it finds. **Common causes and fixes:** - **mkcert CA not installed in OS trust store** — Run `mkcert -install` and restart your browser. - **Firefox on Windows** — Firefox on Windows does not use the Windows certificate store. You must import the mkcert CA manually: Firefox Settings → Privacy & Security → View Certificates → Authorities → Import. Select `rootCA.pem` from the path shown by `mkcert -CAROOT`. Firefox Nightly, Developer Edition, and ESR each maintain a separate trust store and need the same treatment. On macOS and Linux, standard Firefox works via the OS trust store after `mkcert -install`; only special builds (Flatpak, snap, Nightly, Developer Edition) may need a manual import. - **Linux: `certutil` not installed** — Firefox on Linux relies on `certutil` (from `libnss3-tools`) for `mkcert -install` to register the CA. Install it with `sudo apt install libnss3-tools` (or `brew install nss`), then run `mkcert -install` again. - **Expired or CA-rotated certificates** — Run `ddev poweroff && ddev start` to regenerate certificates, or the nuclear option: `ddev poweroff && mkcert -uninstall && rm -rf "$(mkcert -CAROOT)" && mkcert -install && ddev start`. **On WSL2:** The mkcert CA is installed on Windows using `mkcert -install` in PowerShell. WSL2 then points its `$CAROOT` at the Windows mkcert directory so both sides use the same CA. `ddev utility tls-diagnose` checks all the WSL2-specific requirements: - `$CAROOT` must point to the Windows mkcert directory (e.g. `/mnt/c/Users//AppData/Local/mkcert`) - `CAROOT` must be listed in `$WSLENV` so it propagates into WSL2 sessions - The mkcert CA must be installed in the Windows certificate store (via `mkcert -install` in PowerShell) If any of these are missing, follow the setup instructions in [WSL2 and Certificates](). ## Windows WSL2 Network Issues - If you’re using a browser on Windows, accessing a project in WSL2, you can end up with confusing results when your project is listening on a port inside WSL2 while a Windows process is listening on that same port. The way to sort this out is to stop your project inside WSL2, verify that nothing is listening on the port there, and then study the port on the Windows side by visiting it with a browser or using other tools as described above. ## Limitations on Symbolic Links (symlinks) Symbolic links are widely used but have specific limitations in many environments beyond DDEV. Here are some of the ways those may affect you: - **Crossing mount boundaries**: Symlinks may not generally cross between network mounts. In other words, if you have a relative symlink in the root of your project directory on the host that points to `../somefile.txt`, that symlink will not be valid inside the container where `../` is a completely different filesystem (and is typically not mounted). - **Symlinks to absolute paths**: If you have an absolute symlink to something like `/Users/xxx/somefile.txt` on the host, it will not be resolvable inside the container because `/Users` is not mounted there. Some tools, especially on Magento 2, may create symlinks to rooted paths, with targets like `/var/www/html/path/to/something`. These basically can’t make it to the host and may create errors. - **Windows restrictions on symlinks**: Inside the Docker container on Windows, you may not be able to create a symlink that goes outside the container. - **Mutagen restrictions on Windows symlinks**: On macOS and Linux (including WSL2) the default `.ddev/mutagen/mutagen.yml` chooses the `posix-raw` type of symlink handling. (See [Mutagen docs]()). This basically means that any symlink created will try to sync, regardless of whether it’s valid in the other environment. However, Mutagen does not support posix-raw on traditional Windows, so DDEV uses the `portable` symlink mode. So on Windows with Mutagen, symlinks have to be strictly limited to relative links that are inside the Mutagen section of the project. ### Delete and Re-Download Docker Images In a few unusual cases, the actual downloaded Docker images can somehow get corrupted. Deleting the images will force them to be re-downloaded or rebuilt. This does no harm, as everything is rebuilt, but running `ddev start` will take longer while it downloads needed resources: ``` ddev poweroff docker rm -f $(docker ps -aq) # Stop any other random containers that may be running docker rmi -f $(docker images -q) # You might have to repeat this to get rid of all images ``` ## “Your Docker install has only … available disk space” Weird and bad things happen when Docker or your host system run out of disk space. Here are some ways to solve the problem if you get the warning “Your Docker install has only … available disk space”: - **Everybody**: Often a `ddev delete images` and `docker builder prune` (or `docker buildx prune`) will free up plenty of space. Consider deleting all images with `docker rmi -f $(docker images -q)`; this does no damage, but the images will have to be re-downloaded. - **macOS and traditional Windows**: `ddev mutagen reset` in each project will free up space. - **Docker Desktop for Mac** allocates a specific amount of docker-related space, and you can increase it. Settings -\> Resources -\> Disk usage limit. - **Colima, Lima, Rancher Desktop**: Before changing anything, save away your databases with `ddev snapshot -a`. Then follow instructions in each of those projects to increase allocated disk space. If you have your databases saved, you can delete your current instance and recreate with new disk constraints, see [Docker Installation](). - **OrbStack and Linux including WSL2 with Docker CE**: You are probably out of disk space on the disk where Docker stores images and volumes. # Uninstalling DDEV A DDEV installation consists of: - The self-contained `ddev` binary. - Each project’s `.ddev` directory. - The global `$HOME/.ddev` directory where various global items are stored. (This directory can be [moved]() to another location.) - The global `$HOME/.ddev_mutagen_data_directory` directory where Mutagen sync data may be stored. - The associated Docker images, containers and volumes DDEV created. - Any entries in `/etc/hosts`. Please use [`ddev snapshot`]() or [`ddev export-db`]() to make backups of your databases before deleting projects or uninstalling. You can use [`ddev clean`]() to uninstall the vast majority of things DDEV has touched. For example, `ddev clean ` or `ddev clean --all`. To uninstall one project, run [`ddev delete `](). This removes any hostnames in `/etc/hosts` and removes your database. If you don’t want it to make a database backup/snapshot on the way down, include the `--omit-snapshot` option: `ddev delete --omit-snapshot `. To remove all DDEV-owned `/etc/hosts` entries: [`ddev hostname --remove-inactive`](). To remove the global `.ddev` directory: `rm -r $HOME/.ddev`. To remove the global `.ddev_mutagen_data_directory` directory: `ddev poweroff && rm -r $HOME/.ddev_mutagen_data_directory`. If you installed Docker only for DDEV and want to uninstall it with all containers and images, uninstall it for your version of Docker. Otherwise: - Remove Docker images from before the current DDEV release with [`ddev delete images`](). - Remove all DDEV Docker containers that might still exist: `docker rm $(docker ps -a | awk '/ddev/ { print $1 }')`. - Remove all DDEV Docker images that might exist: `docker rmi $(docker images | awk '/ddev/ {print $3}')`. - Remove all Docker images of any type (does no harm; they’ll be re-downloaded): `docker rmi -f $(docker images -q)`. - Remove any Docker volumes: `docker volume rm $(docker volume ls | awk '/ddev|-mariadb/ { print $2 }')`. To remove the `ddev` binary: - Find the binary location with `which -a ddev`, you may have several binaries installed in different ways. The output will give you a general idea of how you installed it. - On macOS or Linux with Homebrew, `brew uninstall ddev`. - For Linux or other simple installs, remove the binary. Example: `sudo rm /usr/local/bin/ddev`. For Linux installed via apt, `sudo apt-get remove ddev`. - On Windows, if you used the DDEV Windows installer, use the uninstall on the Start Menu or in the “Add or Remove Programs” section of Windows Settings. # Vite Integration [Vite]() is a popular front-end build tool that provides fast development server with Hot Module Replacement (HMR) and optimized production builds. DDEV supports Vite development workflows for various frameworks including Laravel, Vue.js, React, Svelte, and more. ## Quick Setup To use Vite with DDEV, you need to: 1. Configure DDEV to expose Vite’s port in `.ddev/config.yaml`: ``` web_extra_exposed_ports: - name: vite container_port: 5173 http_port: 5172 https_port: 5173 ``` 2. Configure Vite in your `vite.config.js`: ``` import { defineConfig } from 'vite' export default defineConfig({ // Your settings // ... // Adjust Vites dev server to work with DDEV // https://vitejs.dev/config/server-options.html server: { // Respond to all network requests host: "0.0.0.0", port: 5173, strictPort: true, // Defines the origin of the generated asset URLs during development, // this must be set to the Vite dev server URL and selected port. origin: `${process.env.DDEV_PRIMARY_URL_WITHOUT_PORT}:5173`, // Configure CORS securely for the Vite dev server to allow requests // from *.ddev.site domains, supports additional hostnames (via regex). // If you use another `project_tld`, adjust this value accordingly. cors: { origin: /https?:\/\/([A-Za-z0-9\-\.]+)?(\.ddev\.site)(?::\d+)?$/, }, }, }) ``` 3. Restart DDEV to apply configuration changes: ``` ddev restart ``` 4. Start Vite development server: ``` ddev npm run dev # or ddev yarn dev ``` Your Vite development server will be available at `https://yourproject.ddev.site:5173`. > [!NOTE] > > **HTTPS Configuration** > > This guide assumes your project runs on `https://`. If you are unable to access the HTTPS version of your project, refer to the [Configuring Browsers](). > [!TIP] > > **Custom TLD** > > If you use a custom [`project_tld`]() other than `ddev.site`, adjust the CORS configuration accordingly in your `vite.config.js`, or use this snippet: > > ``` > export default defineConfig({ > server: { > cors: { > origin: new RegExp( > `https?:\/\/(${process.env.DDEV_HOSTNAME.split(",") > .map((h) => h.replace("*", "[^.]+")) > .join("|")})(?::\\d+)?$` > ) > }, > }, > }); > ``` ## Example Projects Example implementations demonstrating Vite integration with DDEV: - **[Working with Vite in DDEV]()** - Basic PHP project with step-by-step setup guide - **[vite-php-setup]()** - General PHP + Vite integration (adapt `VITE_HOST` for DDEV) For additional integration patterns and framework-specific examples: - **[Vite Backend Integration Guide]()** - Official Vite documentation for backend frameworks - **[Vite Awesome List]()** - Community-maintained list of integrations ## Craft CMS The [Vite plugin by `nystudio107`]() provides official DDEV support with detailed [configuration instructions]() for `vite.config.js` and `config/vite.php`. > [!NOTE] > > **Port Configuration** > > When using `web_extra_exposed_ports` in `.ddev/config.yaml`, the `.ddev/docker-compose.*.yaml` file for port exposure is not required. Example implementations: - **[Craft CMS with Vite integration]()** - **[Craft CMS Starter]()** - **[How we use DDEV, Vite and Tailwind with Craft CMS]()** ## Drupal Several tools and modules are available for integrating Vite with Drupal: - **[Vite module]()** - Uses Vite’s manifest.json to map Drupal library files to compiled versions in `/dist` or to the Vite development server - **[UnoCSS Starter theme]()** - Drupal theme with Vite integration and DDEV setup instructions - **[Foxy]()** - Alternative asset bundling solution for Drupal Community resources: - **[UnoCSS Starter Theme Documentation]()** - **[Proof of concept for Vite bundling in Drupal]()** The Drupal community is actively developing Vite integration solutions for bundling assets across multiple modules and themes. ## Laravel Laravel adopted [Vite as the default asset bundler]() in v9.19, replacing Laravel Mix. Configure DDEV to expose Vite’s port in `.ddev/config.yaml`: ``` web_extra_exposed_ports: - name: vite container_port: 5173 http_port: 5172 https_port: 5173 ``` Configure Vite in your `vite.config.js`: ``` import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; export default defineConfig({ plugins: [ laravel({ input: [ 'resources/css/app.css', 'resources/js/app.js', ], refresh: true, }), ], server: { host: "0.0.0.0", port: 5173, strictPort: true, origin: `${process.env.DDEV_PRIMARY_URL_WITHOUT_PORT}:5173`, cors: { origin: /https?:\/\/([A-Za-z0-9\-\.]+)?(\.ddev\.site)(?::\d+)?$/, }, }, }); ``` Start the Vite development server: ``` ddev npm run dev ``` Example implementation: - **[ddev-laravel-vite]()** - Laravel with Vite integration > [!NOTE] > > **Laravel-Specific Integration** > > Laravel’s Vite integration uses a `public/hot` file to manage development server state through its npm integration. ## Node.js DDEV supports Node.js-only projects by proxying requests to the correct ports within the web container. This configuration enables running Node.js applications like Keystone CMS or SvelteKit entirely within DDEV. This approach supports various architectures: - **Monorepo setup**: Run a PHP backend with a Node.js frontend on separate subdomains within a single DDEV project - **Headless CMS**: Ideal for decoupled architectures combining traditional backends with modern JavaScript frameworks - **Multi-project setup**: Use separate DDEV projects for frontend and backend with [inter-project communication]() Additional resources: - **[How to Run Headless Drupal and Next.js on DDEV]()** - Headless CMS implementation guide - **[ddev-laravel-breeze-sveltekit]()** - Monorepo example with Laravel and SvelteKit ## TYPO3 Several tools are available for integrating Vite with TYPO3: - **[typo3-vite-demo]()** - Vite demo project - **[vite-asset-collector]()** - TYPO3 extension for Vite integration - **[vite-plugin-typo3]()** - Vite plugin for TYPO3 - **[ddev-vite-sidecar]()** - DDEV add-on for zero-config Vite integration The vite-asset-collector extension provides detailed [DDEV installation instructions](). For questions or support, join the Vite channel on [TYPO3 Slack](). ## WordPress Several libraries are available for integrating Vite with WordPress: - **[php-wordpress-vite-assets]()** - PHP library for loading Vite assets in WordPress - **[vite-for-wp]()** - WordPress integration for Vite - **[wp-vite-manifest]()** - Vite manifest loader for WordPress Example implementations: - **[ddev-wp-vite-demo]()** - Demo theme using php-wordpress-vite-assets - **[wp-vite-manifest usage guide]()** - Integration guide for themes and plugins - **[Integrating Vite and DDEV into WordPress]()** ## GitHub Codespaces DDEV supports Vite in GitHub Codespaces with alternative port configuration. Example implementations: - **[Laravel with Vite in Codespaces]()** - **[Craft CMS with Vite in Codespaces]()** > [!NOTE] > > **Alternative Port Configuration Required** > > The DDEV router is unavailable in Codespaces. Instead of using `web_extra_exposed_ports` in `.ddev/config.yaml`, create a `.ddev/docker-compose.vite-workaround.yaml` file: > > ``` > services: > web: > ports: > - 5173:5173 > ``` For additional Codespaces configuration details, see the [DDEV Codespaces documentation](). ## Auto-starting Vite You can configure DDEV to automatically start Vite when the project starts using [hooks](): Add to `.ddev/config.yaml`: ``` hooks: post-start: - exec: "npm run dev" ``` Or use a more robust daemon configuration (logs available via `ddev logs -s web`): ``` web_extra_daemons: - name: "vite" command: bash -c 'npm install && npm run dev -- --host' directory: /var/www/html ``` For a real-world daemon implementation example, see the [ddev.com repository]() configuration. ## Production Builds For production builds, ensure your `vite.config.js` includes proper manifest generation: ``` export default defineConfig({ build: { manifest: true, rollupOptions: { input: { main: 'path/to/your/main.js', } } }, // ... other configuration }) ``` Build for production: ``` ddev npm run build ``` ## DDEV Add-ons Several community add-ons simplify Vite integration: - **[ddev-vite-sidecar]()** - Zero-config Vite integration exposing the development server as a `https://vite.*` subdomain, eliminating the need to expose ports to the host system - **[ddev-vitest]()** - Helper commands for projects using [Vitest](), a Vite-native testing framework - **[ddev-viteserve]()** - First DDEV Vite add-on (no longer maintained, but pioneered the integration) Additional Vite-related add-ons are available in the [DDEV Add-on Registry](). ## Troubleshooting ### Bad Gateway Errors **Problem**: Getting “502 Bad Gateway” when accessing Vite URL. **Solutions**: 1. **Check port configuration**: Ensure `web_extra_exposed_ports` is correctly configured in `.ddev/config.yaml`. 2. **Verify Vite is running**: Check if Vite development server is actually running: ``` ddev logs -s web ``` 3. **Restart DDEV**: After changing configuration: ``` ddev restart ``` ### CORS Issues **Problem**: Browser console shows CORS errors. **Solutions**: 1. **Update CORS configuration** in `vite.config.js`: ``` export default defineConfig({ server: { cors: { origin: /https?:\/\/([A-Za-z0-9\-\.]+)?(\.ddev\.site)(?::\d+)?$/, }, }, }); ``` 2. **Check origin setting**: ``` export default defineConfig({ server: { origin: `${process.env.DDEV_PRIMARY_URL_WITHOUT_PORT}:5173`, }, }); ``` ### Port Already in Use **Problem**: “Port 5173 is already in use” error. **Solutions**: 1. **Use different port**: Change the port in both DDEV and Vite configurations: ``` # .ddev/config.yaml web_extra_exposed_ports: - name: vite container_port: 5174 http_port: 5172 https_port: 5174 ``` ``` // vite.config.js export default defineConfig({ server: { port: 5174, }, }); ``` 2. **Kill existing process**: ``` ddev exec "pkill -f vite" ``` ### Assets Not Loading **Problem**: CSS/JS assets not loading properly. **Solutions**: 1. **Verify base path** in production builds matches your web server configuration. 2. **Check manifest.json** is being generated and loaded correctly. 3. **Ensure proper asset URLs** in your templates/framework integration. ## Best Practices 1. **Use specific Node.js versions**: Specify `nodejs_version` in your DDEV configuration for consistency across team members. 2. **Include Vite in your project dependencies**: Don’t rely on global Vite installations. 3. **Configure proper `.gitignore`**: Exclude build artifacts: ``` /dist/ /build/ node_modules/ ``` 4. **Document your setup**: Include Vite configuration instructions in your project’s readme. 5. **Use environment variables**: Leverage `process.env.DDEV_PRIMARY_URL_WITHOUT_PORT` for dynamic configuration. # Support We love to hear from you and want you to be successful with DDEV! - See the included [`ddev help`]() command, which includes lots of examples. - [FAQ]() - [Discord]() interactive community support. - [DDEV issue queue]() for bugs and feature requests. - [Mastodon]() - [Bluesky]() # Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not only for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others’ private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at `support`(at)`ddev.com`. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1\. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2\. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3\. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4\. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant](), version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code\_of\_conduct.html](). Community Impact Guidelines were inspired by [Mozilla’s code of conduct enforcement ladder](). For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](). Translations are available at [https://www.contributor-covenant.org/translations](). # Configuration # Config Options DDEV configuration is stored in YAML files that come in two flavors: 1. **Project** `.ddev/config.yaml` settings, with optional [environmental override](<#environmental-overrides>) variants. 2. **Global** `$HOME/.ddev/global_config.yaml` settings that can apply to all projects. Most of these settings take effect when you run [`ddev start`](). ## Managing Configuration ### Setting Options You can hand-edit the YAML files DDEV creates for you after running [`ddev config`](), and you can also define most settings with equivalent CLI arguments: ``` php_version: "8.4" ``` ``` ddev config --php-version 8.4 ``` Run `ddev help config` to see all the available config arguments. ### Environmental Overrides You can override the per-project `config.yaml` with files named `config.*.yaml`, and files like this are often created by [DDEV add-ons](). For example, `config.python2.yaml` in [Python 2 add-on]() adds additional configuration related to Python 2. Many teams use `config.local.yaml` for configuration that is specific to one environment, and not checked into the team’s default `config.yaml`. For example, you might enable `performance_mode: mutagen` only on your machine. Or you could use a different database type. The file `config.local.yaml` is gitignored by default. The global `$HOME/.ddev/global_config.yaml` can be extended the same way with `global_config.*.yaml` files in `$HOME/.ddev`, which is useful for keeping machine-specific or team-distributed settings out of the file DDEV rewrites. For examples, see the [Extending and Customizing Environments]() page. --- ## `additional_fqdns` An array of [extra fully-qualified domain names]() to be used for a project. | Type | Default | Usage | | --- | --- | --- | | project | `[]` | ‌ | Example: `additional_fqdns: ["example.com", "sub1.example.com"]` would provide HTTP and HTTPS URLs for `example.com` and `sub1.example.com`. See [Hostnames and Wildcards and DDEV, Oh My!]() for more information on DDEV hostname resolution. > [!WARNING] > > Take care with `additional_fqdns`; it adds items to your `/etc/hosts` file which can cause confusion. ## `additional_hostnames` An array of [extra hostnames]() to be used for a project. | Type | Default | Usage | | --- | --- | --- | | project | `[]` | ‌ | Example: `additional_hostnames: ["somename", "someothername", "*.thirdname"]` would provide HTTP and HTTPS URLs for `somename.ddev.site`, `someothername.ddev.site`, and `one.thirdname.ddev.site` + `two.thirdname.ddev.site`. The wildcard (`*.`) setting only works if you’re [using DNS to resolve hostnames (default)](<#use_dns_when_possible>) and connected to the internet and using `ddev.site` as your [`project_tld`](<#project_tld>). See [Hostnames and Wildcards and DDEV, Oh My!]() for more information on DDEV hostname resolution. ## `bind_all_interfaces` When the network interfaces of a project should be exposed to the local network, you can specify `bind_all_interfaces: true` to do that. This is an unusual application, sometimes used to [share projects on a local network](). | Type | Default | Usage | | --- | --- | --- | | project | `false` | Can be `true` or `false`. | ## `composer_root` The relative path, from the project root, to the directory containing `composer.json`. (This is where all Composer-related commands are executed.) | Type | Default | Usage | | --- | --- | --- | | project | ‌ | ‌ | ## `composer_version` Composer version for the web container and the [`ddev composer`]() command. | Type | Default | Usage | | --- | --- | --- | | project | `2` | Can be `2` or empty (`""`) for latest major version at container build time.

Can also be the LTS version `2.2` (there’s no other `2.x` LTS), an explicit version like `2.9.3`, or a keyword like `stable`, `preview` or `snapshot`. See [Composer documentation](). | > [!WARNING] > > **Composer version is cached at container build time** > > DDEV installs Composer at container build time and caches it. If you use a non-specific version like `2`, `2.2`, `""` (empty), or `stable`, DDEV installs the latest available version in that range at build time. > > The next update of Composer will be done automatically when DDEV is upgraded. If you want to update Composer in the web container before then, [restart]() with `--no-cache` or [rebuild](): > > ``` > ddev restart --no-cache > # or > ddev utility rebuild > ``` > > See [Composer Self-Update Changes Do Not Persist]() for details. > [!TIP] > > **Using `vendor/bin/composer`** > > See the example in [Composer from `vendor/bin/composer`](). ## `corepack_enable` Whether to run `corepack enable` on Node.js configuration. | Type | Default | Usage | | --- | --- | --- | | project | `false` | Can be `true` or `false`. | When `true`, `corepack enable` will be executed, making latest `pnpm` and `yarn` package managers available. Node.js 25 and later no longer include `corepack`. When it is missing, DDEV installs it from npm while building the web image, so that build needs access to the npm registry. To set a specific version of `pnpm` for a project: ``` ddev config --corepack-enable && ddev restart # pnpm@latest requires Node.js 22+ # use pnpm@latest-10 for Node.js 18 or 20 ddev exec corepack use pnpm@latest ddev exec pnpm --version ``` To set a specific version of `yarn` for a project: ``` ddev config --corepack-enable && ddev restart # yarn@stable is the latest stable release # use yarn@4.9.1 to pin exactly ddev exec corepack use yarn@stable ddev yarn --version ``` `corepack use` pins the version by setting the `packageManager` field in `package.json`. To clear the global cache, run: ``` ddev exec corepack cache clear ``` ## `database` The type and version of the database engine the project should use. | Type | Default | Usage | | --- | --- | --- | | project | `mariadb:11.8` | The following database types are currently supported:
\- MariaDB 5.5-10.8, 10.11, 11.4, 11.8, 12.3
\- MySQL 5.5-8.0, 8.4, 9.7
\- Postgres 9-18 | See [Database Server Types]() for examples (e.g. `ddev config --database=mariadb:11.8`) and caveats (e.g. how to migrate from one database version to another). > [!TIP] > > **For very old database types, see [Using DDEV to spin up a legacy PHP application]().** ## `dbimage` > [!WARNING] > > **Proceed with caution** > > It’s unusual to change this option, and we don’t recommend it without Docker experience and a good reason. Typically, this means additions to the existing db image using a `.ddev/db-build/Dockerfile.*`. The Docker image to use for the database server. | Type | Default | Usage | | --- | --- | --- | | project | [`ddev/ddev-dbserver`]() | Specify your own image based on the [ddev-dbserver]() image matching your project’s `database` type and version. | DDEV images carry a `com.ddev.image-tag` label recording the image tag they were built as, and a derived image inherits it. When a pinned image was built from a different generation of DDEV images than the running DDEV expects, `ddev start` reports it as custom configuration, along with a note showing both tags. That’s the signal to rebuild your derived image against the current DDEV images. Images built before the label existed are not reported. See [DDEV Labels on Images]() for how this differs from `com.ddev.webtag`. ## `dbimage_extra_packages` Extra Debian packages for the project’s database container. (This is rarely used.) | Type | Default | Usage | | --- | --- | --- | | project | `[]` | ‌ | Example: `dbimage_extra_packages: [netcat, telnet, sudo]` will add the `netcat`, `telnet`, and `sudo` packages when the database container is built. ## `ddev_version_constraint` You can configure a [version constraint]() for DDEV that will be validated against the running DDEV executable and prevent `ddev start` from running if it doesn’t validate. For example: ``` ddev_version_constraint: '>= v1.24.0-alpha1' ``` | Type | Default | Usage | | --- | --- | --- | | project | | `>= 1.24.8` | ## `default_container_timeout` Seconds DDEV will wait for all containers to become ready. | Type | Default | Usage | | --- | --- | --- | | project | `120` | Can be adjusted to avoid timeouts on slower systems or for huge snapshot restores. | ## `developer_mode` Not currently used. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | ## `disable_settings_management` Whether to disable CMS-specific settings file management. | Type | Default | Usage | | --- | --- | --- | | project | `false` | Can be `true` or `false`. | When `true`, DDEV will not create or update CMS-specific settings files. ## `disable_upload_dirs_warning` Whether to disable the standard warning issued when a project is using `performance_mode: mutagen` but `upload_dirs` is not configured. | Type | Default | Usage | | --- | --- | --- | | project | `false` | Can be `true` or `false`. | When `true`, DDEV will not issue the normal warning on `ddev start`: “You have Mutagen enabled and your ‘php’ project type doesn’t have `upload_dirs` set”. See [Mutagen and User-Generated Uploads]() for context on why DDEV avoids doing the Mutagen sync on `upload_dirs`. ## `docker_buildx_version` > [!WARNING] > > **Advanced Usage** > > `docker_buildx_version` should only be changed for troubleshooting purposes or if you can’t install `docker-buildx` on your system. Controls which `docker-buildx` plugin DDEV uses. | Type | Default | Usage | | --- | --- | --- | | global | `system` | `system` or a version string like `0.33.0` | When set to `system` (the default), DDEV uses whatever `docker-buildx` is already installed on your system and never downloads anything. ## `docroot` Relative path to the document root containing `index.php` or `index.html`. | Type | Default | Usage | | --- | --- | --- | | project | automatic | DDEV will attempt to detect this and set it for you, otherwise falling back to the current directory. | ## `fail_on_hook_fail` Whether [`ddev start`]() should be interrupted by a failing [hook](), on a single project or for all projects if used globally. | Type | Default | Usage | | --- | --- | --- | | project
global | `false` | Can be `true` or `false`. | ## `hooks` DDEV-specific lifecycle [hooks]() to be executed. | Type | Default | Usage | | --- | --- | --- | | project
global | \`\` | Global hooks in `global_config.yaml` run for every project, see [Global Hooks](). | ## `host_db_port` Port for binding database server to localhost interface. | Type | Default | Usage | | --- | --- | --- | | project | automatic | ‌ | Not commonly used. Can be a specific port number for a fixed database port. If unset, the port will be assigned automatically and change each time [`ddev start`]() is run. Can be a specific port number for a fixed database port, which can be useful for configuration of host-side database clients. (May still be easier to use [`ddev mysql`](), `ddev psql`, `ddev sequelace`, etc., which handle changing ports automatically, as does the sample command `ddev mysqlworkbench`.) ## `host_https_port` Specific, persistent HTTPS port for direct binding to localhost interface. | Type | Default | Usage | | --- | --- | --- | | project | automatic | ‌ | Not commonly used. Can be a specific port number for a fixed HTTPS URL. If unset, the port will be assigned automatically and change each time [`ddev start`]() is run. Example: `59001` will have the project always use `https://127.0.0.1:59001` for the localhost URL—used less commonly than the named URL which is better to rely on. ## `host_mailpit_port` Specific, persistent Mailpit port for direct binding to localhost interface. | Type | Default | Usage | | --- | --- | --- | | project | automatic | ‌ | Not commonly used. Can be a specific port number for a fixed Mailpit URL. If unset, the port will be assigned automatically and change each time [`ddev start`]() is run. ## `host_webserver_port` Specific, persistent HTTP port for direct binding to localhost interface. | Type | Default | Usage | | --- | --- | --- | | project | automatic | ‌ | Not commonly used. Can be a specific port number for a fixed HTTP URL. If unset, the port will be assigned automatically and change each time [`ddev start`]() is run. Example: `59000` will have the project always use `http://127.0.0.1:59000` for the localhost URL—used less commonly than the named URL which is better to rely on. ## `host_xhgui_port` Specific, persistent XHGui port for direct binding to localhost interface. | Type | Default | Usage | | --- | --- | --- | | project | automatic | ‌ | Very rarely used. Can be a specific port number for a fixed XHGui URL. ## `instrumentation_opt_in` Whether to allow [instrumentation reporting](). | Type | Default | Usage | | --- | --- | --- | | global | `true` | Can be `true` or `false`. | When `true`, anonymous usage information is collected via [Amplitude](). ## `instrumentation_queue_size` Maximum number of locally collected events for [instrumentation reporting](). | Type | Default | Usage | | --- | --- | --- | | global | `100` | Can be any integer. | ## `instrumentation_reporting_interval` Reporting interval in hours for [instrumentation reporting](). | Type | Default | Usage | | --- | --- | --- | | global | `24` | Can be any integer. | ## `instrumentation_user` Specific name identifier for [instrumentation reporting](). | Type | Default | Usage | | --- | --- | --- | | global | \`\` | ‌ | ## `internet_detection_timeout_ms` Internet detection timeout in milliseconds. | Type | Default | Usage | | --- | --- | --- | | global | `3000` (3 seconds) | Can be any integer. | DDEV must detect whether the internet is working to determine whether to add hostnames to `/etc/hosts`. In rare cases, you may need to increase this value if you have slow but working internet. See [FAQ]() and [GitHub issue](). ## `letsencrypt_email` Email associated with Let’s Encrypt feature. (Works in conjunction with [`use_letsencrypt`](<#use_letsencrypt>).) | Type | Default | Usage | | --- | --- | --- | | global | \`\` | ‌ | Set with `ddev config global --letsencrypt-email=me@example.com`. Used with the [hosting]() feature. ## `mailpit_http_port` Port for project’s Mailpit HTTP URL. | Type | Default | Usage | | --- | --- | --- | | project
global | `8025` | Can be changed to avoid a port conflict. | ## `mailpit_https_port` Port for project’s Mailpit HTTPS URL. | Type | Default | Usage | | --- | --- | --- | | project
global | `8026` | Can be changed to avoid a port conflict. | ## `messages` Configure messages like the Tip of the Day. | Type | Default | Usage | | --- | --- | --- | | global | `ticker_interval:` | hours between ticker messages. | Example: Disable the “Tip of the Day” ticker in `$HOME/.ddev/global_config.yaml` (see [global configuration directory]()) ``` messages: ticker_interval: -1 ``` Example: Show the “Tip of the Day” ticket every two hours: ``` messages: ticker_interval: 2 ``` ## `name` The URL-friendly name DDEV should use to reference the project. | Type | Default | Usage | | --- | --- | --- | | project | enclosing directory name | Must be unique; no two projects can have the same name. It’s best if this matches the directory name. If this option is omitted, the project will take the name of the enclosing directory. This value may also be set via `ddev config --project-name=`. (The `ddev config` flag is `project-name`, not `name`, see [`ddev config` docs]().)” | ## `no_bind_mounts` Whether to not use Docker bind mounts. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | Some Docker environments (like remote Docker) do not allow bind mounts, so when `true` this turns those off, turns on Mutagen, and uses volume copies to do what bind mounts would otherwise do. ## `no_project_mount` Whether to skip mounting project into web container. | Type | Default | Usage | | --- | --- | --- | | project | `false` | Can be `true` or `false`. | > [!WARNING] > > **Advanced users only!** > > When `true`, project will not be mounted by DDEV into the web container. Enables experimentation with alternate file mounting strategies. ## `no_tui` Whether to disable the TUI (terminal user interface) when running the `ddev` command without any arguments. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | ## `nodejs_root` The relative path, from the project root, to the directory containing the Node.js version file. (Requires [`nodejs_version`](<#nodejs_version>) `auto` or `engine`.) | Type | Default | Usage | | --- | --- | --- | | project | ‌ | `nodejs_root: web/themes/custom/mytheme` | ## `nodejs_version` Node.js version for the web container, managed by [`n`](). | Type | Default | Usage | | --- | --- | --- | | project | current LTS version | Any [Node.js version](); see the table below for what each value does. | What DDEV does depends on the value: | Value | Result | | --- | --- | | `24` | Default version preinstalled in DDEV. | | `""` (empty) | Uses the version already included in the image, with no download step (fastest for CI). Follows DDEV default behavior over time. | | `v24` | Installs the newest `24.x` release when the image is built. Use the `v` prefix only to get the latest of the current default major. | | any other version (`22`, `20.2`, `18.19.2`, etc.) | Installs that version when the image is built. A bare major like `22` already installs the newest `22.x`. | | `auto`, `engine` | Reads the version from a file in your project (see below). | > [!WARNING] > > **The Node.js version changes only when the image is rebuilt** > > A version installed by `n` is fixed at build time. It changes only when the `ddev-webserver` image is rebuilt, for example when you upgrade DDEV. You can also trigger a rebuild manually: > > ``` > ddev restart --no-cache > # or > ddev utility rebuild > ``` > > Example: you set `22`, and `n` installs the newest `22.x` available at build time, for example `22.14.0`. If `22.14.1` is released before the next rebuild, your project stays on `22.14.0` until then. > [!TIP] > > **How to define the Node.js version using a file** > > Set `nodejs_version: auto` to read the version from a file in your project root ([`DDEV_APPROOT`]()). `n` checks these in order: `.n-node-version`, `.node-version`, `.nvmrc`, then the `engines.node` field in `package.json`. Set `nodejs_version: engine` to use only `engines.node`. Either value fails validation if no such file exists. See [n’s documentation]() for details. > > If your version file is not in your project root, point [`nodejs_root`](<#nodejs_root>) at its directory. For example, if you have `frontend/.nvmrc`: > > ``` > nodejs_root: frontend > nodejs_version: auto > ``` > [!TIP] > > **Installing additional Node.js versions** > > To have multiple Node.js versions available, install additional ones via a [`post-start` hook]() using `n`: > > ``` > hooks: > post-start: > - exec: "n install 18 && n install 20" > ``` > > All installed versions are cached; the last one becomes active. Switch with `ddev exec n `, clean up with `ddev exec n prune`. ## `omit_containers` Containers that should not be loaded automatically for one or more projects. | Type | Default | Usage | | --- | --- | --- | | project
global | `[]` | **For projects**, can include `db`, and `ddev-ssh-agent`.
**Globally**, can include `ddev-router`, and `ddev-ssh-agent`. | Example: `omit_containers: [db, ddev-ssh-agent]` starts the project without a `db` container and SSH agent. Some containers can be omitted globally in `$HOME/.ddev/global_config.yaml` (see [global configuration directory]()) and the result is additive; all containers named in both places will be omitted. > [!WARNING] > > Omitting the `db` container will cause database-dependent DDEV features to be unstable. ## `omit_project_name_by_default` Determines whether `ddev config` updates the `name` field in the `.ddev/config.yaml` by default. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | When the `name` field is omitted in `.ddev/config.yaml`, DDEV gets the project name from the directory the project is in. If this option is set to `true`, `ddev config` will not update the `name` field unless you use `ddev config --project-name=` to explicitly set the project name. People using `git worktree` often prefer to omit the project name so they can work on multiple projects at the same time in different worktrees. ## `omit_snapshot_on_delete` Determines whether to omit the database snapshot when running `ddev delete` by default. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | Snapshots are taken by default when running `ddev delete` to allow recovery of data if the deletion was accidental. Setting this to `true` will turn off that safety feature. You can also override this behavior on a per-command basis with `ddev delete --omit-snapshot=false`. ## `override_config` Whether to override config values instead of merging. | Type | Default | Usage | | --- | --- | --- | | project
global | `false` | Can be `true` or `false`. | When `true`, the `config.*.yaml` or `global_config.*.yaml` file with the option will have its settings *override* rather than *merge with* others. Allows statements like `use_dns_when_possible: false` or `additional_hostnames: []` to work. See [Extending `config.yaml` with Custom `config.*.yaml` Files](). ## `performance_mode` Defines the performance optimization mode to be used. Currently [Mutagen asynchronous caching]() is supported. Mutagen is enabled by default on Mac and Traditional Windows. | Type | Default | Usage | | --- | --- | --- | | project
global | \`\` | Can be `global`, `none`, `mutagen`. | This is typically a global setting. The project-specific value will override global config. ## `php_version` The PHP version the project should use. | Type | Default | Usage | | --- | --- | --- | | project | `8.4` | Can be `5.6` through `8.6`. New versions are added when released upstream. | You can only specify the major version (`7.3`), not a minor version (`7.3.2`), from those explicitly available. ## `project_tld` Default Top-Level-Domain (`TLD`) to be used for a project’s domains, or globally for all project domains. This defaults to `ddev.site` and it’s easiest to work with DDEV using the default setting. | Type | Default | Usage | | --- | --- | --- | | project
global | `ddev.site` | Can be changed to any TLD you’d prefer. | See [Hostnames and Wildcards and DDEV, Oh My!]() for more information on DDEV hostname resolution. ## `router_bind_all_interfaces` Whether to bind `ddev-router`’s ports on all network interfaces. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | When `true`, the router will bind on all network interfaces instead of only `localhost`, exposing DDEV projects to your local network. This is sometimes used to share projects on a local network, see [Sharing Your Project](). ## `router_http_port` Port for DDEV router’s HTTP traffic. | Type | Default | Usage | | --- | --- | --- | | project
global | `80` | Usually changed only if there’s a conflicting process using that port. | See the [Troubleshooting]() page for more on addressing port conflicts. ## `router_https_port` Port for DDEV router’s HTTPS traffic. | Type | Default | Usage | | --- | --- | --- | | project
global | `443` | Usually changed only if there’s a conflicting process using that port. | See the [Troubleshooting]() page for more on addressing port conflicts. ## `share_default_provider` The default share provider to use with the [`ddev share`]() command. | Type | Default | Usage | | --- | --- | --- | | project
global | `ngrok` | Built-in providers: `ngrok`, `cloudflared`. Custom providers can be added to `.ddev/share-providers/`. Project config overrides global config. | Set globally with `ddev config global --share-default-provider=cloudflared` or per-project with `ddev config --share-default-provider=cloudflared`. Can also be overridden with the `--provider` flag when running `ddev share`. ## `share_provider_args` Extra flags for configuring provider when [sharing projects]() with the [`ddev share`]() command. | Type | Default | Usage | | --- | --- | --- | | project | \`\` | ‌ | Example: - [ngrok](): `--basic-auth username:pass1234 --domain foo.ngrok-free.app`. - [cloudflared](): `--tunnel my-tunnel --hostname mysite.example.com`. > [!NOTE] > > **Replaces `ngrok_args`** > > This option replaces the deprecated `ngrok_args`. The old name still works for backward compatibility. ## `simple_formatting` Whether to disable most [`ddev list`]() and [`ddev describe`]() table formatting and implicitly set `NO_COLOR=1`. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. If you don’t like the table lines in `ddev list` or `ddev describe`, you can completely turn them off with `ddev config global --simple-formatting=true`. | When `true`, turns off most table formatting in [`ddev list`]() and [`ddev describe`]() and suppresses colorized text everywhere. ## `skip_global_hooks` Whether to skip the [global hooks]() in this project. | Type | Default | Usage | | --- | --- | --- | | project | `false` | Can be `true` or `false`. Project hooks still run. | ## `ssh_agent_upstream` Make the `ddev-ssh-agent` container relay to an SSH agent you already run, such as the macOS agent, 1Password, or one forwarded with `ssh -A`, instead of running its own agent. See [Using an Existing SSH Agent]() for how this works with each Docker provider. | Type | Default | Usage | | --- | --- | --- | | global | (empty) | Can be empty, `host`, or an absolute or `~/`-relative path of an agent socket. | - Empty: `ddev-ssh-agent` runs its own agent, and [`ddev auth ssh`]() adds key files to it. - `host`: relay to the agent your Docker provider forwards (Docker Desktop on macOS or Linux, OrbStack, Colima with `--ssh-agent`, or Lima with `ssh.forwardAgent`), or on Linux and WSL2 to the socket in `$SSH_AUTH_SOCK` when a project starts. - A socket path: relay to that socket. The Docker host must be able to reach it, so on macOS use `host` instead. Traditional Windows doesn’t support this setting, because Windows SSH agents such as 1Password listen on a named pipe that Docker can’t reach. On WSL2, bridge that pipe to a socket path first, as [Using an Existing SSH Agent]() describes. If DDEV can’t use the setting, for example `host` with a provider that doesn’t forward an agent, projects still start with DDEV’s own agent and a warning. With an upstream agent, `ddev auth ssh` lists the agent’s keys rather than adding keys. ## `table_style` Style for [`ddev list`]() and [`ddev describe`](). | Type | Default | Usage | | --- | --- | --- | | global | `default` | Can be `default`, `bold`, and `bright`. | `bright` is a pleasant, colorful output some people may prefer. If you don’t like the table lines at all, you can remove them with `ddev config global --simple-formatting=true`. ## `timezone` Timezone for container and PHP configuration. | Type | Default | Usage | | --- | --- | --- | | project | Automatic detection or `UTC` | Can be any [valid timezone](), like `Europe/Dublin` or `MST7MDT`. | If `timezone` is unset, DDEV will attempt to derive it from the host system timezone using the `$TZ` environment variable or the `/etc/localtime` symlink. ## `traefik_monitor_port` Specify an alternate port for the Traefik (ddev-router) monitor port. This defaults to 10999 and rarely needs to be changed, but can be changed in cases of port conflicts. | Type | Default | Usage | | --- | --- | --- | | global | `10999` | Can be any unused port below 65535. | ## `type` The DDEV-specific project type. | Type | Default | Usage | | --- | --- | --- | | project | `php` | Can be `asterios`, `backdrop`, `cakephp`, `codeigniter`, `craftcms`, `drupal`, `drupal6`, `drupal7`, `drupal8`, `drupal9`, `drupal10`, `drupal11`, `drupal12`, `generic`, `joomla`, `laravel`, `magento`, `magento2`, `maho`, `modx`, `php`, `shopware6`, `silverstripe`, `symfony`, `typo3`, `wordpress`, or `wp-bedrock`. | The `php` type doesn’t attempt [CMS configuration]() or settings file management and can work with any project. The many versions of the Drupal project types can be used, for example `drupal11` or `drupal6`. There is also a special `drupal` type that is interpreted as “latest stable Drupal version”, so until the release of Drupal 12, `drupal` currently means `drupal11`. The special `generic` project type does nothing and implies nothing. It is normally used with `webserver_type: generic`. ## `upload_dirs` Paths from the project’s docroot to the user-generated files directory targeted by `ddev import-files`. Can be outside the docroot but must be within the project directory e.g. `../private`. Some CMSes and frameworks have default `upload_dirs`, like Drupal’s `sites/default/files`; `upload_dirs` will override the defaults, so if you want Drupal to use both `sites/default/files` and `../private` you would list both, `upload_dirs: ["sites/default/files", "../private"]`. `upload_dirs` is used for targeting `ddev import-files` and also, when Mutagen is enabled, to bind-mount those directories so their contents does not need to be synced into Mutagen. If you do not have directories of static assets of this type, or they are small and you don’t care about them, you can disable the warning `You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set.` by setting [`disable_upload_dirs_warning`](<#disable_upload_dirs_warning>) with `ddev config --disable-upload-dirs-warning`. | Type | Default | Usage | | --- | --- | --- | | project | | A list of directories. | ## `use_dns_when_possible` Whether to use DNS instead of editing `/etc/hosts`. | Type | Default | Usage | | --- | --- | --- | | project | `true` | Can be `true` or `false`. | When `false`, DDEV will always update the `/etc/hosts` file with the project hostname instead of using DNS for name resolution. See [Using DDEV Offline](). ## `use_hardened_images` Whether to use hardened images for internet deployment. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | When `true`, more secure hardened images are used for an internet deployment. These do not include sudo in the web container, and the container is run without elevated privileges. Generally used with the [hosting]() feature. ## `use_letsencrypt` Whether to enable Let’s Encrypt integration. (Works in conjunction with [`letsencrypt_email`](<#letsencrypt_email>).) | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | May also be set via `ddev config global --use-letsencrypt` or `ddev config global --use-letsencrypt=false`. When `true`, `letsencrypt_email` must also be set and the system must be available on the internet. Used with the [hosting]() feature. ## `web_environment` Additional [custom environment variables]() for a project’s web container. (Or for all projects if used globally.) | Type | Default | Usage | | --- | --- | --- | | project
global | `[]` | ‌ | ## `web_extra_daemons` Additional daemons that should [automatically be started in the web container](). | Type | Default | Usage | | --- | --- | --- | | project | `[]` | ‌ | ## `web_extra_exposed_ports` Additional named sets of ports to [expose via `ddev-router`](). | Type | Default | Usage | | --- | --- | --- | | project | `[]` | ‌ | For common front-end development tools like Vite, see the [Vite Integration]() documentation for complete configuration examples. ## `webimage` > [!WARNING] > > **Proceed with caution** > > It’s unusual to change this option, and we don’t recommend it without Docker experience and a good reason. Typically, this means additions to the existing web image using a `.ddev/web-build/Dockerfile.*`. The Docker image to use for the web server. | Type | Default | Usage | | --- | --- | --- | | project | [`ddev/ddev-webserver`]() | Specify your own image based on [ddev/ddev-webserver](). | ## `webimage_extra_packages` Extra Debian packages for the project’s web container. | Type | Default | Usage | | --- | --- | --- | | project | `[]` | ‌ | Example: `webimage_extra_packages: ['php${DDEV_PHP_VERSION}-tidy', 'php${DDEV_PHP_VERSION}-yac']` will add the `phpX.Y-tidy` and `phpX.Y-yac` packages when the web container is built. ## `webserver_type` Which available [web server type]() should be used. | Type | Default | Usage | | --- | --- | --- | | project | `nginx-fpm` | Can be `nginx-fpm`, `apache-fpm`, `generic`. | To change from the default `nginx-fpm` to `apache-fpm`, for example, you would need to edit your project’s `.ddev/config.yaml` to include the following: ``` webserver_type: apache-fpm ``` This is the same as `ddev config --webserver-type=apache-fpm`. Then run [`ddev restart`]() to have the change take effect. The `generic` type is special: It tells DDEV not to run any web server daemons, and the user can configure their own with the [`web_extra_daemons`](<#web_extra_daemons>) option. > [!TIP] > > For generic web server configuration examples, see the [quickstart guide](). ## `working_dir` Working directories used by [`ddev exec`]() and [`ddev ssh`](). | Type | Default | Usage | | --- | --- | --- | | project | ‌ | ‌ | Example: `working_dir: { web: "/var/www", db: "/etc" }` sets the working directories for the `web` and `db` containers. ## `wsl2_no_windows_hosts_mgt` > [!WARNING] > > **Proceed with caution** > > It’s unusual to change this option, and we don’t recommend it. On Windows, it may prevent site access when using a custom [`project_tld`](<#project_tld>) or working [offline](). (WSL2 only) Whether to disable the management and checking of the Windows hosts file. By default, when using WSL2, DDEV manages the system-wide hosts file on the Windows side (normally `C:\Windows\system32\drivers\etc\hosts`) by using `ddev.exe` installed on the *Windows* side. This normally works better for all applications, including browsers and IDEs. However, this behavior can be disabled by setting `wsl_no_windows_hosts_mgt: true`. | Type | Default | Usage | | --- | --- | --- | | global | `false` | Can be `true` or `false`. | May also be set via `ddev config global --wsl2-no-windows-hosts-mgt` or `ddev config global --wsl2-no-windows-hosts-mgt=false`. ## `xdebug_enabled` Whether Xdebug should be enabled for [step debugging]() or [profiling](). | Type | Default | Usage | | --- | --- | --- | | project | `false` | Please leave this `false` in most cases. Most people use [`ddev xdebug`]() and `ddev xdebug off` (or `ddev xdebug toggle`) commands. | ## `xdebug_ide_location` > [!WARNING] > > **Proceed with caution** > > It’s unusual to change this option, and we don’t recommend it. Ask for help in one of our [support channels]() before changing it unless you understand its use completely. Adjust Xdebug listen location for WSL2 or in-container. This is used very rarely. | Type | Default | Usage | | --- | --- | --- | | global | `""` | Can be empty (`""`), `"wsl2"`, `"container"`, or an explicit IP address. | For PhpStorm running inside WSL2 (or JetBrains Gateway), use `"wsl2"`. For in-container like VS Code Language Server, set to `"container"`. It can also be set to an explicit IP address. Examples: - `xdebug_ide_location: 172.16.0.2` when you need to provide an explicit IP address where the IDE is listening. This is very unusual. - `xdebug_ide_location: container` when the IDE is actually listening inside the `ddev-webserver` container. This is only done very occasionally with obscure Visual Studio Code setups like VS Code Language Server. - `xdebug_ide_location: wsl2` when an IDE is running (or listening) in WSL2. This is the situation when running an IDE directly inside WSL2 instead of running it on Windows. ## `xhgui_http_port` Port for project’s XHGui HTTP URL (for router). Only changed when there are port conflicts on the default port 8143. | Type | Default | Usage | | --- | --- | --- | | project
global | `8143` | Can be changed to avoid a port conflict. | ## `xhgui_https_port` Port for project’s XHGui HTTPS URL (for router). Only changed when there are port conflicts on the default port 8142. | Type | Default | Usage | | --- | --- | --- | | project
global | `8142` | Can be changed to avoid a port conflict. | ## `xhprof_mode` Whether XHProf should use `prepend` or `xhgui` mode. | Type | Default | Usage | | --- | --- | --- | | project
global | `xhgui` | Can be `global`, `prepend` or `xhgui`. | # Environment Variables There are three different things people mean by “environment variables” in a DDEV project, and they land in different places. This page covers all of them, in what order they override each other, and where to put a secret. The same two scopes as [config options]() apply here: 1. **Project** files, in the project directory and mostly under `.ddev`. 2. **Global** files in the [global DDEV directory](), which apply to every project. This page writes that directory as `$HOME/.ddev`, its usual location, but it can be somewhere else. > [!TIP] > > **Container variables take effect on restart** > > The `.ddev/.env*` files, their global equivalents, and `web_environment` are read while containers are being created, so run [`ddev restart`]() after changing them. An application `.env` file in the project root is not one of these: your framework reads it on every request, so changing it needs no restart. ## Which Variables Reach Where | Where you set it | Type | What it reaches | Who reads it | | --- | --- | --- | --- | | An application `.env` file in the project root | project | Nothing in the container environment; it stays a file in your project | Your framework: Laravel, Craft CMS, Symfony, Silverstripe, and others | | [`.ddev/.env*`](<#project-env-files>) and [`$HOME/.ddev/.env*`](<#global-env-files>) | project
global | The environment of any container in the project: `web`, `db`, and add-on or custom services | Anything running in the container, plus interpolation in `.ddev/docker-compose.*.yaml` | | [`web_environment`](<#web_environment>) | project
global | The environment of the `web` container only | Anything running in `web` | ## Application `.env` Files, Outside `.ddev/` Many frameworks read a `.env` file in the project root. That file belongs to your application, and DDEV treats it as ordinary project code: nothing in it becomes a container environment variable. For several project types DDEV writes database credentials and the project URL into that file as part of [settings management](), creating it if it doesn’t exist. Set [`disable_settings_management`]() to `true` if you want to manage the file yourself. You can edit it from the command line with [`ddev dotenv set`](), which is useful in a `README` or a CI script because it creates the file and updates single keys without disturbing the rest: ``` ddev dotenv set .env --app-key=value ``` Your framework reads this file on every request, so a change to it takes effect without a `ddev restart`. The `.local` convention below applies only inside `.ddev/` and `$HOME/.ddev/`. A root `.env.local`, like the one Symfony and Shopware use, is your project’s file and is covered by your project’s own `.gitignore`. ## Project Env Files The `.env*` files in the project’s `.ddev` directory set real environment variables in the project’s containers. The filename decides which service gets them: ``` .env[.[.