Docker on Mac Studio used to be a recurring nightmare
I love my Mac Studio. I hated Docker on it for a long time.
Random build failures. Containers chewing 30 GB of RAM. Volume performance going from “fine” to “did my machine freeze?” overnight. Every few weeks something broke after an update, a new project, or just because I dared to run another stack.
This is the setup that finally stopped breaking on my Mac Studio. Not perfect. Just boring. Which is exactly what you want from Docker.
I will walk through the actual errors I hit, the dead ends, and the final config that has been stable for months.
My hardware and baseline
For context, this is the machine:
- Mac Studio M2 Max
- 64 GB RAM
- 2 TB SSD
- macOS Sonoma (been on 14.x, now 15 beta, same story)
Docker flavor:
- Docker Desktop for Mac (Apple Silicon build)
- Colima installed, but now only for experiments
I run the usual front-end circus: Node, pnpm, Vite, Next.js, Laravel, Postgres, Redis, a couple of legacy PHP projects, and one ugly Java thing that nobody wants to touch.
The errors that kept hitting me
These are the ones that cost me the most time.
1. "no space left on device" with 800 GB free
My favorite kind of error. Complete lie. I had half a terabyte free. Docker did not care.
It usually happened when building multiple images in a row, or when I ran a local registry plus a few heavier services.
Typical log line:
failed to copy: write /var/lib/docker/...: no space left on device
2. Containers randomly dying under light load
This one hurt because it smelled like hardware, but it was not.
Symptoms:
- Next.js dev server inside Docker just exited with code 137
- Postgres container stopping during a migration with no helpful message
- Browser reconnecting to Vite every few minutes
Exit code 137 is usually “killed by the kernel”. On Linux that often means OOM. On Mac with Docker it often means “the little Linux VM ran out of something”.
3. Shared volume performance tanking randomly
Sometimes node_modules installs took 10 seconds. Sometimes 3 minutes. Same project. Same commands. Same coffee.
It turned out to be a mix of:
- How I mounted volumes
- Mutagen / VirtioFS / cached flags
- Watchers going crazy inside containers
4. "standard_init_linux.go:211: exec user process caused: exec format error"
This one hit me early with the move to Apple Silicon.
Usually when pulling older images, or building images from base images that were still amd64 only.
standard_init_linux.go:211: exec user process caused: exec format error
If you see that on a Mac Studio, it is almost always an architecture mismatch.
The approach that finally worked
Fixing this was not a single trick. It was a set of decisions that reduced surprise.
Rough order of attack:
- Lock Docker Desktop to a sane config
- Clean up disk usage and control growth
- Normalize architecture across images
- Fix volumes for front-end workflows
- Make Docker Compose files boring and predictable
1. Docker Desktop: my stable Mac Studio config
First thing I changed: I stopped treating Docker Desktop like a magic box and started treating it like a tiny server I actually manage.
Resources tab
My current settings:
- CPUs: 8 (out of 12)
- Memory: 16 GB (out of 64)
- Swap: 4 GB
- Virtual disk size: 256 GB
The big change that killed most “no space left on device” issues was increasing the virtual disk size and then actually watching it.
64 GB was not enough for my use. 128 GB worked for a while. 256 GB has been the sweet spot.
I also stopped giving Docker “everything”. When I let it use 30+ GB RAM, it happily did that and then macOS became sluggish. With 16 GB it has constraints but still runs multiple stacks fine.
General tab
Settings that matter for stability:
- Use Virtualization framework: On
- Use Rosetta for x86/amd64 emulation: On
- Use gRPC FUSE for file sharing: Off
I had more problems with gRPC FUSE than benefits. Disabling that and sticking to the default, plus volume mount tweaks, was more predictable for me.
2. Cleaning Docker's mess properly
I used to run docker system prune like a ritual. It helped, but it was not enough on Mac Studio because of the virtual disk.
The actual commands I use now
Once a week or when I hit weird errors, I run:
docker system df
This tells me where the bloat is: images, containers, local volumes, build cache.
Then I am a bit more explicit:
# stop everything
docker compose down -v
# remove dangling images, containers, networks
docker system prune -f
# remove unused volumes (careful: this wipes local DBs etc.)
docker volume prune -f
# clean build cache
docker builder prune -af
The important piece is docker builder prune -af. BuildKit happily hoards cache layers. On my machine it reclaimed tens of gigabytes the first time.
If disk usage gets close to the virtual disk size, I shut everything down and then do a full reset of the disk from Docker Desktop:
- Settings → Troubleshoot → Clean / Purge data
I treat that like reformatting a dev server. Annoying, but clean.
3. Architecture: no more "exec format error"
Apple Silicon is fast. Rosetta is good. But mixing architectures in Docker is a recipe for subtle pain.
My rules now:
- Default everything to
linux/arm64 - Only opt into
linux/amd64when the stack forces it
Setting the default platform
In ~/.docker/config.json I added:
{
"platform": "linux/arm64"
}
That made docker pull and docker build behave more predictably on this machine.
Explicit platform in images that need amd64
For the one Java stack that just refuses to run properly on arm64, I pin it inside docker-compose.yml:
services:
legacy-app:
image: somecorp/legacy-app:2.3
platform: linux/amd64
Same for old Postgres versions when I have to match production:
services:
db:
image: postgres:11
platform: linux/amd64
Once I did that, the exec format error just disappeared.
4. Volumes and front-end performance on Mac Studio
This was the biggest quality of life upgrade.
Most front-end dev pain on Docker for Mac is volume related. File watching, node_modules, and hot reloading over the Docker VM boundary are a bad combo if you get the mounts wrong.
I stopped mounting node_modules from the host
This alone killed a lot of flakiness and weird performance spikes.
Old style:
services:
web:
build: .
volumes:
- .:/app
This means your node_modules sits on the host and gets mapped into the container. Every file access for the toolchain crosses the VM boundary.
New style I use now for Node projects:
services:
web:
build: .
working_dir: /app
volumes:
- .:/app
- /app/node_modules
The second line (/app/node_modules) creates an anonymous volume inside Docker for that path. So the source code is synced, but node_modules lives inside the VM where it is fast.
Install script in the Dockerfile stays simple:
RUN corepack enable \
&& pnpm install --frozen-lockfile
This alone made Vite and Next dev servers much less erratic.
Using delegated / cached where it actually helps
I do not go crazy with the mount options, but I use them in a few places.
Example for a Laravel app with a Node front-end:
services:
app:
volumes:
- .:/var/www:cached
- /var/www/node_modules
vite:
volumes:
- .:/var/www:delegated
- /var/www/node_modules
cached tells Docker that the container can see slightly stale data. For PHP code that is fine.
delegated favors writes from the container. For Vite writing to public and temp files, that helped stabilize things.
I am not religious about the exact combo. The main point is: do not mount everything blindly and then wonder why file IO sucks.
Reduce watchers inside the container
I also changed how I run watch scripts. Instead of:
"scripts": {
"dev": "vite --host 0.0.0.0"
}
I often run with a polling interval or limited watch scope:
"scripts": {
"dev": "vite --host 0.0.0.0 --watchOptions.usePolling --watchOptions.interval=500"
}
Not as instant as inotify, but more predictable on Docker for Mac.
5. Boring docker-compose files
My early Compose files tried to be clever. Lots of overrides. Fancy networks. Conditional build args. That was fun until something broke and it took an hour to untangle.
My main Mac Studio rule now: keep the local dev Compose minimal and explicit.
One real example
This is the trimmed version of the Compose I use for a Next.js + Postgres app:
version: "3.9"
services:
web:
build:
context: .
dockerfile: Dockerfile
args:
NODE_ENV: development
platform: linux/arm64
ports:
- "3000:3000"
env_file:
- .env.local
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/app
volumes:
- .:/app
- /app/node_modules
command: pnpm dev
db:
image: postgres:15-alpine
platform: linux/arm64
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app
ports:
- "5433:5432"
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Notes:
- Both services pin
linux/arm64 - Node modules isolated inside Docker
- Postgres data in a named volume, not a bind mount
- Database port mapped to 5433 on host, so it does not clash with a local Postgres
This file has not changed in months. That is the target.
6. Fixing the random container deaths
Once the resources and volumes were sane, the last weird issue was containers dying with exit code 137 under what looked like light load.
What actually fixed it on my Mac Studio:
- Lowering Docker's RAM from 24+ GB to 16 GB, then restarting everything
- Giving the VM some swap (4 GB), instead of disabling it altogether
- Checking for run-away processes with
docker stats
In one case, a broken Next build script spawned child processes in a loop. docker stats exposed that in seconds. Before that, I blamed Apple Silicon, Docker, the weather, whatever.
I also added basic healthchecks for critical services, so they restart cleanly instead of silently dying and leaving me guessing.
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
What I stopped doing
Stability came just as much from what I removed as from what I added.
- No more overlapping: Docker Desktop, Colima, and OrbStack all installed and half-configured
- No more running everything with
latesttags - No more clever shell aliases that hide real Docker commands
My rule now: this Mac Studio has one default Docker runtime (Docker Desktop), one main Compose file per project, and pinned image versions for anything I care about.
The boring, stable Docker Mac Studio setup
To recap the parts that actually mattered for stability:
- Right-size Docker Desktop resources: 8 cores, 16 GB RAM, 256 GB virtual disk
- Clean aggressively with
docker system dfanddocker builder prune -af - Default to
linux/arm64, explicitly pinlinux/amd64when needed - Do not mount
node_modulesfrom host; use anonymous volumes - Use simple, explicit Compose files with named volumes for databases
- One Docker stack per project, not a shared mega Compose for everything
None of this is novel. That is the point. Docker on Mac Studio stopped breaking once I treated it like a small server with constraints, not as a magical black hole for containers.
If your Mac Studio keeps throwing "no space left on device" or killing containers with exit 137, start with the disk size, the architecture, and your volume mounts. Those three were responsible for almost all of my pain.
Top comments (0)