Skip to content

How DDEV Works

DDEV is a Go application that stores its configuration in files on your workstation.
It uses those blueprints to mount your project files into Docker containers 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 to another location), and a .ddev directory for each project you set up.

The global configuration directory 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 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.

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 get --installed and ddev get --remove to work, see Managing Installed 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.
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 and check out examples in ddev-contrib.
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 ~/.ddev/homeadditions.) Check out the homeadditions docs for more.
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 ddev-router when it is using Traefik.
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.
The base docker-compose file used to describe a project.
The result of preprocessing .ddev-docker-compose-base.yaml using docker-compose config. Mostly it replaces environment variables with their values.
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.
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: ~/.ddev ($HOME/.ddev) or in ~/.config/ddev. ~/.ddev takes precedence if it exists, unless $XDG_CONFIG_HOME is set, in which case it will be $XDG_CONFIG_HOME/ddev.

Where is my global .ddev config?

Use ddev version (look at global-ddev-dir) to check which location is used for the .ddev global directory.

What if I don’t want to clutter up my $HOME with a .ddev directory?

DDEV can use the $XDG_CONFIG_HOME environment variable from XDG Base Directory Specification to move ~/.ddev to the $XDG_CONFIG_HOME/ddev directory if $XDG_CONFIG_HOME is defined:

ddev poweroff
# permanently set environment variable using a directory that works for you
export XDG_CONFIG_HOME="$HOME/.config"
# restart the terminal and run:
mv ~/.ddev ${XDG_CONFIG_HOME}/ddev

Otherwise, on Linux/WSL2 only, the default $HOME/.config/ddev can be used when ~/.config/ddev exists and ~/.ddev does not exist. You can move the config directory with:

mv ~/.ddev ~/.config/ddev
This YAML file defines your global configuration, which consists of various config settings.
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-compose.
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.
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.

Hidden Global Files

Again, these files are mostly regenerated on every ddev start so it’s best to leave them alone:

Prevents files from getting checked in when they shouldn’t be.
The complete, generated docker-compose directive used for DDEV’s router.
The base docker-compose directive used in generating .router-compose-full.yaml.
docker-compose files with the name router-compose.*.yaml can be used to override stanzas in the .router-compose.yaml file.
The complete, generated docker-compose directive used for DDEV’s SSH agent.
The base docker-compose directive used in generating .ssh-auth-compose-full.yaml.
Directory used for storing DDEV’s generated Dockerfile used in building the SSH agent image.
An empty file whose purpose is mysterious and intriguing.


DDEV uses a global ~/.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-<projectname>-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, your request goes through the router. When you use the 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