architecture.md
July 28, 2026 · View on GitHub
dde follows a layered architecture with thin commands, manager-based orchestration, and dependency injection throughout. This document describes the namespace structure and design principles.
Namespace Overview
Application (App\)
Application-- extends Symfony Console Application. Defines the app version (APP_VERSION), filters visible commands toproject:*/system:*prefixes, adds the global--outputoption, and registers plugin commands viaPluginCommandLoader.Kernel-- Symfony Kernel with PHAR-aware overrides forgetCacheDir()/getLogDir().
Commands (App\Command\)
Commands are the CLI entry points. They are thin wrappers that delegate to managers and services.
App\Command\Project\*-- project-scoped commands (init, up, down, stop, shell, exec, logs, open, status, describe, update)App\Command\Project\Database\*-- database commands (db, db:export, db:import, db:snapshot:*)App\Command\Project\Service\*-- per-project service management (service:list, service:enable, service:disable)App\Command\System\*-- system-wide commands (install, update, up, down, stop, restart, status, doctor, cleanup, service:up)App\Command\AboutCommand-- version and system information
Base classes:
AbstractBaseCommand-- root base class, providesFormatterResolverandresolveFormatter()AbstractProjectCommand-- adds project directory detection, config resolutionAbstractDatabaseCommand-- adds database adapter resolution and connection helpersAbstractSystemCommand-- marker base class for system commands
All commands use the #[AsCommand] attribute for registration.
Managers (App\Manager\)
Managers contain the core business logic and orchestrate complex operations.
| Manager | Responsibility |
|---|---|
ProjectLifecycleManager | Project up/down orchestration (services, certs, dev layers, overrides) |
SystemLifecycleManager | System up/down/stop/update orchestration (global services + versioned containers, image rebuild with --pull, post-install refresh for completion + claude-skill) |
ProjectInitManager | .dde/ directory structure creation during project:init |
ProjectInitAdaptationManager | Project adaptation logic during project:init (compose/env migration proposals; EnvMigrationProposal is its DTO) |
DockerComposeManager | Docker Compose CLI calls, runtime override generation |
DockerManager | Low-level Docker CLI (inspect, network, volume, exec, image operations) |
ImageManager | Image label inspection, dev layer build, cache invalidation |
GlobalConfigManager | Global ~/.dde/config.yml loading |
ProjectConfigManager | Project .dde/config.yml loading, merge with global config, project directory detection |
WorktreeManager | Git worktree detection, hostname resolution / rewriting (incl. subdomains), DB-name resolution, environment override computation (incl. env_file values) |
DatabaseManager | Database shell, export, import, snapshot management, port resolution |
SystemServiceManager | Versionable service container lifecycle (start, stop, status, port allocation) |
ServiceConfigManager | Service container configuration generation |
MkcertManager | mkcert CLI wrapper, cert generation, Traefik dynamic TLS config, CA root path resolution for container trust |
CompletionManager | Shell completion generation and installation |
CleanupManager | Container and volume cleanup |
ProjectInfoManager | Project info display |
ClaudeCodeManager | Detects a Claude Code installation and installs/refreshes the bundled skills/claude/dde skill |
Services (App\Service\)
System services encapsulate the infrastructure containers managed by system:up/system:down. They implement ServiceInterface.
ServiceInterface-- contract for system services, collected via container tagProjectNetworkAwareInterface-- extendsServiceInterface; marks global services that must be attached to every per-project network (with their DNS aliases)AbstractSystemService-- base class with start/stop/status logic viaDockerManagerTraefikService-- reverse proxy (ports 80/443), network creation, Traefik config managementDnsmasqService-- DNS resolver for the.testTLD, image build, resolver file managementSshAgentService-- SSH agent socket sharing across containersMailpitService-- mail testing serviceServiceRegistry-- service type definitions, version defaults, port mappings, global service collectionImageBuilder-- Docker image building for system servicesHostSshAgentResolver-- resolves the host SSH agent socket forhostagent mode (macOS: always the Docker Desktop / OrbStack bridge; Linux:SSH_AUTH_SOCKor an explicit socket path, leading~expanded); single source of truth shared byDockerComposeManagerandSshAgentCheck
Configuration (App\Config\)
GlobalConfig-- DTO for~/.dde/config.yml(output format, DNS forward, SSH keys, service versions)ProjectConfig-- DTO for.dde/config.yml(project name, services, containers)ResolvedConfig-- merged configuration from global + project + defaultsWorktreeInfo-- data class for git worktree metadata
Definition (App\Config\Definition\)
GlobalConfigDefinition-- Symfony TreeBuilder schema for the global configProjectConfigDefinition-- Symfony TreeBuilder schema for the project config
Models (App\Model\)
ContainerConfig-- Docker container creation parameters (image, ports, volumes, labels, etc.)ContainerInfo-- running container metadata fromdocker inspectContainerStatus-- container state representationServiceDefinition-- service name, version, container name, portsServiceStartStatus/ServiceStatus-- start outcome and running/stopped status of a service containerSystemLifecycleProgress-- progress events emitted bySystemLifecycleManager, rendered live by thesystem:*commandsUserContext-- host UID/GID for user mapping inside containersHostSshAgentResolution-- result ofHostSshAgentResolver(available flag, mount source, reason)
Parsers (App\Parser\)
DockerComposeParser-- reads and normalizes docker-compose.yml filesDockerfileParser-- extracts information from Dockerfiles (base image, labels)
Adapters (App\Adapter\)
AdapterRegistry-- discovers and provides adapter scripts (built-in ones fromresources/adapters/, project-specific ones from.dde/adapters/). Handles PHAR extraction.
Database (App\Database\)
DatabaseAdapterInterface-- contract for database-specific operations (DSN generation, shell, export, import)MariaDbAdapter-- MariaDB/MySQL implementationPostgresAdapter-- PostgreSQL implementationDatabaseAdapterRegistry-- maps service names to adapters
Doctor (App\Doctor\)
CheckInterface-- contract for health checks (tagged withdde.doctor_check)CheckResult-- check outcome (name, status, message, fixHint)CheckStatus-- enum:Ok,Warning,ErrorApp\Doctor\Check\*-- 11 concrete check implementations
Events (App\Event\)
AbstractProjectEvent-- base class carrying the project directoryProjectUpPreEvent,ProjectUpPostEvent-- dispatched before/after project:upProjectDownPreEvent,ProjectDownPostEvent-- dispatched before/after project:down
Hooks (App\Hook\)
HookRunner-- executes shell scripts from.dde/hooks/at lifecycle pointsHookSubscriber-- event subscriber that triggersHookRunneron project events
Plugins (App\Plugin\)
PluginLoader-- scans~/.dde/plugins/(global) and.dde/plugins/(project) for annotated shell scriptsPluginDefinition-- parsed plugin metadata (command name, description, script path)PluginProxyCommand-- wraps a plugin script as a Symfony Console command, registered asproject:{name}PluginCommandLoader-- lazy command loader that integrates plugins into the Symfony application
Output (App\Output\)
OutputFormatterInterface-- contract for output formatting (success, error, table, isInteractive)TextFormatter-- human-readable console output with Symfony stylingJsonFormatter-- structured JSON outputOutputFormat-- enum of the supported--outputvaluesFormatterResolver-- resolves and caches the active formatter
Event Listeners (App\EventListener\)
OutputFormatListener-- validates the--outputoption and configures the formatter on every commandSystemInstallCheckListener-- warns when commands run beforesystem:installcompleted
Utilities (App\Util\)
ComposeEnvEntryParser-- composeenvironment:entry normalisationDockerComposeModifier-- persistent modifications to a project'sdocker-compose.ymlduringproject:init: adds Traefik labels, injectsDATABASE_URL/MAILER_DSNfor detected dde services, migratesVIRTUAL_HOST/VIRTUAL_PORT(v1) to labels, and removes v1 boilerplate that the runtime overlay injects insteadDiffUtil-- unified diffs for file comparisonsIdentifierSanitizer-- slug sanitisation for hostnames and DB identifiersNdJsonParser-- newline-delimited JSON parsing (Docker CLI output)PrivilegeEscalator-- optimistic-then-sudo wrapper for host-level writes duringsystem:installProcessFactory--symfony/processfactory used by the managersShellDetectorUtil-- detects the current shell (zsh, bash, etc.)TempFileUtil-- temporary directories/filesTtyUtil-- whether dde is attached to a terminal, i.e. whether a child process may take it overTraefikLabelGenerator-- pure generation of the Traefik v3 label set for a hostname (incl. hostname allow-list againstHost()rule injection)UrlOpenerUtil-- opens URLs in the default browser (cross-platform)
Exceptions (App\Exception\)
HookFailedException-- raised when a lifecycle hook script exits non-zero
Container Labels
Every container created via DockerManager::run() (i.e. all dde-managed system containers — Traefik, dnsmasq, Mailpit, SSH-Agent, and the versioned service containers like dde-postgres-18.3) carries:
dde.managed=true-- marker used byCleanupManageranddde system:down/cleanupto find every dde container, regardless of name.dde.service=<name>-- service identity (traefik,mailpit,postgres, …).dde.version=<version>-- only on versioned services fromSystemServiceManager.com.docker.compose.project=dde-- groups dde-managed system containers under a singleddeproject in the Docker Desktop UI. dde does not actually use docker compose for these containers, but Docker Desktop only inspects this label for grouping.
Design Principles
- Thin commands: Commands only handle CLI I/O. All logic lives in managers and services.
- Dependency injection: All classes use constructor injection with Symfony autowiring. No static methods or service locators.
#[AsCommand]registration: All commands use the attribute, no manual YAML configuration.symfony/processfor all external calls: Docker, git, mkcert, dig -- all external tools are called viaProcess, and only from manager classes. Noshell_exec()orexec().- Strict types: Every file declares
strict_types=1. - Readonly where possible: Value objects and services use
readonlyproperties. - PHP enums for fixed sets:
CheckStatus,OutputFormat, etc. — no constant lists. - Explicit return types: No implicit returns, no mixed returns without reason.
- Not
finalby default: Only leaf classes that implement an interface or extend an abstract class arefinal; pure static utilities may befinal. - Single source of truth: Domain values (e.g. DB credentials) live in the class whose responsibility they are (
DatabaseAdapter), every other caller delegates to it. No hardcoded duplicates.