# How DDEV Works

DDEV is a [Go](<https://go.dev>) 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](<https://docs.docker.com/compose/>) files for you, which is a detail you can cheerfully ignore unless you’re Docker-curious or [defining your own services](<https://ddev.github.io/ddev/en/stable/users/extend/custom-compose-files/index.md>).

## 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](<https://ddev.github.io/ddev/en/stable/users/configuration/config/index.md>) 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](<https://ddev.github.io/ddev/en/stable/users/extend/additional-services/index.md>).

**`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](<https://ddev.github.io/ddev/en/stable/users/extend/using-add-ons/index.md>).

**`apache` directory**

Default Apache configuration when using `webserver_type: apache-fpm`, which [can be customized](<https://ddev.github.io/ddev/en/stable/users/extend/customization-extendibility/#custom-apache-configuration>).

**`commands` subdirectories**

Project custom [shell commands](<https://ddev.github.io/ddev/en/stable/users/extend/custom-commands/index.md>) that can run on the host or inside a container.

**`config.yaml`**

The project’s main [configuration file](<https://ddev.github.io/ddev/en/stable/users/configuration/config/index.md>).

**`config.*.yaml` files**

[Environmental overrides](<https://ddev.github.io/ddev/en/stable/users/configuration/config/#environmental-overrides>) for parts of `config.yaml`.

**`db-build` directory**

Can be used to provide a [custom Dockerfile](<https://ddev.github.io/ddev/en/stable/users/extend/customizing-images/index.md>) for the database container.

**`db_snapshots` directory**

Where snapshots go when you run the [`ddev snapshot`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#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](<https://ddev.github.io/ddev/en/stable/users/extend/custom-compose-files/index.md>) that add or override services. Read more in [Additional Service Configurations &amp; Add-ons](<https://ddev.github.io/ddev/en/stable/users/extend/additional-services/index.md>).

**`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](<https://ddev.github.io/ddev/en/stable/users/extend/in-container-configuration/index.md>) for more. Changes made in the container aren’t copied back, see [Persisting Changes Across Restarts](<https://ddev.github.io/ddev/en/stable/users/extend/in-container-configuration/#persisting-changes-across-restarts>).

**`mutagen` directory**

Contains `mutagen.yml`, where you can [override the default Mutagen configuration](<https://ddev.github.io/ddev/en/stable/users/install/performance/#advanced-mutagen-configuration-options>).

**`mysql` directory**

Contains [optional `mysql` or `mariadb` configuration](<https://ddev.github.io/ddev/en/stable/users/extend/customization-extendibility/#custom-mysqlmariadb-configuration-mycnf>).

**`nginx` directory**

Can be used for add-on [nginx snippets](<https://ddev.github.io/ddev/en/stable/users/extend/customization-extendibility/#custom-nginx-configuration>).

**`nginx_full` directory**

Contains the nginx configuration used by the web container, which [can be customized](<https://ddev.github.io/ddev/en/stable/users/extend/customization-extendibility/#custom-nginx-configuration>).

**`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`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#pull>) command can work with [hosting providers](<https://ddev.github.io/ddev/en/stable/users/providers/index.md>).

**`traefik` directory**

Configures the [Traefik](<https://ddev.github.io/ddev/en/stable/users/extend/traefik-router/index.md>)-based `ddev-router`. Full details in the [Traefik docs](<https://ddev.github.io/ddev/en/stable/users/extend/traefik-router/index.md>).

**`web-build` directory**

Can be used to provide a [custom Dockerfile](<https://ddev.github.io/ddev/en/stable/users/extend/customizing-images/index.md>) 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](<https://ddev.github.io/ddev/en/stable/users/debugging-profiling/xhprof-profiling/index.md>) 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/<username>/.ddev` on macOS
   - `/home/<username>/.ddev` on Linux/WSL2
   - `C:\Users\<username>\.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](<https://ddev.github.io/ddev/en/stable/users/configuration/config/index.md>).

**`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](<https://ddev.github.io/ddev/en/stable/users/extend/customization-extendibility/#extending-configyaml-with-custom-configyaml-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`](<https://ddev.github.io/ddev/en/stable/users/configuration/config/#performance_mode>) and [`docker-buildx`](<https://ddev.github.io/ddev/en/stable/users/configuration/config/#docker_buildx_version>).

**`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](<https://ddev.github.io/ddev/en/stable/users/extend/custom-commands/index.md>) here.

**`db-build` directory**

Like the per-project `db-build` directory, can be used to provide a [custom Dockerfile](<https://ddev.github.io/ddev/en/stable/users/extend/customizing-images/#global-dockerfiles>) 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](<https://ddev.github.io/ddev/en/stable/users/extend/customizing-images/#global-dockerfiles>) 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](<https://ddev.github.io/ddev/en/stable/users/usage/troubleshooting/#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](<https://ddev.github.io/ddev/en/stable/users/install/performance/#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](<https://ddev.github.io/ddev/en/stable/users/usage/faq/#can-different-projects-communicate-with-each-other>).

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`](<https://ddev.github.io/ddev/en/stable/users/configuration/config/#additional_hostnames>) and [`additional_fqdns`](<https://ddev.github.io/ddev/en/stable/users/configuration/config/#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`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#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](<https://ddev.github.io/ddev/en/stable/images/container-diagram.png>)

## 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](<https://ddev.github.io/ddev/en/stable/users/extend/custom-commands/#container-commands>). Unlike the containers, it survives [`ddev restart`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#restart>) and [`ddev poweroff`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#poweroff>).

| Directory | Scope | Contents |
| --- | --- | --- |
| `bashhistory/<project>-db`, `bashhistory/<project>-web` | Per project | Bash history |
| `composer` | Shared | Composer cache |
| `corepack` | Shared | Package managers installed by `corepack` |
| `global-commands` | Shared | [Global commands](<https://ddev.github.io/ddev/en/stable/users/extend/custom-commands/#global-commands>), copied from the host |
| `mkcert` | Shared | The mkcert certificate authority, copied from the host and used for HTTPS certificates |
| `mysqlhistory/<project>-db`, `mysqlhistory/<project>-web` | Per project | MySQL client history |
| `n_prefix/<project>-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`](<https://ddev.github.io/ddev/en/stable/users/configuration/config/#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`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#delete>) removes the project’s own directories and router files. To keep your own files in this volume, see [Persisting Changes Across Restarts](<https://ddev.github.io/ddev/en/stable/users/extend/in-container-configuration/#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`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#start>):

```
ddev poweroff
docker volume rm ddev-global-cache
```
