Search by

lakedrops / docker4drupal

jurgenhaas

Composer Plugin to prepare local Drupal development environment for Docker.

Package info

gitlab.lakedrops.com/composer/plugin/docker4drupal

Issues

Documentation

Type:composer-plugin

pkg:composer/lakedrops/docker4drupal

Statistics

Installs: 51 809

Dependents: 3

Suggesters: 0

v3.10.104 2026-11-14 12:15 UTC

This package is auto-updated.

Last update: 2026-11-14 12:15:37 UTC


README

Composer plugin that renders Docker Compose services for Drupal projects.

Public and private service domains

DOCKER4DRUPAL_TRAEFIK_PRIVATE_DOMAIN affects CI builds only, including Build Prod Site NG (LAKEDROPS_BUILD_NG=yes). When set, auxiliary services use <service>-<project>.<private domain>. The Drupal web server retains its configured project domain. Local development and L3D sandboxes ignore the private domain variable.

Invoice Ninja always stays public in Build Prod Site NG, including staging stacks built by that job. Its router and APP_URL both use invoice-<public project domain>, preserving the URL from before v3.10.98. For example, invoice-main-example.example.com does not change when a runner adds a private domain. Other auxiliary services remain private. The public project domain comes from the existing project configuration; this change does not construct a different public hostname or add a second router.

Non-production pipeline previews still use the private domain for Invoice Ninja, including their container host mappings. They are test environments, not the customer-facing production application.

This is an explicit, non-configurable exception: servicedomain holds the shared auxiliary domain, while the computed invoicedomain holds Invoice Ninja's domain. A configurable per-service exposure switch would let a project accidentally violate the requirement that customer-facing Invoice Ninja stays public.

OpenTelemetry endpoint address

DOCKER4DRUPAL_OPENTELEMETRY_ADDRESS is the IPv4 address that the php container uses for the host of OPENTELEMETRY_ENDPOINT, the OTLP endpoint that lakedrops/drupal-environment configures for the drupal/opentelemetry module. It is meant for a log server that admits NetBird peers only while its name still resolves to a public address, for example DOCKER4DRUPAL_OPENTELEMETRY_ADDRESS=100.64.104.21 with OPENTELEMETRY_ENDPOINT=https://logs.lakedrops.com/api/default. php then gets the entry logs.lakedrops.com:100.64.104.21 in its extra_hosts, in the same list as the names of LOCAL_IP and the private domain.

The mapping applies to pipeline builds, Build Prod Site NG, and local development, where L3D exports the variables of ~/.env and the project's .env. L3D sandboxes ignore it. Without the variable, or without a host in OPENTELEMETRY_ENDPOINT, nothing changes. An address that is not IPv4 is ignored with a warning.

The log server's own host. There, a request from a container to one of the host's addresses arrives at Traefik from the gateway of traefik-public, which Traefik refuses for NetBird-only names. A pipeline build recognizes this host when the variable equals the runner's DOCKER4DRUPAL_TRAEFIK_NETBIRD_ADDRESS: php joins traefik-public and maps the endpoint's host to Traefik's address there, never to the gateway. As with the private domain, gw_priority: 1 on the project's default network of php and the web server keeps service names such as php resolving to the own project once a php of another project joins traefik-public. This needs Docker 28 and Compose 2.33 or later. The web servers of stacks rendered without it on the same host, by older releases of this plugin or without these variables, have no such priority.

Build Prod Site NG always uses the address as given. DeployNG writes the gateway of traefik-public, not Traefik's address, for TRAEFIK-IP-PLACEHOLDER, so live and staging stacks on the log server's own host cannot reach a NetBird-only endpoint there. Such stacks are not supported.

Invoice Ninja database

in-db runs MariaDB 12.3 LTS (supported until June 2029) and keeps its data in in/mariadb. Versions up to 3.10.100 used MySQL 5.7 with data in in/db. Invoice Ninja 5.13 needs window functions and REGEXP_REPLACE, and Laravel's database queue uses SKIP LOCKED only on MySQL 8.0.1 or later and MariaDB 10.6 or later; without it, queue workers deadlock on the jobs table.

Its healthcheck pings the server over TCP with the root password from the container's environment, not with the image's healthcheck.sh. In pipeline workspaces with default ACLs, the image's .my-healthcheck.cnf becomes world-writable, so the client ignores it and healthcheck.sh fails with Access denied. TCP also keeps the temporary server of the initialization, which runs without networking, from counting as healthy.

