Running macOS inside Linux with Docker-OSX
Tags: general, linux, docker, macos
Published: 2022-01-17 07:57:22

---
title: Running macOS inside Linux with Docker-OSX
published: true
date: 2022-01-16 19:48:41 UTC
tags: General, Linux, Docker, macOS
canonical_url: 
---

*Originally appeared on [my blog](https://gombosg.com/2022/01/running-macos-inside-linux/).*

It just happened to me that at my current workplace, [Process Street](http://process.st/), I had to fix a bug in our [iOS application](https://apps.apple.com/us/app/process-street/id1523499559). But… I’d like to do this on a Linux box, and not on a MacBook. What to do in this case? Let’s run macOS inside a virtual machine!

![](https://gombosg.com/wp-content/uploads/2022/01/image-1024x576.png)

 Table Of Contents 

1. [Prerequisites](#prerequisites)
  - [CPU virtualization](#cpu-virtualization)
  - [RAM, disk space](#ram-disk-space)
  - [Docker](#docker)
2. [Installing Docker-OSX](#installing-docker-osx)
  - [Get started!](#get-started)
  - [Install the OS](#install-the-os)
3. [Customizing your installation](#customizing-your-installation)
  - [Extracting the disk image](#extracting-the-disk-image)
  - [Adding RAM & CPU](#adding-ram-cpu)
  - [Sharing folders from the host machine](#sharing-folders-from-the-host-machine)
    - [Writing shared folders](#writing-shared-folders)
  - [Networking, accessing ports](#networking-accessing-ports)
    - [Setting up host networking](#setting-up-host-networking)
  - [Using a VNC client](#using-a-vnc-client)
4. [Conclusion](#conclusion)

## Prerequisites

### CPU virtualization

Your CPU should support virtualization like Intel VT or AMD-V. [Enable it](https://www.bleepingcomputer.com/tutorials/how-to-enable-cpu-virtualization-in-your-computer-bios/) in your BIOS/UEFI before proceeding. (For example, on my Ryzen system, it was an “AMD SVM” setting that had to be enabled in BIOS.)

Then install Kernel Virtualization Manager (KVM) and QEMU. For Fedora Linux, see docs [here](https://docs.fedoraproject.org/en-US/quick-docs/getting-started-with-virtualization/). It’s as simple as:

```
# sudo dnf install @virtualization
```

Then start the `libvirtd` systemd service and check if the `kvm` kernel module is running.

With KVM, x86 virtualization offers near-native performance, which means that CPU calls from the Mac virtual machine are routed directly to your processor in a secure way without any kind of emulation penalty.

Although graphics and general UI responsiveness will be slow, but with KVM, software will run blazing fast provided that you have some strong CPU like a more recent Intel i7 or Ryzen 7.

Compare the efficiency of KVM with for example [DOSBox](https://dosbox.com/), where full emulation is needed because of DOS programs using low-level BIOS interrupts and [real CPU mode](https://en.wikipedia.org/wiki/Real_mode). That kind of emulation can be orders of magnitude slower, though on today’s machines this is not an issue when emulating DOOM. :)

### RAM, disk space

If you want to run Xcode, have at least 16Gb of RAM installed so that you can allocate about 8Gb to the virtual machine.

You’ll need tens of gigabytes of disk space. QEMU [“qcow”](https://en.wikipedia.org/wiki/Qcow) images will expand like crazy once you start installing software, until they hit a preset limit of 200 Gb. My image file grew to 170 Gb after a few days of use. So have a HDD/SSD ready with plenty of free space.

### Docker

As we’ll be using the [Docker-OSX](https://github.com/sickcodes/Docker-OSX) project, you’ll need to pull, run and maybe create Docker images locally. You might have luck with [podman](https://fedoramagazine.org/getting-started-with-podman-in-fedora/), though it interacts with your system in a different way than Docker does. YMMV. For installing Docker, follow the [installation guide](https://docs.docker.com/engine/install/).

## Installing Docker-OSX

Get acquainted with the Docker-OSX [download page](https://github.com/sickcodes/Docker-OSX). It has a nice, long README! Commands are subject to change, so please refer to the README for the latest version.

In short, Docker-OSX runs an Arch Linux container that executes QEMU and sets up preinstalled or vanilla macOS images for you. It’s insanely customizable: you can get started quickly via a single `docker run` command, but depending on your needs, you can customize several aspects of how the virtual machine is run, like screen resolution, shared folders, USB passthrough, VNC connection and so on.

### Get started!

Just run a basic image. Choose your OS version (Catalina, Big Sur, Monterey). I suggest choosing Big Sur which is still supported, but not as new as Monterey which may have some emulation issues.

Run the appropriate [command](https://github.com/sickcodes/Docker-OSX#big-sur-):

```
docker run -it \
    --device /dev/kvm \
    -p 50922:10022 \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -e "DISPLAY=${DISPLAY:-:0.0}" \
    sickcodes/docker-osx:big-sur
```

What does this do? It pulls a prebuilt Big Sur image, forwards your /`dev/kvm` device and an X11 socket into the container. It exposes the 10022 port inside the container at 50922 port in your host machine for SSH access (optional). Also it sets up an environment variable which tells the container which X display to use.

For managing Docker containers, I suggest using the [Docker extension for Visual Studio code](https://code.visualstudio.com/docs/containers/overview) if you don’t want to deal with the command line. It’s very convenient!

Basically, while the container is running, the QEMU virtual machine will keep on running. If you shut down the machine from inside macOS, the container would stop. `docker run` starts a brand new container, but you can start the same one later via `docker start`.

### Install the OS

After booting up, you’ll be greeted with the recovery installer of macOS:

![](https://gombosg.com/wp-content/uploads/2022/01/image-1-1024x612.png)
_Boot loader of the Mac VM_

You have to follow the README’s [“additional boot instructions”](https://github.com/sickcodes/Docker-OSX#additional-boot-instructions-for-when-you-are-creating-your-container) part to format the virtual disk image and install macOS on it (the image contains pre-mounted Big Sur installation media).

![](https://gombosg.com/wp-content/uploads/2022/01/image-2-1024x563.png)
_Formatting the virtual disk image_

It’s really simple point-and click work and you’ll soon be greeted with your own macOS installation!

![](https://gombosg.com/wp-content/uploads/2022/01/image-3-1024x575.png)
_Enjoy the new installation!_

From here on it’s up to you how you want to customize your image. I’m going to show a few tricks here!

## Customizing your installation

### Extracting the disk image

The first thing I suggest doing is extracting the macOS disk image so that you’ll have a persistent installation with all your work and settings while you’re experimenting with VM settings. It’s just a single, large file inside `/var/lib/docker`:

```
sudo find /var/lib/docker -size +10G | grep mac_hdd_ng.img
```

Copy the file to a convenient location where it has space to grow (2-300Gb!). From here on, you can create your own custom image using one of the “naked” containers where this file is simply mounted as a volume:

```
docker run -it \
    --device /dev/kvm \
    -v "${PWD}/mac_hdd_ng.img:/image" \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -e "DISPLAY=${DISPLAY:-:0.0}" \
    sickcodes/docker-osx:naked
```

From here on, I suggest creating a text file where you store versions of the `docker run` command for your convenience. Time to hack!

### Adding RAM & CPU

By default, the VM is only created with 4 cores and 4 Gb of RAM. Why not use more? Depending on your system, add the arguments `-e RAM=8 -e CPU_STRING=16` to make it much, much faster!

### Sharing folders from the host machine

This one is really useful as you might want to share some files from the host. Unfortunately, it’s not trivially simple. Because networking works out of the box, you may actually be better of sharing files via a cloud provider like Dropbox, if it’s just a few smaller files!

See instructions [here](https://github.com/sickcodes/Docker-OSX#share-folder-with-docker-osx-qemu-macos). You basically pass the folder name as a `docker run` parameter like `-v "$HOME/mac/shared:/mnt/hostshare"` and add some extra QEMU parameters which define this folder as a device called `hostshare`. Moreover, inside the VM, after each boot, you have to mount this folder: `sudo -S mount_9p hostshare`.

In Finder’s menu bar, click on “Go – Computer” to access this shared folder. This kind of shared folder is not as fast as the native “qcow” disk image; don’t use it for folders with thousands of files like Git repos, it will be a slow experience.

#### Writing shared folders

You may notice that you’re unable to write to shared folders. It’s because of how the Linux permission system works: each user has an ID which is associated with each file. When determining filesystem permissions, this ID is actually being used instead of your user name.

macOS, also being a POSIX-compliant operating system, uses the same access model, but your macOS user very likely has a different ID by default, so permission will be denied by the host OS.

Making the shared folder writable by anyone might work but is awful from a security standpoint. Fortunately you can change your macOS user ID with a few commands. See a guide [here](https://linuxhandbook.com/uid-linux/). Not super complicated, but not very simple either. If you plan on doing this, I suggest doing it before installing any software in your VM, as each file needs their user IDs reset.

### Networking, accessing ports

[Networking in Docker](https://docs.docker.com/network/) is hard in itself, moreover, we’re having this VM being run inside QEMU. Ouch. You might have different use cases beyond just browsing the web, which should work out of the box.

**Forwarding ports** from the VM to the host, like for example, running nginx or some server backend in the VM is a valid use case. For each port being forwarded, a few extra arguments are needed when setting up the container, see the README section [here](https://github.com/sickcodes/Docker-OSX#forward-additional-ports-nginx-hosting-example).

**Accessing host machine ports** from the VM i.e. `localhost` access is also possible by forwarding the corresponding port to the container with the `-p` Docker argument. QEMU sets up the host machine’s `localhost` (which is inside Docker!) as `10.0.2.2` so after that,

#### Setting up host networking

If you don’t want to individually forward ports into the Docker container, you can opt to use the [“host networking” driver](https://docs.docker.com/network/network-tutorial-host/). In this case, you can access all of your Linux machine’s ports via `10.0.2.2`. This is also a downside from a security standpoint: only do this if you know what you’re doing.

### Using a VNC client

The default QEMU window does the job, but it’s a lot more convenient to access your VM through a VNC client. It has advantages like:

- Not having to stop the VM when closing the emulator window
- Showing the host’s mouse cursor for a smoother experience
- Capturing keyboard shortcuts like `CMD+C` etc.
- “Inserting” from the host clipboard by typing it in (even if there is no direct clipboard sharing)
- Changing connection quality etc.

I suggest the [Remmina](https://remmina.org/) VNC client as it’s pretty advanced, but feel free to use your own. There are multiple ways to get a VNC-capable version, the simplest is probably building a [custom Dockerfile](https://github.com/sickcodes/Docker-OSX/blob/master/vnc-version/Dockerfile.nakedvnc) in the repo (edit the file if you want a different resolution):

```
$ git clone git@github.com:sickcodes/Docker-OSX.git
$ cd Docker-OSX/vnc-version
$ docker build -t docker-osx:nakedvnc -f Dockerfile.nakedvnc .
```

Note down the VNC password printed during building the container! It will be permanent for each run so you can save it to your VNC client. You can connect to it via `localhost:5999`. Don’t forget to set “grab input” and “hide menubar” both in the QEMU menu bar _and_ the VNC client.

If you terminate the VNC connection, your macOS system will keep on running. Neat!

Here’s my example Docker command for reference, running a VNC-capable container (built above) with a shared folder, a custom image and host networking.

```
docker run \
    --device /dev/kvm \
    --network host \
    -v "$HOME/mac/mac_hdd_ng.img:/image" \
    -v "$HOME/mac/shared:/mnt/hostshare" \
    -e EXTRA="-virtfs local,path=/mnt/hostshare,mount_tag=hostshare,security_model=passthrough,id=hostshare" \
    -e RAM=8 \
    -e CPU_STRING=16 \
    docker-osx:nakedvnc
```

## Conclusion

I hope you enjoyed reading this small guide. Trying this experiment really shows how advanced our current virtualization technology is and allows you to use macOS for legitimate use cases like security research inside a secure virtual machine.