0% прочитано

How I Simplified My Laradock Multi-PHP Workflow with dphp, dartisan and dcomposer

I show how I configured Laradock for PHP 8.3, 8.4 and 8.5 and replaced long Docker commands with simple dphp, dartisan and dcomposer wrappers.

14 серпня 2026 р. 8 хв читанняLaradock

Why I Decided to Change My Laradock Workflow

For a long time, my local Laradock setup was very simple to use in everyday development. I started the required services once and then worked with my projects without constantly thinking about Docker.

That changed when I needed several PHP versions at the same time. Some projects remained on PHP 8.3, others were already running PHP 8.4, while newer ones moved to PHP 8.5.

I had prepared this multi-PHP environment while upgrading my local Laravel setup and package to PHP 8.5. The next problem was making that setup convenient enough for everyday CLI work.

For HTTP, this is quite natural to solve: each Nginx virtual host can use its own PHP-FPM service. CLI usage was less convenient. For Artisan, Composer, or even a simple php -v, I had to remember which workspace belonged to which project and type long Docker Compose commands.

I wanted to bring back the simple workflow without giving up a proper multi-PHP environment.

До
bash
docker compose run --rm --no-deps workspace-85 bash -lc \ 'cd /var/www/icanup && php artisan test'
Після
bash
dartisan test

Multi-PHP Architecture: Separate CLI and PHP-FPM for Each

The first important decision was not to mix the CLI runtime with PHP-FPM.

PHP-FPM handles HTTP requests through Nginx, while the workspace is used for CLI tools such as Artisan, Composer, PHPUnit, PHPStan, and other development commands.

My final Laradock setup looks like this:

PHP

CLI service

FPM service

PHP 8.3

workspace

php-fpm

PHP 8.4

workspace-84

php-fpm-84

PHP 8.5

workspace-85

php-fpm-85

PHP 8.3 remains the default runtime, while 8.4 and 8.5 run as additional parallel environments.

Why one workspace is not enough

If a site runs through php-fpm-85, that does not mean that php artisan or Composer also runs on PHP 8.5. CLI and FPM are independent runtimes. This is why my multi-PHP environment uses matching workspace, workspace-84, and workspace-85 services.

How I Configured Persistent Workspaces

I did not want to create a temporary container for every CLI command. All three workspaces therefore run as regular persistent Docker Compose services.

The main workspace keeps its host ports, while workspace-84 and workspace-85 do not publish them. This allows all three containers to run simultaneously without port conflicts.

The PHP-FPM services are also connected to their matching workspaces.

YAML
php-fpm-84:  depends_on: !override    - workspace-84      workspace-84:    ports: !reset []      php-fpm-85:    depends_on: !override      - workspace-85        workspace-85:    ports: !reset []
YAML
nginx:   depends_on:     - php-fpm    - php-fpm-84    - php-fpm-85

Matching UID and GID Between the Host and Workspace

Another problem appeared after I started using the wrappers.

The source code is bind-mounted from the host machine and belongs to my local user with UID/GID 1000:1000. Initially, the laradock user inside the containers had a different UID, causing Git to treat the repository ownership as suspicious.

Instead of adding a global safe.directory=*, I aligned the Laradock user UID/GID with the host user.

ENV
WORKSPACE_PUID=1000WORKSPACE_PGID=1000
Do not hide ownership problems with safe.directory=*

When bind-mounted files belong to UID 1000, development commands inside the container should ideally run as a user with the same UID. This solves the Git warning and also prevents root:root files from appearing in application repositories.

Local Project-to-Workspace Mapping

I cannot add local Docker runtime files to every work repository, so the PHP version mapping lives entirely outside the application projects.

I keep it in:

~/.config/laradock/projects.conf

PHP 8.3 is the default, so projects running on 8.3 do not need to be listed at all. Only projects requiring an alternative workspace are added to the mapping.

text
default=workspace # PHP 8.4/home/alyona/Projects/some-project/project-core=workspace-84 # PHP 8.5/home/alyona/Projects/icanup=workspace-85