The new release does not read in/db. An existing project starts with an empty Invoice Ninja database until its data is migrated; in/db stays untouched as the rollback copy. To migrate, replacing <root password> with the MYSQL_ROOT_PASSWORD of in-db:

  1. With the old stack running, stop in-app (docker compose stop in-app), then dump the database:

    docker compose exec -T in-db mysqldump -u root -p'<root password>' \
      --single-transaction --routines --triggers --hex-blob \
      --default-character-set=utf8mb4 ninja > ninja.sql
    
  2. Update the plugin and regenerate docker-compose.yml.

  3. Start only the database, wait until docker compose ps in-db reports it healthy, then import the dump:

    docker compose up -d in-db
    docker compose exec -T in-db mariadb -u root -p'<root password>' ninja < ninja.sql
    
  4. Start the remaining services with docker compose up -d; in-app runs its migrations on startup.

  5. Verify Invoice Ninja, then remove in/db.

Deployments that start all services at once, such as Build Prod Site NG, give no chance to import between steps 3 and 4. Pre-seed in/mariadb before deploying instead: run a temporary container with the same image, environment, command, and data mount as in-db, import the dump there, and stop it cleanly.

Production jobs install the newest release, so migrate each Invoice Ninja production stack in the same window as the deployment that picks up this release.

AI agent settings

The plugin writes no AI agent settings by default. When enabled, it writes .claude/mcp.json, .ai/template.md, and .ai/prepare.md, and adds /.claude/ to .gitignore; ahoy d4d aiprepare then shows how to prepare AGENTS.md with them. Enable it for a project in .lakedrops.yml:

docker4drupal:
  ai:
    enable: 1

The same key in .lakedrops.user.yml enables it for one developer only. Alternatively, set DOCKER4DRUPAL_AI=1 in the project's .env, where the plugin records the default DOCKER4DRUPAL_AI=0 on its first run. Then run ahoy d4d update.

Projects that already have these files keep them: without the setting, the plugin only stops writing them. Remove them, and the /.claude/ entry in .gitignore, manually if they are no longer wanted.

Tests

Install dependencies without activating Composer plugins in this plugin's own checkout, then run the integration test:

composer install --no-interaction --no-scripts --no-plugins
composer --no-plugins test
composer --no-plugins validate --no-check-publish
for file in src/*.php tests/*.php; do php -l "$file" || exit; done

On an L3D host, run these commands through l3d .. The test uses the real configuration class and Twig template, parses the generated YAML, and checks production with and without a private domain, production staging, pipeline previews, local development, and sandboxes. It checks the Invoice Ninja router and APP_URL together, other auxiliary routes, and pipeline host mappings. In the same contexts, it checks that in-db runs MariaDB with a healthcheck that pings over TCP without healthcheck.sh, keeps its data in in/mariadb, never in/db, and that in-app waits for a healthy in-db. For DOCKER4DRUPAL_OPENTELEMETRY_ADDRESS, it checks the entry and the networks of php in each of these contexts, on the log server's own host and on another one, and that the file stays the same without the variable, without a host in the endpoint, or with an address that is not IPv4. The Service domain regression job runs these checks, Drupal/DrupalPractice coding standards, and PHPStan (using phpstan.neon) in develop and feature/* branch pipelines. Its lint tools are installed separately so they do not alter the plugin's dependencies. The tool installation sets COMPOSER_BIN_DIR explicitly to .qa/vendor/bin: shared Composer artifacts export a different bin directory, which must not redirect these isolated tools into vendor/bin.

The shared validate stage runs in branch pipelines for develop and feature/*. Merge requests get no pipeline of their own, so a merge request reflects the pipeline of its source branch. Use a feature/<issue>-<description> branch and require both its full validate stage and Service domain regression job before merging; branches outside feature/* run neither check.

Release flow

Feature and fix merge requests target develop. The shared CI configuration runs Publish Release on develop unless SKIP_PUBLISH_RELEASE=1; that job clones develop and invokes the shared merge command for the release merge. The resulting Merging develop into main merge request is separate from the feature/fix MR.

The main pipeline's Tag Release job recognizes a commit beginning Merge branch 'develop' into 'main' (also release into main, or a commit containing [tag release]). It runs gen-semver and creates the generated tag on main. [MAJOR_VERSION], [MINOR_VERSION], [PATCH_VERSION], and [REVISION_VERSION] select an explicit increment; skip-tag suppresses tagging. The tag pipeline's Build Package Tag job publishes the tag to GitLab's Composer registry. Do not merge the fix or the release MR, or trigger tagging, until the change has been reviewed.

After a reviewed release, rebuild the production Compose file and deploy it to restore the public Invoice Ninja route. Production jobs install the newest release; pipeline builds follow each project's lock file. Reverting the change would restore the private production route and break the public Invoice Ninja URL when the private domain is set.