DDEV Composer Integration and Project Scaffolding: A Technical Guide

DDEV provides first-class Composer integration through manifest abstraction, configurable project roots, version-controlled container builds, and automated task execution that supports both dependency management and project scaffolding workflows.

DDEV offers comprehensive Composer integration for PHP projects, allowing developers to manage dependencies, scaffold new applications, and automate workflows entirely within containerized environments. According to the ddev/ddev source code, this system relies on a manifest-based architecture and specialized task handlers that bridge Composer operations with DDEV's lifecycle management.

Core Architecture of DDEV Composer Integration

Composer Manifest Abstraction

In pkg/composer/composer.go, DDEV implements a Composer manifest that reads composer.json and its associated vendor/ directory. This layer exposes helper methods including GetVendorDir() and HasComposerFile(), enabling DDEV to programmatically inspect project dependencies regardless of file system location.

ComposerTask Execution Model

The ComposerTask type defined in pkg/ddevapp/task.go represents Composer operations within DDEV's hook system. It parses both the legacy composer: install string syntax and the modern list-style composer: [install] format, forwarding resolved commands to the web container with correct working directory context.

Application Configuration Fields

The DdevApp struct in pkg/ddevapp/ddevapp.go maintains two critical fields: ComposerRoot (the relative path to the directory containing composer.json) and ComposerVersion (the specific version to install inside the container). These fields drive path resolution and container preparation throughout the application lifecycle.

Version Handling and Dockerfile Injection

DDEV handles Composer version management in pkg/ddevapp/config.go through the WriteBuildDockerfile function. During container builds, DDEV injects self-update logic:

RUN export XDEBUG_MODE=off; composer self-update --stable || \
    composer self-update --stable || true; \
    composer self-update {{ .composer_version }} || \
    log-stderr.sh composer self-update {{ .composer_version }} || true

The version string derives from app.ComposerVersion, defaulting to "stable" when unspecified. The system also emits warnings when detecting Composer 1.x versions due to Packagist compatibility issues.

Template Integration

Container templates in pkg/ddevapp/templates.go include placeholders {{ .composer_root }} and {{ .composer_version }} that DDEV replaces during ddev start, ensuring the web container launches with pre-configured Composer settings.

Configuring composer_root for Subdirectory Projects

For monorepos or frameworks like Symfony Flex where composer.json resides in a subdirectory, configure composer_root in .ddev/config.yaml:

composer_root: subfolder
composer_version: "2"

DDEV resolves this through GetComposerRoot() (utilized across the codebase including shopware6.go and verified by shopware6_test.go), constructing the full path via filepath.Join(app.AppRoot, app.ComposerRoot, "composer.json"). The system places .env.local in the calculated Composer root and adjusts the web container's working directory for all subsequent Composer commands.

CLI Commands and Project Scaffolding

Direct Composer Command Forwarding

The ddev composer implementation in cmd/ddev/cmd/composer.go forwards any sub-command to the web container, executing within the context of composer_root:

ddev composer install
ddev composer require guzzlehttp/guzzle

Scaffolding New Projects

For project initialization, cmd/ddev/cmd/composer-create-project.go implements ddev composer create-project, a wrapper around Composer's scaffolding that respects both composer_root and composer_version settings. After scaffolding completes, DDEV automatically re-detects the application type (Drupal, Laravel, etc.) and configures appropriate web container settings.

ddev composer create-project drupal/recommended-project . --no-interaction

Automating Workflows with Composer Hooks

Composer tasks integrate into DDEV's hook system via pkg/ddevapp/task.go. Define automated execution in .ddev/config.yaml:

hooks:
  post-start:
    - composer: install
  pre-stop:
    - composer: clear-cache

The ComposerTask parser handles both legacy string format and modern list syntax, ensuring backward compatibility while supporting complex multi-step operations.

Summary

  • Manifest abstraction in pkg/composer/composer.go provides GetVendorDir() and HasComposerFile() for dependency inspection and vendor location resolution
  • Configuration fields ComposerRoot and ComposerVersion in pkg/ddevapp/ddevapp.go control project location and tooling version constraints
  • Container preparation via WriteBuildDockerfile in pkg/ddevapp/config.go injects version-specific composer self-update commands during image builds
  • Task execution through ComposerTask in pkg/ddevapp/task.go enables both manual CLI usage and automated hook integration with dual-syntax support
  • Project scaffolding via cmd/ddev/cmd/composer-create-project.go supports full application generation with automatic application type detection post-creation

Frequently Asked Questions

How does DDEV handle Composer commands when composer.json is in a subdirectory?

DDEV uses the composer_root configuration setting to resolve the correct path. The GetComposerRoot() function joins app.AppRoot with app.ComposerRoot to locate composer.json via filepath.Join(), ensuring commands execute from the proper directory and that environment files like .env.local are placed in the correct location.

Can I specify an exact Composer version for my DDEV project?

Yes. Set composer_version in .ddev/config.yaml to a specific version string such as "2.2" or "2.5". During container startup, the WriteBuildDockerfile function injects composer self-update {{ .composer_version }} into the build process, ensuring the web container contains exactly the requested version before handling any commands.

What happens when I run ddev composer create-project?

The command implementation in cmd/ddev/cmd/composer-create-project.go executes Composer's create-project inside the web container, respecting your composer_root and composer_version settings. After scaffolding completes, DDEV automatically triggers application type detection to configure appropriate web server settings for the newly created project.

Are Composer hooks in DDEV compatible with both old and new configuration syntax?

Yes. The ComposerTask implementation in pkg/ddevapp/task.go parses both the legacy composer: install string format and the modern list-style composer:\n - install\n - update format, ensuring existing configurations continue functioning while supporting complex multi-step automation sequences.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →