Skip to content

Add runtime functionality to allow local scripts to run in docker or other runtimes #27

Closed

Description

@gaambo

Problem

A Deployer host is responsible for deployment state, SSH, rsync, and host filesystem work. Application commands may need to execute in a different environment.

This currently breaks local database export with DDEV:

host path:      /project/data/db_dumps/site.sql
DDEV path:      /var/www/html/data/db_dumps/site.sql

db:local:backup creates and transfers the dump on the host, but passes the host path to WP-CLI running in DDEV. The container cannot create that host path.

Goal

Introduce a runtime layer for local application commands.

A runtime is attached to the localhost Deployer host, but is not itself a Deployer host. It decides:

  • Where runtime-capable commands execute.
  • Which binaries are used in that environment.
  • How explicitly identified host paths map into that environment.
  • How the working directory maps into that environment.

Without a configured runtime, behavior remains native and backward-compatible.

Initial runtime support is local DDEV only.

Configuration

localhost()->set('runtime', new DdevRuntime('/var/www/html'));

DDEV maps host paths relative to the localhost deploy_path by default.

Projects whose DDEV mount does not start at deploy_path must be able to override the host project root:

localhost()->set(
    'runtime',
    new DdevRuntime('/var/www/html', projectRoot: '/custom/host/project')
);

The exact constructor naming may be adjusted during implementation, but these are the required semantics.

Runtime API

Runtime::run() is the primary runtime-aware execution API.

Runtime::run('{{bin/wp}} core version', [
    'cwd' => Localhost::getConfig('current_path'),
]);

It uses the localhost runtime when configured. Otherwise it falls back to native Localhost::run() behavior.

Runtime::within() is additional public API. It lets custom or existing code using Localhost::run() opt into runtime execution without changing every call:

Runtime::within(function () {
    Localhost::run('{{bin/wp}} plugin status', [
        'cwd' => Localhost::getConfig('current_path'),
    ]);
});

Inside the callback, the custom Localhost::run() wrapper delegates to Runtime::run().

The context must be stack-safe and restored with try/finally, including when a command fails.

Runtime-Specific Configuration

A runtime provides context-specific values before falling back to localhost configuration.

For example:

Native execution:
bin/wp = localhost `bin/wp` configuration
execution = direct host process

DDEV execution:
bin/wp = wp, or a DDEV-specific override
execution = ddev exec ...

This applies consistently to bin/wp, bin/composer, bin/npm, and bin/php.

Do not configure bin/wp as ddev wp or another command prefix. A runtime owns the execution wrapper; binaries remain actual tools inside that runtime.

Explicit Path Mapping

Runtimes must not parse shell commands to guess which text represents a path.

Helpers receive explicit host paths through an optional runtimePaths argument:

WPCLI::runCommandLocally(
    "db export $hostDumpFile --add-drop-table",
    Localhost::getConfig('current_path'),
    runtimePaths: [$hostDumpFile],
);

The WP-CLI helper maps only paths listed in runtimePaths before calling Runtime::run().

The task identifies which values are host paths, but does not call Runtime::path() directly. Runtime details remain in the helper layer.

Commands without runtime paths remain unchanged:

WPCLI::runCommandLocally(
    "search-replace $remoteUploadsDir $localUploadsDir",
    Localhost::getConfig('current_path'),
);

The search-replace values may look like filesystem paths, but they are database values and must not be converted for DDEV.

The runtime maps cwd separately from explicit command paths.

A path outside the configured host project root must fail with a clear error. DDEV normally cannot access it, and passing it through unchanged would produce a misleading failure.

DDEV Runtime Behavior

DdevRuntime must:

  • Map local cwd into the DDEV container.
  • Map explicitly supplied host paths into the DDEV container.
  • Execute application commands through DDEV.
  • Preserve normal Deployer command output and failure behavior.
  • Use a native local execution path internally when starting ddev exec, avoiding recursive runtime interception.

Example:

host cwd:  /project
DDEV cwd:  /var/www/html

