Guide: Multi-Service Project
This guide covers projects with multiple application containers — for example a web frontend, a background worker, a scheduler, and a project-local third-party container that is not part of dde’s built-in service catalogue (databases, caches, mail).
Prerequisites
Section titled “Prerequisites”- dde installed and
dde system:doctorpassing
Architecture
Section titled “Architecture”A typical multi-service project has several containers defined in docker-compose.yml, all sharing the same codebase but running different processes:
my-app/ docker-compose.yml Dockerfile .dde/ config.yml src/ ...1. Docker Compose Configuration
Section titled “1. Docker Compose Configuration”services: web: build: context: . target: dev volumes: - .:/var/www labels: - 'traefik.enable=true' - 'traefik.http.routers.web.rule=Host(`my-app.test`)' - 'traefik.http.routers.web.tls=true'
worker: build: context: . target: dev volumes: - .:/var/www command: ["php", "bin/console", "messenger:consume", "async", "--time-limit=3600"]
scheduler: build: context: . target: dev volumes: - .:/var/www command: ["supercronic", "/etc/crontab"]
storage: image: dxflrs/garage:v1.0.1 volumes: - garage_meta:/var/lib/garage/meta - garage_data:/var/lib/garage/data - ./garage.toml:/etc/garage.toml:ro
volumes: garage_meta: garage_data:A few things to notice:
- Shared infrastructure like a database, cache, or mail server is not declared inline. Those belong in
.dde/config.ymlunderservices:(see next step) so dde can run them as versioned, machine-wide containers instead of spinning up a dedicated copy per project. storageis a project-local third-party container (Garage, an S3-compatible object store). It is declared inline precisely because it is not part of dde’s built-in service catalogue. The application reaches it atstorage:3900over the per-project network, just like any compose-defined sibling.- The file declares no
networks:block and nodde_ssh-agent_socket-dirvolume — the per-projectdde-services-<project>(ordde-services-<project>-<suffix>for a worktree) network and the SSH-Agent volume are injected by the overlay thatproject:upgenerates.
2. Initialize the Project
Section titled “2. Initialize the Project”cd ~/projects/my-appdde project:init --name=my-app --services=mariadb,valkey --container=web --shell=bashThe --container=web flag sets web as the default container for dde project:shell and dde project:exec.
3. dde Configuration
Section titled “3. dde Configuration”.dde/config.yml:
name: my-appservices: - mariadb - valkeycontainers: web: shell: bash4. How dde Handles Multiple Services
Section titled “4. How dde Handles Multiple Services”When dde project:up runs, it generates a docker-compose override for every service in the compose file. Every service gets:
- Attached to the per-project
dde-services-<project>(ordde-services-<project>-<suffix>for a worktree) network - A
dde.managed=truelabel
In addition, services whose image has a shell (web, worker, scheduler in this example) also get:
- The entrypoint set to
/dde/entrypoint.sh, with the original entrypoint and command from the image preserved as the newcommand - The built-in and project adapters mounted
DDE_UID,DDE_GID, andSSH_AUTH_SOCKenvironment variables- The shared SSH-Agent socket volume mounted at
/tmp/ssh-agent
This means all shell-bearing application containers (web, worker, scheduler) get the dde user created with matching UID/GID, regardless of which one is configured as the default container. Shell-less images like the storage (Garage) container are left otherwise untouched — the dde entrypoint would fail on them, and there is no interactive shell to attach SSH keys to anyway.
5. Execute Commands in Specific Containers
Section titled “5. Execute Commands in Specific Containers”Use the --service (-s) option to target a specific service:
# Run in web container (default, configured via --container in project:init)dde project:exec php bin/console cache:clear
# Run in worker containerdde project:exec -s worker -- php bin/console messenger:consume --limit=10
# Run in scheduler containerdde project:exec -s scheduler cat /etc/crontab6. View Logs Across Services
Section titled “6. View Logs Across Services”# All containersdde project:logs
# Specific containerdde project:logs -s worker
# Follow logsdde project:logs --follow -s worker7. Traefik Labels for Multiple Web Services
Section titled “7. Traefik Labels for Multiple Web Services”If multiple containers serve HTTP traffic, add Traefik labels to each:
services: web: labels: - 'traefik.enable=true' - 'traefik.http.routers.web.rule=Host(`my-app.test`)' - 'traefik.http.routers.web.tls=true'
api: labels: - 'traefik.enable=true' - 'traefik.http.routers.api.rule=Host(`api.my-app.test`)' - 'traefik.http.routers.api.tls=true'Both hostnames receive trusted TLS certificates from dde’s certificate manager.
8. Hooks and Multi-Service Projects
Section titled “8. Hooks and Multi-Service Projects”Hooks run on the host machine and can interact with any container:
#!/usr/bin/env bashset -euo pipefail
# Run migrations in web containerdde project:exec -s web php bin/console doctrine:migrations:migrate --no-interaction
# Warm up caches in all relevant containersdde project:exec -s web php bin/console cache:warmupdde project:exec -s worker php bin/console cache:warmupRelated
Section titled “Related”- Project Lifecycle — up, down, restart commands
- Project Exec — executing commands in containers
- Hooks — lifecycle hook scripts