Heading:

I did not want separate aliases such as php83, php84, php85, composer84, or composer85.

The project already has a local mapping, so the CLI can determine the correct workspace automatically.

I use one dispatcher called laradock-exec.

It:

  • detects the current project's Git root;

  • looks up the appropriate workspace in projects.conf;

  • falls back to workspace;

  • converts the host path /home/alyona/Projects/... to /var/www/...;

  • verifies that the required service exists and is running;

  • executes the command through docker compose exec;

  • runs it as the laradock user.

bash
exec docker compose exec \  --user laradock \  -w "$container_dir" \  "$service" \  "$@"

dphp, dartisan and dcomposer

On top of the dispatcher, I keep only three small wrapper commands.

dphp runs PHP in the correct workspace.

dartisan is a shortcut for php artisan.

dcomposer runs Composer on the same PHP version as the project's CLI runtime.

dphp
#!/usr/bin/env bashexec "${HOME}/.local/bin/laradock-exec" php "$@"
dartisan
#!/usr/bin/env bashexec "${HOME}/.local/bin/laradock-exec" php artisan "$@"
dcomposer
#!/usr/bin/env bashexec "${HOME}/.local/bin/laradock-exec" composer "$@"

What My Daily Workflow Looks Like Now

I can now work with a Docker-based PHP runtime almost as if PHP were installed directly for each project.

bash
cd ~/Projects/icanup dphp -vdartisan testdartisan migratedcomposer install

For IcanUp, the dispatcher automatically selects workspace-85, so all these commands run on PHP 8.5.

In another Core project, the same commands automatically use workspace-84.

Any project missing from the mapping falls back to the default workspace running PHP 8.3.

This also removes one easy source of mistakes: accidentally running a project or its tests under the wrong PHP version. I had already seen how noticeable that can become when the same round() test behaved differently on PHP 8.3 and PHP 8.4.

I no longer need to remember container names every time I run Artisan or Composer.

До
bash
cd ~/Projects/laradock docker compose exec \  --user laradock \  -w /var/www/icanup \  workspace-85 \  php artisan test
Після
bash
cd ~/Projects/icanup dartisan test

How to Add Another PHP 8.4 or PHP 8.5 Project

A new project does not need any Docker-specific files inside its repository. First, I determine its Git root:

bash
git rev-parse --show-toplevel

Then I only need to add one line to ~/.config/laradock/projects.conf.

text
# PHP 8.4/home/alyona/Projects/project=workspace-84 # PHP 8.5/home/alyona/Projects/project=workspace-85
PHP 8.3 projects do not need to be added. Because default=workspace, every project without an explicit mapping automatically runs through PHP 8.3.

The Commands Also Work from Nested Directories

The dispatcher uses git rev-parse --show-toplevel, so I do not need to be in the project root.

Even from:

~/Projects/icanup/app/Services

dphp -v detects the root at ~/Projects/icanup, selects workspace-85, and runs from /var/www/icanup inside the container.

It is a small detail, but it makes the wrapper much more useful in real daily development.

What I Ended Up With

  • PHP 8.3, 8.4, and 8.5 can run simultaneously.

  • Each PHP version has its own CLI workspace and PHP-FPM service.

  • I do not need to enter Docker containers manually.

  • No temporary workspace containers are needed.

  • No Docker runtime files are added to work repositories.

  • Artisan and Composer always use the correct PHP version.

  • Commands run with the correct UID/GID.

  • PHP 8.3 remains the default for older projects.

  • PHP 8.4 and 8.5 projects require only one local mapping line.

Docker should simplify my development environment, not make me think about which container should run php artisan every time.

Yakymiv Alyona

A multi-PHP setup should not make local development more complicated by itself.

For me, the most practical solution was to keep a clear separation between PHP-FPM and the CLI runtime, use persistent workspaces, and add a very thin local CLI layer on top of Docker Compose.

The infrastructure remains fully containerized, while my everyday workflow is reduced to the short dphp, dartisan, and dcomposer commands.