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.
Overview of changes
Section titled “Overview of changes”| Component | v1 | v2 |
|---|---|---|
| Language | Bash | PHP 8.5 + static-php-cli |
| Reverse proxy | nginx-proxy | Traefik v3 |
| TLS certificates | Custom openssl CA | mkcert (OS-trusted root CA) |
| Mail catcher | MailCrab | Mailpit |
| Configuration | Shell variables | YAML (~/.dde/config.yml, .dde/config.yml) |
| Worktree support | None | Automatic hostname per worktree |
| Plugin system | None | .dde/plugins/ directory |
| Global config | None | ~/.dde/config.yml |
Step-by-step migration
Section titled “Step-by-step migration”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):
dde system:destroyThis 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:
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.
2. Clean up shell configuration
Section titled “2. Clean up shell configuration”Remove the dde v1 aliases and autocomplete from your ~/.zshrc or ~/.bashrc.
3. Optionally remove v1 data
Section titled “3. Optionally remove v1 data”rm -rf ~/ddeWarning: This deletes all database contents managed by v1. Make backups first if needed.
4. Install dde v2
Section titled “4. Install dde v2”Install the v2 binary following the installation guide, then run the system setup:
dde system:install5. Initialize your projects
Section titled “5. Initialize your projects”Navigate to each project and initialize it for v2:
cd ~/projects/my-appdde project:initdde project:upproject: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.
.env file migration
Section titled “.env file migration”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:
.envstays 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:changemeon127.0.0.1,null://nullas mailer — the app boots without touching the developer’s machine.docker-compose.ymlholds the dde-specific runtime. The container-side credentials, service hostnames (mariadb,mailpit), ports and server-version hints live here. This is the layer thatdde project:upactually 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:
| Variable | Where | Behaviour |
|---|---|---|
MAILER_DSN | compose environment: + .env | Only 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_URL | compose environment: + .env | User-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.
Remove dde user from existing Dockerfiles
Section titled “Remove dde user from existing Dockerfiles”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.
Breaking changes
Section titled “Breaking changes”| v1 command/feature | v2 equivalent | Notes |
|---|---|---|
dde project fix-permissions | Removed | No longer needed (automatic UID/GID mapping) |
dde project exec-root | dde project:exec --root | Now a flag on project:exec |
VIRTUAL_HOST env var | Traefik labels (auto-generated) | Set automatically by project:init |
| Custom openssl CA | mkcert root CA | Installed via system:install |
| MailCrab | Mailpit | Different web UI, same SMTP interface |
| nginx-proxy | Traefik v3 | Routing via labels instead of env vars |
v2: restart commands removed
Section titled “v2: restart commands removed”project:restart and system:restart were removed in v2. The lifecycle
commands are now explicitly symmetric:
Replacement for project:restart
Section titled “Replacement for project:restart”- 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
Replacement for system:restart
Section titled “Replacement for system:restart”- 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
New commands
Section titled “New commands”dde project:stop/dde system:stop: halt containers without removing them. Resume quickly viaproject:up/system:up.dde system:update: removes all dde containers, rebuilds the global service images withdocker build --pull, and refreshes the integrations that are bound to the dde binary (shell completion, Claude skill). Package managers run this automatically after anapt/dnf/pacman/apkupgrade; Homebrew surfaces it in its caveats.