New course: Adobe Commerce Cert-Prep

Get certified
Ultimate Docker Desktop Performance Guide for Mac in 2025

Ultimate Docker Desktop Performance Guide for Mac in 2025

Learn how to get 80% faster Docker performance on Mac with selective volume mounting strategies that work on both Intel and Apple Silicon.

Let me guess... you're running Docker Desktop on your shiny new Mac, you mounted your entire Magento codebase, ...and now you're wondering why your local environment feels like it's still running on an Intel Core 2 Duo chip?

I've got news for you: event that M4 chip isn't going to save you from bad Docker volume mount strategies.

The problem that won't die

I first wrote about this problem back in 2018. Since then, Docker Desktop has gone through multiple rebrandings, Apple silicon changed the game, VirtioFS became a thing, Mutagen showed up (I was a very early tester of it, but it ultimately failed the resilience test for me).

And yet... developers are still mounting their entire source directory and wondering why their 100,000+ file project runs their server is located in a sauna.

The fundamental issue hasn't changed: Docker Desktop on macOS runs through a virtualization layer. Every single file read and write has to cross that boundary. When you're dealing with frameworks like Magento that touch thousands of files per request, that overhead compounds fast.

Why your current setup is killing performance

Here's what most developers do:

volumes:
    - ./src:/var/www/html:delegated # RIP your performance

Simple, clean, everything syncs.

And it's also absolutely terrible for performance.

On a typical Magento install, this means Docker is syncing:

  • 100,000+ files and folders
  • Generated cache directories that rebuild constantly
  • Vendor folders with thousands of dependencies
  • Temp files you'll never look at
  • Static assets that get compiled and recompiled

Your page load times could be 20-30 seconds with a cold cache, maybe even more. Even with all the Docker Desktop optimizations available, and even with Apple silicon, performance can still drag.

But it's not just Magento. Symfony, Laravel, WordPress with tons of plugins... any framework with a large codebase and lots of file operations suffers the same fate.

And it has to do with how volume mounts work.

The solution: selective volume mounting

Here's the thing nobody tells you: you don't need to mount everything.

Think about it. When you're developing on Magento, where do you actually write code?

For most of us, it's basically just in the app/ directory. Everything else are just things like the library code, generated files, caches, vendor packages. It's all of the stuff you never touch.

So why are you syncing it?

Here's the approach that works (and one we run in docker-magento):

services:
    app:
        volumes:
            &appvolumes ## Host mounts with performance penalty, only put what is necessary here
            - ./src/app/code:/var/www/html/app/code:cached
            - ./src/app/design:/var/www/html/app/design:cached
            - ./src/app/etc:/var/www/html/app/etc:cached
            - ./src/composer.json:/var/www/html/composer.json:cached
            - ./src/composer.lock:/var/www/html/composer.lock:cached
            - ./src/grunt-config.json.sample:/var/www/html/grunt-config.json:cached
            - ./src/Gruntfile.js.sample:/var/www/html/Gruntfile.js:cached
            - ./src/dev/tools/grunt/configs:/var/www/html/dev/tools/grunt/configs:cached
            - ./src/nginx.conf.sample:/var/www/html/nginx.conf:cached
            - ./src/package.json.sample:/var/www/html/package.json:cached
            - ./src/generated:/var/www/html/generated:cached
            - ./src/var:/var/www/html/var:cached
            #- ./src/auth.json:/var/www/html/auth.json:cached
            #- ./src/m2-hotfixes:/var/www/html/m2-hotfixes:cached
            #- ./src/patches:/var/www/html/patches:cached

We are only mounting the directories that we are actively working in. Everything else is kept in native Docker volumes, which is an order of magnitude faster because it doesn't make the files or folders cross the virtualization boundary.

The folders you should never mount

