Running a Full-Stack Dev Environment (Node.js + Docker) in a Linux VM on Apple Silicon: What Worked for Us

Hi everyone,


Our team has been running most of our backend and full-stack development inside Ubuntu VMs on Apple Silicon Macs for a while now. It took some trial and error to get a setup that feels fast and stable day to day, so I wanted to share what worked for us, and hear how others are handling it.


1. Use the ARM64 version of Ubuntu


On Apple Silicon, go with the ARM64 (aarch64) build of Ubuntu. Parallels can download it directly during VM creation, which is the easiest route. Running an x86 distribution is technically possible through emulation but far too slow for real development work.


2. Resource allocation


On a 16 GB Mac, we give the VM 4 vCPUs and 8 GB RAM. That comfortably handles a Node.js API, a React/Next.js frontend, and 3–4 Docker containers (Postgres, Redis, etc.) running together. On 32 GB machines we go up to 6 vCPUs and 12 GB. Giving the VM too much memory actually made macOS itself sluggish, so leave the host enough headroom.


3. Keep your code inside the VM, not in shared folders


This was our biggest lesson. We started with the project folder on macOS shared into the VM through Parallels shared folders (/media/psf/...). It works, but file watching was unreliable. Hot reload in Nodemon, Vite, and Next.js would sometimes miss changes, and npm install on large projects was noticeably slower.


What worked better: clone the repo directly into the VM's own filesystem (e.g. ~/projects) and edit it from macOS using VS Code with the Remote-SSH extension. File watching became instant and installs got much faster.


4. Node.js setup


We install Node through nvm rather than the Ubuntu package, so each project can use its own version:



curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash<br>nvm install --lts

Also install build-essential and python3 early. Some npm packages with native modules (bcrypt, sharp, etc.) need to compile on ARM64 and will fail without them.


5. Docker inside the VM


We install Docker Engine directly in Ubuntu (not Docker Desktop) using Docker's official apt repository, then add the user to the docker group so sudo isn't needed:



sudo usermod -aG docker $USER

The main thing to watch is image architecture. Most official images (node, postgres, redis, nginx, mongo) are multi-arch and run natively on ARM64. If an older or third-party image is x86-only, you can run it with --platform linux/amd64 after setting up QEMU emulation, but expect it to be slow. For anything used daily, it's worth finding or building an ARM64 image.


6. Accessing services from macOS


With the default Shared Network mode, the VM gets its own IP (check with ip addr). You can open http://&lt;vm-ip&gt;:3000 directly in a Mac browser. We add the VM IP to /etc/hosts on the Mac with a friendly name like devbox, which makes URLs and SSH much cleaner. Also remember to bind dev servers to 0.0.0.0 instead of localhost, otherwise they won't be reachable from the host.


7. Snapshots before big changes


Before upgrading Node, Docker, or the OS, we take a Parallels snapshot. If something breaks, rolling back takes a minute instead of an afternoon of debugging.


8. Disk space


Docker images and node_modules fill up disk faster than expected. Running docker system prune regularly and allocating at least 64 GB for the VM disk has saved us from a few surprises.


Overall result


With this setup, our Linux VM feels close to a native Linux machine for daily development. Builds are fast, containers run natively on ARM, and the Mac stays responsive for everything else.


A couple of questions for others running similar setups:


  • Has anyone found a reliable way to make file watching work well over shared folders, or is working inside the VM the only real fix?
  • How are you handling x86-only Docker images on Apple Silicon: emulation, rebuilding, or a remote build machine?

Would be great to hear what's working for you.
 
Back
Top