Mac Docker Performance Savior: How to Losslessly Migrate Local Mounts to Docker Volumes (Using OrbStack as an Example)
As a macOS developer, have you ever run into this: your Docker container is clearly allocated only 2GB of memory, yet in macOS Activity Monitor the Docker background process (such as `OrbStack Helper` or `com.docker.hyperkit`) is using 6GB or even more memory?
/8864b5a7-bb27-446c-9991-5ba497fb91f6.webp)
🛑 Background: Why Does Memory Usage Stay So High?
As a macOS developer, have you ever run into this: your Docker container is clearly allocated only 2GB of memory, yet in macOS Activity Monitor the Docker background process (such as OrbStack Helper or com.docker.hyperkit) is using 6GB or even more?
The culprit is usually not the container itself, but the “mount style” we have gotten used to.
Bind Mount vs Docker Volume
When developing, we habitually mount a local directory straight into the container, like this:
volumes:
- ./postgres_data:/var/lib/postgresql/data # Bind Mount (mounting a local directory)
This approach (Bind Mount) is perfectly fine on Linux, but on macOS the host is macOS while the container runs inside a Linux VM, so the file system has to go through a cross-system layer of “translation” and synchronization (usually via VirtioFS or gRPC).
The consequence: when a database (such as PostgreSQL/MySQL) or an app reads and writes files frequently, macOS generates a huge file system cache to keep files in sync. This overhead is not bounded by the container’s memory limit, so the host memory gets “eaten up” and Swap spikes.
The solution: move the directories that are read and written heavily onto a Docker Named Volume. The data lives directly on the VM’s virtual disk instead of going through the macOS file system, so performance improves noticeably and memory usage drops sharply.
🛠️ Walkthrough: Losslessly Migrate Data (Using Beszel as an Example)
This article uses migrating the lightweight monitoring tool Beszel as an example.
Key point: Beszel’s data contains a public/private key pair. If you just delete the container and recreate it, the public key changes and all Agents drop offline. That is why we must migrate the data losslessly.
1. Preparation
First, enter the directory containing your docker-compose.yml and confirm the name of your current local data folder. Suppose the current configuration is:
volumes:
- ./beszel_data:/beszel_data # old local mount style
Confirm that the beszel_data folder exists locally.
2. Stop the Service
To prevent data corruption, the service must be paused before migrating:
docker-compose down
# or
docker-compose stop beszel
3. Create a New Docker Volume
We need to manually create a new “volume” to take over the data:
docker volume create beszel_data_vol
4. Do the “Move” (Core Step) 🚀
We cannot drag files into a Volume from macOS Finder (because the Volume lives inside the VM’s belly). Instead, we use a temporary alpine container as a “mover”.
Run the following command in the terminal:
docker run --rm \
-v $(pwd)/beszel_data:/old_home \
-v beszel_data_vol:/new_home \
alpine sh -c "cp -av /old_home/. /new_home/"
Command breakdown:
--rm: automatically delete this temporary container when the task finishes.-v $(pwd)/beszel_data:/old_home: mount the old data on your computer to/old_homeinside the container.-v beszel_data_vol:/new_home: mount the new volume you just created to/new_homeinside the container.alpine: use a tiny Linux image (about 5MB).cp -av ...: copy every file from the old directory into the new volume (preserving permissions and attributes).
Note: if the alpine image is not present locally, Docker will pull it automatically; just wait a few seconds.
5. Edit the Configuration File
Open docker-compose.yml and edit the volumes section:
services:
beszel:
image: 'henrygd/beszel'
# ... other configuration ...
volumes:
# 🔴 Delete or comment out the old style
# - ./beszel_data:/beszel_data
# 🟢 Change to reference the newly created volume
- beszel_data_vol:/beszel_data
# 👇 Declare this volume at the very bottom of the file
volumes:
beszel_data_vol:
external: true # tell Compose that this volume was created manually by us
6. Restart and Verify
docker-compose up -d
Log in to your Beszel dashboard and check:
- Is the historical data still there?
- Are the monitored systems still online (green)?
If everything looks right, the public key and data migrated perfectly! 🎉
📈 Before and After
After the migration you may notice:
- Memory usage: OrbStack Helper’s memory usage may drop by 30% - 50% (depending on the original read/write volume of the database).
- I/O performance: database write speed may improve by 5-10x.
- Local files: the original
./beszel_datafolder is no longer used. You can rename it as a backup and delete it once you have confirmed everything is fine, leaving you a clean project directory.
💡 Summary
For Mac users (Docker Desktop or OrbStack), “code locally, data in a Volume” is the best practice:
- Code/Config: use a Bind Mount (
- ./src:/app) so edits in your IDE take effect immediately. - Database/Data: use a Named Volume (
- db_data:/var/lib/mysql) for performance and stability.
I hope this little trick frees up a few GB of memory on your Mac!
Comments