DEV Community

Gergely Gombos
Gergely Gombos

Posted on • Originally published at gombosg.com on

Running macOS inside Linux with Docker-OSX

Originally appeared on my blog.

It just happened to me that at my current workplace, Process Street, I had to fix a bug in our iOS application. 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!

Table Of Contents

  1. Prerequisites
  2. Installing Docker-OSX
  3. Customizing your installation
  4. Conclusion

Prerequisites

CPU virtualization

Your CPU should support virtualization like Intel VT or AMD-V. Enable it 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. It’s as simple as:

# sudo dnf install @virtualization
Enter fullscreen mode Exit fullscreen mode

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, where full emulation is needed because of DOS programs using low-level BIOS interrupts and real CPU 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” 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 project, you’ll need to pull, run and maybe create Docker images locally. You might have luck with podman, though it interacts with your system in a different way than Docker does. YMMV. For installing Docker, follow the installation guide.

Installing Docker-OSX

Get acquainted with the Docker-OSX download page. 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:

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
Enter fullscreen mode Exit fullscreen mode

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 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:


Boot loader of the Mac VM

You have to follow the README’s “additional boot instructions” part to format the virtual disk image and install macOS on it (the image contains pre-mounted Big Sur installation media).


Formatting the virtual disk image

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


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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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. 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. 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 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.

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. 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 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 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 .
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (20)

Collapse
jailsonpaca profile image
jailsonpaca

to be abble to coonect to Apple Id we need also to put -e GENERATE_UNIQUE=true in the command

Collapse
gerzonc profile image
gerzonc

wow this definitely seems like something that might work for me personally. I've wanted to buy a System76 laptop for myself in a long time already but being unable to run my apps for iOS was keeping me from doing it

Collapse
gombosg profile image
Gergely Gombos Author

Well, don't expect wonders though because it's only a fairly slow VNC connection. But maybe there are some workarounds or different ways to connect to QEMU to make it feasible for daily production use.

Collapse
liviufromendtest profile image
Liviu Lupei

This is interesting, but how many hours did it take you?
There are services like MacStadium, that provide access to real MacOS machines for a small price.
From your company's perspective, would it have been less costly just to use MacStadium for a few days instead of instaling MacOS on a Linux machine?
Taking into consideration your hourly rate multiplied by the number of hours dedicated to this task.

Collapse
gombosg profile image
Gergely Gombos Author • Edited on

Good question! Setting up the container itself didn't take much time, at most half a workday. Setting up the whole environment took something like a day in total, but that was because it takes time to set up any dev environment. Even if I had a MacBook or a MacStadium access, it would have taken a few hours to get to the first successful compile, just like if I was onboarding at the company.

(Think Git access, HTTPS certs with mkcert, installing a different Xcode version, getting Ruby work with rbenv, messing with build parameters and environment variables a bit.)

And now I have access to a ready-to-use Xcode environment 1 click away, for free. It really amazed me how smooth the experience was. And this is a long-term investment because it may very well happen that in the upcoming months I'll have to code some feature in that environment.

Collapse
plutohddev profile image
PlutoHD

Thank you for this great article! I'm currently trying to install Docker-OSX on my NAS without a desktop environment on it, so I decided to go for the VNC version. However, when I try to build the image (docker build -t docker-osx:nakedvnc -f Dockerfile.nakedvnc .), the build ends with the following error: toptal.com/developers/hastebin/uji...
I would really appreciate any advice!

Collapse
094459 profile image
Ricardo Sueiras

As someone who has previous run hackintosh, this is super interesting. I am defo going to try this out - thanks for putting this together.

Collapse
meligy profile image
Meligy

Hello,
I heard a lot that it's not legal to run macOS on a non-Apple machine. Do you know anything about this?

Thanks

Collapse
gombosg profile image
Gergely Gombos Author

Docker-OSX links to this article explaining the situation. tl;dr it's legal as long as you use it in good faith for security research, and report security bugs to Apple. IANAL.

Collapse
mpashka profile image
Pavel Moukhataev

As far as I know you are not able to sign in into apple using your apple id if you are not using genuine apple device. So you are not able to sign you iOS apps and publish them via xcode. Or not?

Thread Thread
gombosg profile image
Gergely Gombos Author

I haven't published via own keys. But I was able to sign in via Apple ID just fine. The VM appears as a Mac Pro.

Collapse
ichavezf profile image
Eduardo Chavez

I have a question. Its posible compile apps into that image? Flutter o Swift?

Collapse
gombosg profile image
Gergely Gombos Author

Yes, you can use any kind of software inside the image including compilation. It' quite fast thanks to KVM.

Collapse
vbao profile image
csvobao

can I ask how the performance look like compare to some product? Just wondering because I haven't used any Apple product before

Collapse
duard profile image
Carlos Aquino

there is no sound from HDMI of my GTX 1050ti :-( and only 1920x1080

Collapse
gombosg profile image
Gergely Gombos Author

See the Docker-OSX readme. I haven't tried sound as I don't need it, but you can change resolution with a single docker run parameter.

Collapse
javitolin profile image
Javi • Edited on

This sounds amazing.
Thanks for sharing.

Collapse
raibtoffoletto profile image
Raí B. Toffoletto

There's also github.com/kholia/OSX-KVM

Last time I needed a VM it was super easy... But I didn't figure out how to run it from virt-manager.

Collapse
gombosg profile image
Gergely Gombos Author

Definitely, I have also found this project! I guess that this Docker solution is just a bit more turnkey and reproducible as it ships its own QEMU inside the container, and solely relies on a KVM kernel module.

Collapse
duard profile image
Carlos Aquino

What about resolution and VGA ? Im using GTX 1050ti with 4k resolution