Here are the directories that are performance killers. Keep them in native Docker volumes (don't sync them!):

  • node_modules/

    • JavaScript developers, this one's for you. Massive folder, never edited directly, absolutely murders performance when mounted.
  • pub/

    • These rebuild constantly. Every request potentially touches hundreds of files here. Native Docker volume only!
  • var/

  • vendor/

    • This is the big one. Thousands of files, tons of reads, zero edits. Your composer dependencies don't need bidirectional sync. If you're debugging a vendor package, mount that specific path temporarily.

The Setup Process

"But Mark, how do I get my files into the container if I'm not mounting them?"

Well, most of the time you will never need to, because your main code lives in app/ (or equivalent) and is bi-directionally synced.

But for the rest, you can just copy them over when needed. I've set up helper copy commands in docker-magento (see bin/copyfromcontainer and bin/copytocontainer), but you can also just copy them directly with Docker. If you're new to Docker, you might want to brush up on how to run containers first, but the process is straightforward:

# Copy everything into the container initially
docker cp ./src/. $(docker-compose ps -q php):/var/www/html/
 
# Or if you need to update vendor after a composer install
docker cp ./src/vendor $(docker-compose ps -q php):/var/www/html/

Real world performance numbers

Let me give you some real numbers from a Magento 2.4.8-p2 install with 100,000+ files:

Full mount approach: 20-30 seconds page load (cold cache)
Selective mounting: 3-4 seconds page load (cold cache)

That's over an 80% performance improvement... just by being smart about what you mount!

With warm cache? The difference is even more dramatic. Full mounting might get you down to 5-6 seconds, while selective mounting will get you down to sub-second responses.

Modern Alternatives (And Why They're Not Silver Bullets)

VirtioFS: Better than the old osxfs, sure. But it's still not native speed. Maybe you get a 20-30% improvement, but not the 80% you get from selective mounting.

Mutagen: Adds complexity, requires daemon management, and can get out of sync (which is why I've never ported it into docker-magento). Great for some workflows for projects with not many files, but it's overkill for most. Plus, now you're debugging sync issues instead of just writing code.

Docker Dev Environments: Interesting approach but locks you into Docker's ecosystem and tooling. Not always practical for existing projects.

OrbStack/Colima: Different Docker Desktop alternatives. Better in a lot of ways, but the fundamental problem with volume mounting remains. Physics is physics.

The selective mounting approach? Works everywhere, no extra tools, no complexity. It just works.

Implementation strategy for existing projects

If you're already deep into a project with full mounting, here's how to transition:

First, identify what you actually edit. You'll want to inspect your container details to understand what's currently mounted, then run this in your project:

# Find files you've modified in the last 30 days
find ./src -type f -mtime -30 | grep -v vendor | grep -v generated | grep -v var

Chances are, it's a tiny fraction of your codebase.

Next, update your compose.yaml (or docker-compose.yml for older versions) gradually:

volumes:
    # Start with a named volume
    - appdata:/var/www/html
 
    # Add selective mounts for your active development areas
    - ./src/app/code/YourVendor:/var/www/html/app/code/YourVendor:delegated
 
    # Keep adding as needed

Then, sync your initial data. If you need a refresher on working with Docker images and containers, check out the Docker Core Fundamentals playlist:

# One-time sync to populate the named volume
docker cp ./src/. $(docker-compose ps -q php):/var/www/html/

The docker-magento implementation

If you're using my docker-magento repo, you might have noticed this selective sync approach is already built in. The setup includes this exact strategy by default, which is one of the reasons it performs so much better than a vanilla Docker setup.

The automated setup script handles the initial file copy, sets up the selective mounts, and manages the sync process. This is based on my volume mount approach for performance that I've been refining for years.

The Bottom Line

Stop fighting Docker Desktop performance or waiting for the perfect solution. And definitely stop mounting your entire codebase!

Mount just what you edit, and keep everything else native. Your CPU and battery will thank you, and you might actually enjoy local development with Docker again.

It's also important to mention that this isn't a hack or a workaround! This is understanding how Docker volumes actually work and using them correctly. The fact that this approach from 2018 still works better than most "modern" solutions should tell you something.

Your M4 Mac is fast. Docker Desktop has improved drastically over the years. But physics still exists, and crossing virtualization boundaries still has overhead.

Need to check if a file exists in vendor? Use docker exec to execute commands inside your container.

Need to debug generated code? Copy it out temporarily.

Need to edit a third-party module? Mount that specific path for the debugging session, then unmount it.

The key is being intentional about what crosses the virtualization boundary. Every mounted file is a potential performance hit. Make each one count.

Ready to level up your Docker game? Here are a few resources to help:

  1. Check out the free Magento 2 Development Environment course (10,000+ students)
  2. Learn how to create a Dockerfile for a custom Docker image for more control
  3. Get bi-weekly tech stack deep-dives and stay up to date with best practices (10,000+ subscribers)