How the DDEV Hooks System Executes Pre-Config and Post-Start Events

DDEV runs pre-config hooks before interactive configuration prompts and post-start hooks after container initialization via the ProcessHooks function in pkg/ddevapp/ddevapp.go, validating task types against a whitelist in pkg/ddevapp/config.go while supporting containerized exec, host-level exec-host, and composer commands.

The DDEV hooks system enables developers to automate setup tasks and custom workflows at precise points in the project lifecycle. According to the ddev/ddev source code, hooks are declared in .ddev/config.yaml and processed by a centralized execution engine that handles YAML validation, container context detection, and granular error handling for lifecycle events.

Declaring Pre-Config and Post-Start Hooks

Hooks are defined under the hooks key in .ddev/config.yaml as a map of event names to task lists. Each task specifies an action type and command string.

hooks:
  pre-config:
    - exec-host: "touch .ddev/ready-pre-config"
  post-start:
    - exec: "drush status"
    - exec-host: "echo 'DDEV containers are ready'"

The pre-config event triggers before the configuration wizard or non-interactive ddev config execution, while post-start fires after all containers are running and networks are configured. The complete schema for hook syntax is documented in docs/content/users/configuration/hooks.md.

Hook Validation in pkg/ddevapp/config.go

Before execution, DDEV validates hook definitions against hardcoded whitelists. The validateHookYAML function (lines 1820‑1848 in pkg/ddevapp/config.go) verifies that:

  • Hook names match the validHooks slice (including pre-config, post-start, pre-start, and others)
  • Task keys are limited to supported types: exec, exec-host, and composer

Invalid hook names or task types trigger an immediate error before any command runs, preventing runtime failures due to typos in configuration.

The Hook Execution Engine (ProcessHooks)

The core execution logic resides in ProcessHooks at lines 85‑124 of pkg/ddevapp/ddevapp.go. This function implements the following sequence:

  1. Global Skip Check – If SkipHooks is set to true, the function returns immediately without executing tasks.
  2. Task Retrieval – Loads the task slice from app.Hooks[hookName].
  3. Iteration and Execution – For each task, DDEV creates a Task object via NewTask(app, c) and calls a.Execute().
  4. Pre-Start Restriction – The engine enforces that pre-start hooks may only contain exec-host tasks; exec and composer tasks are forbidden at this stage because containers are not yet running.
  5. Error Handling – If app.FailOnHookFail or the global equivalent is enabled, any task error aborts the entire DDEV command. Otherwise, errors are logged as warnings and execution continues.

The Task abstraction in pkg/ddevapp/task.go handles the three execution contexts: running commands inside the web container (exec), on the host machine (exec-host), or via Composer (composer).

When Pre-Config Hooks Execute

The config command (cmd/ddev/cmd/config.go) invokes pre-config hooks at lines 68‑70 immediately after the project object is instantiated but before any interactive prompting or file writing occurs:

err = app.ProcessHooks("pre-config")

This placement allows developers to modify project settings or prepare the environment before DDEV collects configuration data. If fail-on-hook-fail is enabled and a task exits with an error, the configuration process aborts immediately.

When Post-Start Hooks Execute

During ddev start, DDEV invokes hooks at two distinct points in pkg/ddevapp/ddevapp.go:

  • Pre-start (lines 151‑155): Runs after Docker setup but before health checks, restricted to exec-host tasks only.
  • Post-start (lines 2087‑2089): Runs after all containers are initialized and healthy.

The post-start event sees fully running services, enabling exec tasks to run inside the target container (defaulting to the web container) and exec-host tasks to execute on the host system. This is the appropriate hook for database imports, cache warming, or application installation routines.

Example: Automating WordPress Installation

The following configuration demonstrates practical use of post-start hooks for automated setup:


# .ddev/config.yaml

hooks:
  post-start:
    - exec: "wp core install --url=${DDEV_HOSTNAME} --title='My Site' --admin_user=admin --admin_password=admin [email protected]"
    - exec-host: "echo 'WordPress installed at $(date)' >> /tmp/ddev.log"

DDEV parses this YAML, validates the hook name against validHooks, creates two Task objects, and executes them sequentially. The first command runs inside the web container using the WordPress CLI, while the second appends a timestamped log entry on the host machine.

Summary

  • Hook Declaration: Define pre-config and post-start tasks in .ddev/config.yaml using exec, exec-host, or composer keys.
  • Validation: pkg/ddevapp/config.go (lines 1820‑1848) enforces valid hook names and task types before execution.
  • Execution Engine: ProcessHooks in pkg/ddevapp/ddevapp.go (lines 85‑124) iterates through tasks, respecting the SkipHooks flag and FailOnHookFail settings.
  • Pre-Config Timing: Runs in cmd/ddev/cmd/config.go (lines 68‑70) before interactive prompts.
  • Post-Start Timing: Runs in pkg/ddevapp/ddevapp.go (lines 2087‑2089) after container health checks pass.
  • Container Context: exec runs inside containers; exec-host runs on the host; pre-start restricts to exec-host only.

Frequently Asked Questions

How do I skip hooks during a DDEV command?

Set the global SkipHooks boolean to true or use the --skip-hooks flag when available. When ProcessHooks detects this flag at line 85 of pkg/ddevapp/ddevapp.go, it returns immediately without executing any tasks defined in .ddev/config.yaml.

Why does my pre-start hook fail when using exec commands?

The pre-start hook runs before containers exist, so DDEV enforces a strict limitation in ProcessHooks allowing only exec-host tasks. Attempting to use exec or composer in pre-start triggers a validation error because there is no running container to receive the command.

What happens if a post-start hook fails?

Behavior depends on the fail-on-hook-fail configuration. If enabled in .ddev/config.yaml or globally, ProcessHooks returns the error immediately and aborts the ddev start command. If disabled, DDEV logs the error, prints a warning to the console, and continues with the remaining tasks and startup sequence.

Where are valid hook names defined?

The whitelist of valid hook names including pre-config, post-start, pre-start, and others is defined in the validHooks slice within pkg/ddevapp/config.go at lines 1820‑1848. This array is referenced by validateHookYAML to ensure only supported lifecycle events are processed by the hook engine.

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 →