Skip to content

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.

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* and $HOME/.ddev/.env* 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 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[.<service>[.<label>...]][.local]
File Reaches Notes
.ddev/.env Every container in the project
.ddev/.env.local Every container in the project Gitignored, DDEV v1.25.4+
.ddev/.env.web The web container Any service name works: db, redis, and so on
.ddev/.env.web.local The web container Gitignored, DDEV v1.25.4+
.ddev/.env.web.myaddon The web container A label keeps files from different sources apart, DDEV v1.25.4+
.ddev/.env.web.myaddon.local The web container Gitignored, DDEV v1.25.4+
.ddev/.env.redis-build Nothing No service is named redis-build, so this file is expanded but never injected

A trailing .local only marks the file as gitignored, and a label after the service name only keeps files apart. Both are dropped before DDEV reads the service name, so local cannot be used as a service or label name. Files ending in .example are skipped, which is how an add-on ships a documented template without setting anything.

Every one of these files is also passed to docker-compose config, whatever it is named, so ${SOME_VARIABLE} in a .ddev/docker-compose.*.yaml file is expanded from all of them together. That is the point of the .ddev/.env.redis-build row above: when a variable is only meant to be substituted into a compose file, give the file a name that is not a service name and it will never appear inside a container.

Override Order

Later wins. Everything in the global DDEV directory is applied before anything belonging to the project, and within each directory DDEV sorts the files by their name with a trailing .local removed, which puts a .local file directly after the file it overrides.

The table below is a project that has every kind of file at once, with db and web as its services and myaddon as a label. The global files, the .local files, and the labeled files all need DDEV v1.25.4+; .ddev/.env and .ddev/.env.<service> are older.

# Content Notes
1 web_environment in $HOME/.ddev/global_config.yaml Reaches the web container only
2 web_environment in .ddev/config.yaml, then .ddev/config.*.yaml Reaches the web container only
3 $HOME/.ddev/.env Every container of every project
4 $HOME/.ddev/.env.local Overrides the file above it, gitignored
5 $HOME/.ddev/.env.db Services in alphabetical order, so db before web
6 $HOME/.ddev/.env.db.local Overrides the file above it, gitignored
7 $HOME/.ddev/.env.web
8 $HOME/.ddev/.env.web.local Overrides the file above it, gitignored
9 $HOME/.ddev/.env.web.myaddon Labels in alphabetical order, all after the unlabeled $HOME/.ddev/.env.web
10 $HOME/.ddev/.env.web.myaddon.local Overrides the file above it, gitignored
11 .ddev/.env Every container of this project
12 .ddev/.env.local Overrides the file above it, gitignored
13 .ddev/.env.db Services in alphabetical order, so db before web
14 .ddev/.env.db.local Overrides the file above it, gitignored
15 .ddev/.env.web
16 .ddev/.env.web.local Overrides the file above it, gitignored
17 .ddev/.env.web.myaddon Labels in alphabetical order, all after the unlabeled .ddev/.env.web
18 .ddev/.env.web.myaddon.local Overrides the file above it, gitignored, and applied last of all

Only the files you actually have are applied, and the rest of the order is unchanged. A second label, .ddev/.env.web.otheraddon, would come after row 18, because myaddon sorts before otheraddon and a .local file never leaves the file it overrides.

To see the result:

  • ddev utility check-custom-config lists the env files DDEV found under Environment, in the order they are applied.
  • ddev utility compose-config shows the rendered .ddev/.ddev-docker-compose-full.yaml, including the environment section of each service.
  • ddev exec env and ddev exec -s <service> env show what actually reached a running container.

Global Env Files

With DDEV v1.25.4+, the same filenames work in the global configuration directory, where they apply to every project on the machine. $HOME/.ddev/.env reaches every container of every project, and $HOME/.ddev/.env.db reaches the db container of every project.

This is the only way to set a variable globally for something other than the web container, which is all web_environment can do.

Global files are applied before anything belonging to the project, so a global value is only a default: a project that sets the same variable in .ddev/.env or .ddev/.env.<service> wins. See Override Order for the full sequence.

Use ddev dotenv global set to write them. Name the file .ddev/<file> exactly as you would in a project, and DDEV resolves it in the global directory:

ddev dotenv global set .ddev/.env.web --api-url=https://example.com

Secrets

With DDEV v1.25.4+, DDEV adds /.env.local and /.env.*.local to both .ddev/.gitignore and $HOME/.ddev/.gitignore, so any env file ending in .local stays out of Git without you having to take over those files.

That gives a shared service a natural split: commit .ddev/.env.web with the settings your team shares, and keep the token next to it in .ddev/.env.web.local, which is applied right after it.

# Committed, everyone gets it
ddev dotenv set .ddev/.env.web --api-url=https://example.com

# Gitignored, yours only
ddev dotenv set .ddev/.env.web.local --api-key=secret

To document which keys are expected without committing their values, add a .ddev/.env.web.example file listing the keys with empty values. DDEV never reads .example files, and .ddev/.gitignore ignores them, so commit it with git add -f .ddev/.env.web.example.

The global files work the same way, so $HOME/.ddev/.env.web.local is a reasonable home for a credential you use in every project (but note that it is overridden by project-level .ddev/.env or .ddev/.env.web, etc).

web_environment

The web_environment setting predates the env files above and only reaches the web container. It lives in .ddev/config.yaml or any .ddev/config.*.yaml for a project, and in $HOME/.ddev/global_config.yaml for every project:

web_environment:
    - MY_ENV_VAR=someval
    - MY_OTHER_ENV_VAR=someotherval

You can also pass a variable through from the host by listing just its name with no value. DDEV hands the bare name to docker-compose, which resolves it from the host environment when the container starts:

web_environment:
    - MY_HOST_VAR

To set these from the command line, use ddev config. Use --web-environment instead of --web-environment-add to replace the existing list rather than add to it:

# Set MY_ENV_VAR for the project
ddev config --web-environment-add="MY_ENV_VAR=someval"

# Set MY_ENV_VAR globally
ddev config global --web-environment-add="MY_ENV_VAR=someval"

Applying Changes

A running container never picks up a new value on its own. After adding or editing any .ddev/.env* file, its global equivalent, or a web_environment value, run:

ddev restart

An application .env file in the project root is the exception, since your framework reads it on every request rather than at container start.

ddev dotenv

The ddev dotenv commands create and update these files without an editor, quoting values correctly. Flags become variable names: --api-url writes API_URL.

Command Type Where the file lands
ddev dotenv set, ddev dotenv get project The path is relative to the project root, so .env for an application file and .ddev/.env.web for a DDEV one
ddev dotenv global set, ddev dotenv global get global The same .ddev/<file> path, resolved in the global DDEV directory, DDEV v1.25.4+

A DDEV env file is named .ddev/.env* in both scopes, so the only difference between the two commands is the word global:

ddev dotenv set .ddev/.env.redis --redis-tag 7-bookworm
ddev dotenv get .ddev/.env.redis --redis-tag

ddev dotenv global set .ddev/.env.redis --redis-tag 7-bookworm
ddev dotenv global get .ddev/.env.redis --redis-tag

See Also