All notable changes to this project will be documented in this file.
- A
dde-nightlypackage is now built and published on every push to thev2branch (APT, Alpine, Arch, RPM, Homebrew). It installs the sameddebinary as stable, declaresConflicts: dde, and tracks the latest commit onv2. See the "Nightly channel" section of the installation docs.
dde --versionis now produced by the build pipeline. Stable builds report the git tag (e.g.v2.0.0-beta.1), nightly builds report the short commit SHA, and an unbuilt local checkout (bin/console) reportsdev.
- Plugins are now registered under the
project:namespace instead ofproject:exec:. A plugin with@command deploynow runs asdde project:deploy(previouslydde project:exec:deploy). Built-inproject:*commands take precedence; colliding plugin names are silently shadowed.
- Each git worktree now owns its own per-project Docker network (
dde-services-<project>-<suffix>). Main and worktree can run different versions of the same shared service (e.g. main on postgres-16, worktree on postgres-18) without DNS-alias collisions on the canonical service name. Main checkouts are unaffected.
- Mailpit retains all messages instead of capping at the most recent 500. The container now starts with
MP_MAX_MESSAGES=0; users on existing installations pick this up after the nextdde system:update. (#143) project:db*commands (project:db,project:db:open,project:db:export,project:db:import,project:db:snapshot:create,project:db:snapshot:restore) now target the worktree-suffixed database when run from inside a git worktree, matching theDATABASE_URLthe compose override rewrites for the application container. An explicit--databasevalue is still forwarded verbatim.project:initno longer injectsAPP_ENV=devintodocker-compose.yml. Symfony skips loading.env/.env.localwheneverAPP_ENVis already defined in the environment, which silently broke projects that rely on.envfor application-level configuration. (#132)- Worktrees nested inside the main checkout (e.g.
<main>/.claude/worktrees/<name>) and worktrees whose branch has not yet committed.dde/are now correctly detected. Previously they launched with the main checkout's hostnames, breaking isolation. - Traefik labels for subdomain hosts (e.g.
Host(\preview..test`)`) are now rewritten in the worktree compose override, preventing the "router defined multiple times" conflict between main and worktree. - URL-bearing env vars declared via
env_file:are now rewritten in the worktree compose override, so values likeE2E_TARGET_URLorMERCURE_URLpoint at the worktree variant instead of leaking through to the main checkout. - Worktree TLS certificates now cover every subdomain declared via Traefik labels, not just the bare worktree hostname. Subdomain routes inside a worktree are no longer served with an untrusted wildcard certificate.
project:upandproject:updatenow list every worktree subdomain URL in the "Available at:" output instead of only the bare worktree hostname.project:up"Available at:" only advertises URLs of services that are actually running — services excluded by inactive Compose profiles are no longer surfaced with broken TLS.project:upnow detaches stale service containers from the per-project network when a service version changes (e.g. mariadb 10.11 → 11.8), preventing Docker's embedded DNS from round-robining between the old and new containers.- The dev-layer build now skips shell-less images (distroless/scratch bases such as Garage) when selecting the target service, avoiding
exec: /bin/sh: no such file or directoryerrors in multi-service projects.
project:restartremoved — useproject:stop && project:upto restart with preserved containers, orproject:updateto rebuild with fresh images.system:restartremoved — usesystem:stop && system:upto restart preserved containers, orsystem:updateto rebuild global service images.- APT/Arch/Alpine/RPM package upgrades now call
dde system:update(previouslydde system:install).
system:update— rebuilds global service images (docker build --pull) and refreshes integrations (shell completion, Claude skill).system:stop— stops all dde containers (global + versioned) without removing them.
- Homebrew formula now installs
mkcertas a dependency.
- PostgreSQL services now use
/var/lib/postgresql/dataas the data directory, matching the upstream image default.
project:upandproject:updatenow print the project's reachable domains after a successful run (worktree-aware)- Auto-generated container hostnames (
<project>-<service>) so the shell prompt inside a container showsbeispiel-webinstead of the random container ID; explicithostname:in the user'sdocker-compose.ymlis respected project:initnow rewritesMAILER_DSNautomatically (compose +.env) whenmailpitis configured and prompts to migrateDATABASE_URLfrom.envinto the compose service- Per-worktree
DATABASE_URLrewrite in the compose override so main and worktree checkouts talk to distinct databases on the shared service
project:downon a worktree no longer strips the shared database from the per-project network while the main project is still runningproject:downno longer tries to remove the per-project network while other projects are still attachedproject:openfrom a worktree checkout now opens the worktree URL instead of the main project URL- Worktrees now emit unique Traefik routers with
!overridelabels, preventing the 404 fallback caused by duplicate router definitions between main and worktree .envmigration duringproject:initstrips and preserves surrounding quotes, soDATABASE_URL="mysql://…"is recognised and rewritten correctlyproject:initno longer errors out when the services prompt is submitted empty- RPM distribution URL corrected
- Auto-start project and system services when running a shell in a stopped project (
feat(shell)) - Per-project Docker network: created on
project:up, disconnected onproject:down; system service containers are automatically connected/disconnected - SSH-agent injected into compose override per project, replacing hardcoded init boilerplate
- Multi-select service chooser with per-service version picker during
project:init
- Mailpit is now accessible exclusively via Traefik; host port forwarding removed
ConfigManagersplit intoGlobalConfigManagerandProjectConfigManagerfor clearer responsibility boundaries- dde network, SSH-agent, and
OPEN_URLboilerplate removed from init template; injected via compose override instead
- SSH keys are now added to the agent after
system:restart - SSH keys loaded from global config instead of auto-detecting all keys on the system
- Homebrew formula uses
caveatsinstead ofpost_installfor post-install instructions
- RPM distribution for Fedora, RHEL, Rocky Linux, and AlmaLinux via DNF repository
dde v2 is a complete rewrite of the Docker Development Environment. The previous Bash-based solution has been replaced with a PHP binary built as a standalone executable via static-php-cli, running on macOS and Linux.
- Complete CLI rewrite in PHP 8.5 with Symfony 8
- Traefik v3 as reverse proxy (replacing deprecated nginx-proxy)
- mkcert for locally-trusted TLS certificates
- Mailpit for email testing (replacing MailCrab)
- Git worktree support with automatic subdomain generation
- Plugin system for custom project commands
- Global and project-level configuration (YAML)
- Database management: shell, export, import, snapshots
- Service versioning (multiple MariaDB/PostgreSQL versions simultaneously)
--output=jsonon all commands for scripting- Hook system (project.up.pre/post, project.down.pre/post)
- Shell completion (bash, zsh)
- Cross-platform binary (macOS/Linux x86_64+arm64)
system:doctorhealth checks (10 checks)- Claude Code skills for AI-assisted development
- CLI syntax:
dde project <cmd>→dde project:<cmd> - Configuration:
.dde.yml→.dde/config.ymlwith new structure - Dockerfile: 4-line boilerplate removed, automatic dev layer instead
dde project:exec:root→dde project:exec --root
dde project fix-permissions(no longer needed, UID/GID mapping is automatic)- Bash-based implementation
- nginx-proxy dependency
- Custom OpenSSL CA