Volumes, Sync, OPcache and the Hyva Watcher
A slow Magento Docker environment drags down every development cycle. With the right volume strategy, Mutagen sync, correctly configured OPcache, and a running Hyva watcher, perceived speed improves dramatically, without giving up the isolation benefits of Docker.
Table of Contents
- 1. The Performance Problem with Magento in Docker
- 2. Volume Types and Their Impact on Magento
- 3. Mutagen Sync: File Synchronization Without Bind Mount Overhead
- 4. Configuring OPcache Correctly in Docker
- 5. Excluding the Generated Folder and var Cache from Sync
- 6. Running the Hyva CSS Watcher Inside the Container
- 7. Building the Optimal Development Compose Override
- 8. Combining Xdebug and OPcache in Development Mode
- 9. Volume Strategies Compared
- 10. Summary
- 11. FAQ
1. The Performance Problem with Magento in Docker
Anyone running Magento in Docker knows the problem: a simple page request that takes under a second on the host system takes three to five seconds inside a container with a bind mount. The reason isn't Magento itself, but the way Docker bridges file I/O between the host filesystem and the container filesystem on macOS and Windows. In a Magento project with tens of thousands of PHP files, every request triggers hundreds of stat calls, each of which has to pass through the virtualization layer.
The result isn't just a slow page request, but also a sluggish development cycle: bin/magento commands feel like they take forever, the Composer install takes minutes instead of seconds, and the Hyva Tailwind build stalls during file watching. Anyone who wants to develop productively with Magento in Docker has to address this I/O layer directly. The good news: with the right combination of volume strategy, sync tool, and OPcache configuration, the problem largely disappears.
2. Volume Types and Their Impact on Magento
Docker has three main volume types: bind mounts, named volumes, and tmpfs. For Magento in Docker, each type has a different use case. Bind mounts (./src:/var/www/html) are the simplest solution: code on the host is immediately visible inside the container. The downside is significant I/O overhead on macOS, because the osxfs or VirtioFS layer has to coordinate every file access. Named volumes, on the other hand, live entirely inside the container filesystem, which means native I/O speed, but code synchronization then requires a separate mechanism.
A hybrid strategy works well for Magento in Docker: the source code lives in a named volume with fast I/O, a sync tool keeps it up to date, and only a few selected directories such as app/code and app/design are mounted as bind mounts. Directories that generate a lot of small writes, such as var/cache, var/page_cache, and generated, get their own named volumes or even tmpfs mounts, so these hot paths aren't slowed down by the sync layer.
# compose.dev.yaml: hybrid volume strategy for Magento in Docker
services:
phpfpm:
volumes:
# Main source: named volume for fast I/O
- magento-src:/var/www/html
# Developer code: bind-mount only active development dirs
- ./src/app/code:/var/www/html/app/code:delegated
- ./src/app/design:/var/www/html/app/design:delegated
# Fast scratch volumes: native container I/O
- magento-var:/var/www/html/var
- magento-generated:/var/www/html/generated
- magento-static:/var/www/html/pub/static
# tmpfs for session and cache hot path
- type: tmpfs
target: /var/www/html/var/cache
- type: tmpfs
target: /var/www/html/var/page_cache
volumes:
magento-src:
magento-var:
magento-generated:
magento-static:
3. Mutagen Sync: File Synchronization Without Bind Mount Overhead
Mutagen is a file synchronization tool built specifically for fast bidirectional syncing between host and container, and it's integrated as a standard component in modern Mark Shust Docker setups. Instead of a bind mount, the source code is handled as a named volume. Mutagen keeps the host and the container volume in async sync: changes on the host are transferred in the background, without every file I/O operation having to cross the slow cross OS layer. The result for Magento in Docker: page load times that are barely distinguishable from a native setup.
Configuration happens through a mutagen.yml file, which defines the sync interval, ignore patterns, and conflict resolution. Important for Magento in Docker: directories such as var, generated, and pub/static belong on Mutagen's ignore list, since they are generated exclusively inside the container and don't need bidirectional sync. Only the directories developers actively work in (app/code, app/design, template files) need to stay in sync. This significantly reduces the sync load and keeps Mutagen responsive.
4. Configuring OPcache Correctly in Docker
OPcache is the single biggest performance factor for Magento in Docker. Without OPcache, every PHP file is reparsed and recompiled on every request, and with Magento's deep inheritance hierarchy that often means over a thousand files for a single request. OPcache caches the compiled bytecode in shared memory and dramatically reduces PHP overhead. The problem during development: when OPcache is active, Magento doesn't see code changes immediately, because the cache still holds the old version.
The solution for Magento in Docker is a development-specific OPcache configuration that enables revalidation. opcache.validate_timestamps=1 together with opcache.revalidate_freq=0 makes OPcache check on every request whether a file on disk is newer than the cached version. That costs one stat syscall per file, but it's still much faster than running without a cache at all. In production, validate_timestamps is disabled and the cache is invalidated manually after each deploy.
# php/conf.d/opcache-dev.ini: OPcache config for Magento development in Docker
[opcache]
; Enable OPcache: essential for Magento performance even in dev
opcache.enable=1
opcache.enable_cli=1
; Memory: Magento needs at least 512 MB for full codebase
opcache.memory_consumption=512
opcache.interned_strings_buffer=64
opcache.max_accelerated_files=130986
; Development mode: revalidate every request (0 = check every time)
opcache.validate_timestamps=1
opcache.revalidate_freq=0
; Disable JIT in development: easier debugging, consistent behavior
opcache.jit=0
# php/conf.d/opcache-prod.ini: OPcache config for production builds
[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=512
opcache.interned_strings_buffer=64
opcache.max_accelerated_files=130986
; Production: never revalidate, invalidate manually after deploy
opcache.validate_timestamps=0
; Enable JIT for PHP 8.x: significant throughput improvement
opcache.jit=1255
opcache.jit_buffer_size=256M
5. Excluding the Generated Folder and var Cache from Sync
The generated folder in Magento in Docker is one of the biggest performance killers when it's part of a bind mount. Magento generates interceptor classes, factory classes, and proxy objects here, and di:compile alone produces tens of thousands of small PHP files. Writing and reading each of these files through a bind mount means massive I/O overhead. The same applies to var/cache, var/page_cache, and pub/static: these directories are relevant only to the container and should never end up on the host.
The correct strategy for Magento in Docker is to explicitly override these directories with their own named volumes after the main source code mount has been defined. In Docker Compose, a more specific volume mount overrides a broader one. If ./src:/var/www/html is defined as a bind mount and magento-generated:/var/www/html/generated is defined afterward as a named volume, reads and writes to generated are served directly from the container filesystem, without ever touching the host.
6. Running the Hyva CSS Watcher Inside the Container
The Hyva Tailwind watcher observes changes in template files and rebuilds the CSS as soon as a file changes. In a Magento in Docker setup with Mutagen sync, this works smoothly: the watcher runs as its own process inside the container, watches the filesystem within the container, and reacts to changes that Mutagen has synced from the host. The watcher process itself doesn't need its own bind mount for template files, as long as Mutagen reliably syncs the app/design folder.
For Magento in Docker, a dedicated Compose service for the watcher is recommended, one that starts automatically when bin/start runs. The watcher service shares the Magento volume with the PHP-FPM container and runs the npm build script. Since the process doesn't need to accept incoming connections, it doesn't need a port and can run on minimal resources. A restart: unless-stopped setting ensures the watcher restarts automatically after a crash.
# compose.dev.yaml: Hyva CSS watcher as dedicated service
services:
hyva-watcher:
build:
context: .
target: node-watcher
volumes:
# Share the same source volume as phpfpm
- magento-src:/var/www/html
# Bind-mount only the theme being developed
- ./src/app/design:/var/www/html/app/design:delegated
working_dir: /var/www/html/app/design/frontend/Mironsoft/default/web/tailwind
command: npm run watch
restart: unless-stopped
environment:
# Disable polling: use native inotify inside container
CHOKIDAR_USEPOLLING: "0"
depends_on:
- phpfpm
# Dockerfile target for the watcher
# FROM node:20-alpine AS node-watcher
# RUN apk add --no-cache inotify-tools
# WORKDIR /var/www/html
7. Building the Optimal Development Compose Override
A well-structured Compose override for Magento in Docker separates production-relevant configuration from development-specific adjustments. The compose.yaml defines the base infrastructure with named volumes, networks, and services, without any development extras. The compose.dev.yaml overrides only what needs to differ in development: bind mounts for active development directories, the Xdebug PHP configuration, the Hyva watcher service, and more relaxed resource limits. This override pattern lets you spin up a complete development environment with docker compose -f compose.yaml -f compose.dev.yaml up.
For Magento in Docker with a Mark Shust setup, the bin/start wrapper script takes care of calling the Compose files correctly, so developers don't have to type out the long Compose command chain every time. Important: the order of volume definitions in the override matters. Docker processes them top to bottom, and later entries override earlier ones for the same container path. This is what allows generated and var to be redirected into named volumes while the rest of the source code stays available as a bind mount.
8. Combining Xdebug and OPcache in Development Mode
Xdebug and OPcache aren't mutually exclusive, even though that's often assumed. For Magento in Docker, the recommended configuration is: OPcache always active with validate_timestamps=1, and Xdebug in develop mode, which improves var_dump output but doesn't add debugger overhead. Only when actively debugging do you switch Xdebug to debug mode, using an environment variable that can be set without restarting the container.
The Mark Shust setup provides the bin/xdebug enable|disable wrapper script, which switches the Xdebug mode inside the running container. Internally, it writes the Xdebug configuration file and restarts PHP-FPM. At that moment, OPcache can also be flushed briefly (bin/magento cache:flush), so the first request after enabling the debugger doesn't fail on stale cache entries. This combination lets you develop Magento in Docker at normal speed and only enable the debugger when you actually need it.
9. Volume Strategies Compared
The choice of volume strategy affects both development speed and setup complexity in Magento in Docker. The table below shows the main approaches along with their advantages and disadvantages.
| Strategy | I/O Speed | Complexity | Recommendation |
|---|---|---|---|
| Full bind mount | Slow (macOS/Win) | Minimal | Linux host only |
| Delegated bind mount | Moderate | Low | macOS without Mutagen |
| Mutagen + named volumes | Native | Medium | Recommended for Magento |
| tmpfs for var/cache | Maximum | Low | Always for cache dirs |
| Hybrid strategy | Native for critical paths | Medium | Best overall solution |
On Linux hosts, the I/O problem disappears entirely, since Docker operates directly on the native filesystem there. On macOS and Windows, Mutagen is the decisive improvement. The hybrid strategy, named volumes for the bulk of the source code, bind mounts only for active development directories, tmpfs for cache hot paths, delivers the best result in practice for Magento in Docker.
Mironsoft
Magento Docker performance, Hyva development, and DevOps infrastructure
Magento in Docker too slow?
We analyze your Docker setup, identify performance bottlenecks in volumes and OPcache, and implement a hybrid volume strategy that makes Magento run natively fast inside the container.
Performance Audit
Analyze and optimize volume strategy, OPcache configuration, and sync setup
Mutagen Setup
Set up and configure Mutagen integration for existing Mark Shust setups
Hyva Integration
Optimize the CSS watcher, Tailwind build, and Hyva development workflow inside the container
10. Summary
Speeding up Magento in Docker isn't a single fix, it's a combination of targeted improvements across several areas. The volume strategy is the most important lever: replace bind mounts for critical paths like generated and var/cache with named volumes or tmpfs, use Mutagen for source code sync, and expose only actively edited directories as bind mounts. OPcache with validate_timestamps=1 and revalidate_freq=0 keeps PHP overhead minimal without hiding code changes.
The Hyva watcher runs as its own service inside the container and benefits directly from Mutagen's fast sync. Xdebug stays in develop mode until active debugging is actually needed. The result is a Magento in Docker environment that no longer feels like a slow container during daily development work, but like a native installation, complete with all the benefits of containerization: isolation, reproducibility, and easy onboarding.
Speeding Up Magento in Docker: The Key Points at a Glance
Volume Strategy
Named volumes for source code, tmpfs for var/cache and generated. Bind mounts only for actively edited directories like app/code and app/design.
Mutagen Sync
Asynchronous file sync between host and container volume. Keeps tens of thousands of Magento files in sync without the macOS bind mount overhead.
OPcache Configuration
validate_timestamps=1 + revalidate_freq=0 in development. At least 512 MB memory, max_accelerated_files set to 130986 for the Magento codebase.
Hyva Watcher
Its own Compose service with restart: unless-stopped. CHOKIDAR_USEPOLLING disabled so inotify reacts natively inside the container.