Skip to content

Migration from v1 to v2

dde v2 is a complete rewrite. v1 and v2 cannot run side by side — you must fully remove v1 before installing v2.

Componentv1v2
LanguageBashPHP 8.5 + static-php-cli
Reverse proxynginx-proxyTraefik v3
TLS certificatesCustom openssl CAmkcert (OS-trusted root CA)
Mail catcherMailCrabMailpit
ConfigurationShell variablesYAML (~/.dde/config.yml, .dde/config.yml)
Worktree supportNoneAutomatic hostname per worktree
Plugin systemNone.dde/plugins/ directory
Global configNone~/.dde/config.yml

1. Stop and remove all dde v1 projects and system services

Section titled “1. Stop and remove all dde v1 projects and system services”

Stop every running dde project, then remove all dde v1 containers and system services. You have two options:

Option A — Use the dde v1 command (recommended):

Terminal window
dde system:destroy

This stops and removes all dde v1 projects and system services in one step.

Option B — Use docker directly:

If dde system:destroy no longer works (for example, because dde v1 is already partially uninstalled), or if you want to be extra sure all dde-managed containers are gone, run:

Terminal window
docker rm -f $(docker ps -a --filter "name=dde-" -q)

You can also run both commands in sequence — first dde system:destroy, then the docker rm command as a safety net. Note that docker rm -f will exit with an error if no matching containers exist (which is expected after dde system:destroy has already removed them).

Warning: Do not use docker rm -f $(docker ps -aq) as this removes all containers on your machine, including those unrelated to dde.

Remove the dde v1 aliases and autocomplete from your ~/.zshrc or ~/.bashrc.

Terminal window
rm -rf ~/dde

Warning: This deletes all database contents managed by v1. Make backups first if needed.

Install the v2 binary following the installation guide, then run the system setup:

Terminal window
dde system:install

Navigate to each project and initialize it for v2:

Terminal window
cd ~/projects/my-app
dde project:init
dde project:up

project:init creates the .dde/ directory, detects your docker-compose.yml, and adds Traefik labels. It also strips v1 boilerplate (the explicit dde external network, the SSH-Agent volume mount and SSH_AUTH_SOCK, legacy DDE_UID/DDE_GID build args, fixed container_name) — in v2, networks and the SSH-Agent socket are injected by the runtime overlay, so the committed compose file stays clean.

Open https://my-app.test in your browser to verify.

While adapting the project, project:init also inspects the .env/.env.local/.env.dev files and migrates a small, well-known set of variables.

Why this split? v2 draws a clear line between the two config layers:

  • .env stays a neutral template. It keeps values that are safe to commit and safe to use outside of dde (e.g. running tests on CI, opening the project on a plain host). app:changeme on 127.0.0.1, null://null as mailer — the app boots without touching the developer’s machine.
  • docker-compose.yml holds the dde-specific runtime. The container-side credentials, service hostnames (mariadb, mailpit), ports and server-version hints live here. This is the layer that dde project:up actually renders into the container.

Because dde-specific values live only in docker-compose.yml, the worktree override can cleanly rewrite them per worktree (hostnames, DATABASE_URL path segment) without ever touching the committed .env. Main and worktree both share the same .env, but see different values inside their containers.

With that split in mind, project:init applies these two rules:

VariableWhereBehaviour
MAILER_DSNcompose environment: + .envOnly when mailpit is a configured dde service. compose gets smtp://mailpit:1025; .env is rewritten to null://null (so the app is safe to run outside dde too).
DATABASE_URLcompose environment: + .envUser-prompted. If the .env value matches a configured dde DB service, you are asked whether to migrate. Accepting rewrites .env to <scheme>://app:changeme@127.0.0.1:<port>/<db>?<query> and adds a compose entry <scheme>://<root>:<root>@<service>/<sanitized-db>?serverVersion=<version>&<query>.

In non-interactive mode (piped stdout, --no-interaction) the DATABASE_URL prompt is silently rejected — run project:init in a real terminal if you want to apply it.

If the existing dev stage of a Dockerfile references the dde user (e.g. in chown instructions), those references must be removed. v2 no longer has a dde user available during the docker bild.

Any customisations that previously relied on the dde user can be implemented using hooks or service adapters instead:

  • Hooks (.dde/hooks/): shell scripts executed at defined lifecycle points (e.g. post-up).
  • Service adapters (.dde/adapters/): allow project-specific configuration of services such as nginx or php-fpm.
v1 command/featurev2 equivalentNotes
dde project fix-permissionsRemovedNo longer needed (automatic UID/GID mapping)
dde project exec-rootdde project:exec --rootNow a flag on project:exec
VIRTUAL_HOST env varTraefik labels (auto-generated)Set automatically by project:init
Custom openssl CAmkcert root CAInstalled via system:install
MailCrabMailpitDifferent web UI, same SMTP interface
nginx-proxyTraefik v3Routing via labels instead of env vars

project:restart and system:restart were removed in v2. The lifecycle commands are now explicitly symmetric:

  • Restart the existing containers (fast, container state is preserved):
    Terminal window
    dde project:stop && dde project:up
  • Rebuild with fresh images:
    Terminal window
    dde project:update
  • Restart the global services only (containers are preserved):
    Terminal window
    dde system:stop && dde system:up
  • Rebuild the global service images plus skill/completion refresh:
    Terminal window
    dde system:update
  • dde project:stop / dde system:stop: halt containers without removing them. Resume quickly via project:up / system:up.
  • dde system:update: removes all dde containers, rebuilds the global service images with docker build --pull, and refreshes the integrations that are bound to the dde binary (shell completion, Claude skill). Package managers run this automatically after an apt / dnf / pacman / apk upgrade; Homebrew surfaces it in its caveats.