host dump: /project/data/db_dumps/site.sql
DDEV dump: /var/www/html/data/db_dumps/site.sql

Database Integration

Refactor the runtime-capable parts of these existing public tasks:

db:local:backup
db:local:import

The host/runtime boundary remains explicit.

db:local:backup:

host:    create dump directory
runtime: WP-CLI database export
host:    rsync dump file to remote

db:local:import:

host:    verify dump file exists
runtime: WP-CLI database import and search-replace commands
host:    remove dump file after successful import

Existing public task names and push/pull task dependencies must remain unchanged.

Helper Scope

The runtime mechanism is shared by WP-CLI, Composer, npm, and PHP commands.

WP-CLI is the first migration because it has existing localhost execution and is required to fix the database regression.

Composer and NPM currently execute remote commands through Deployer's run(). They should use the same runtime approach if local command APIs are added; this issue must not silently change remote deployment behavior.

Debugging

At Deployer verbose/debug levels, show runtime diagnostics such as:

[runtime:ddev] cwd: /project -> /var/www/html
[runtime:ddev] path: /project/data/db_dumps/site.sql -> /var/www/html/data/db_dumps/site.sql

Do not log secrets or otherwise expose sensitive command values beyond Deployer's existing output behavior.

Out of Scope

  • Remote runtimes.
  • Making all localhost commands run in DDEV.
  • Making a runtime into a Deployer host.
  • Named execution profiles.
  • Parsing arbitrary shell syntax to infer paths.
  • DDEV as a CI dependency.

Implementation Plan

  1. Add runtime configuration resolution with native fallback.
  2. Add Runtime::run() and public Runtime::within().
  3. Update the custom Localhost::run() wrapper to dispatch through an active runtime context.
  4. Add a native execution bypass to prevent ddev exec interception recursion.
  5. Add DdevRuntime with deploy_path-based path mapping and project-root override.
  6. Add runtime-specific binary configuration and fallback to localhost values.
  7. Refactor WPCLI::runCommandLocally() to use Runtime::run() and structured cwd.
  8. Add optional runtimePaths support to the WP-CLI helper.
  9. Refactor local database export and import to pass their dump paths through runtimePaths.
  10. Verify a read-only command such as wp core version.
  11. Document native and DDEV setup.

Acceptance Criteria

  • Users without runtime configuration retain current native behavior.
  • DDEV maps local cwd and explicitly listed dump paths correctly.
  • db:local:backup succeeds with DDEV when the dump directory is inside the mapped project root.
  • db:local:import succeeds with DDEV when the dump file is inside the mapped project root.
  • Host-side directory creation, rsync, file checks, and cleanup remain host-side.
  • Search-replace values are not path-mapped.
  • Paths outside the mapped project root fail clearly.
  • Runtime-specific bin/wp is used in DDEV while native execution retains existing bin/wp.
  • Runtime::within() makes Localhost::run() execute through the active runtime.
  • Runtime context is restored after success and failure.
  • Tests cover supported Deployer 7 and 8 behavior.

References

Activity

  1. gaambo commented on Sep 15, 2026

    @gaambo
    OwnerAuthor

    Follow-up for the next major release: drop package-wide Deployer 7 support. This should update the Composer constraint to Deployer 8+, remove v7 compatibility branches/stubs/PHPStan handling, and update CI/docs. It is intentionally separate from the runtime-host commit; runtime hosts require Deployer 8 in the meantime.

  2. gaambo commented on Sep 16, 2026

    @gaambo
    OwnerAuthor

    Runtime implementation has moved to the private gaambo/deployer-utils package. This repository now integrates the shared runtime layer, with WP-CLI and database-task changes here. The deployer-utils package will become public soon.

    See PR for v5 #28

  3. gaambo commented on Sep 16, 2026

    @gaambo
    OwnerAuthor

    Update: gaambo/deployer-utils is now public at v0.1.1. v5 consumes the public release; the temporary VCS repository setup has been removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions