Experience Installing Docker-OSX on Windows 11

My colleague Zheng, who is proficient in Level 6 English, recommended an open-source project to me, and I didn’t even know that such fun stuff existed.

Since I’m using Windows 11, the installation process went very smoothly without any obstacles. After enabling the subsystem and upgrading to WSL2, Ubuntu already supports KVM.

Please note that this article only documents the process and does not guarantee coverage of all scenarios. It may not be applicable to Windows 10.(English version Translated by GPT-3.5, 返回中文)

Introduction

All operations described in this article are based on actual experiences and real results. Therefore, I can only record what I have obtained and what I have operated, and I cannot guarantee to cover every situation and every error. For example, whether the subsystem supports KVM needs to be checked by yourself. In my case, after enabling the subsystem and upgrading to WSL2, the subsystem directly supported KVM and also supported GPU invocation in the subsystem.

So if KVM is not available after the subsystem is up and running, this method may not be applicable.

Project Git repository: https://github.com/sickcodes/Docker-OSX

System Specifications

CPU: Intel® Core™ i7-11800H 2.30GHz
Operating System: Windows 11 Pro

I won’t list the rest because they are not needed.

Enabling the Subsystem

  1. Open the Start menu and go to Settings.
    Start Menu

  2. Search for Control Panel.
    Control Panel

  3. In the Control Panel, select “Programs” - “Turn Windows features on or off” - In the pop-up window, scroll to the bottom and check “Windows Subsystem for Linux” and “Virtual Machine Platform”.
    Enable Subsystem WSL2

  4. The system will require a restart, so go ahead and restart.

Enabling WSL2

Enabling WSL2 went smoothly for me without any obstacles.

  1. Open the Start menu and directly type “powershell”, then right-click and open it as an administrator (I haven’t tested it without admin privileges).
    Open Windows PowerShell

    1
    2
    3
    4
    5
    6
    Windows PowerShell
    版权所有(C) Microsoft Corporation。保留所有权利。

    安装最新的 PowerShell,了解新功能和改进!https://aka.ms/PSWindows

    PS C:\WINDOWS\system32>
  2. Enable WSL 2

    One command

    1
    wsl --set-default-version 2

    Output

    1
    2
    3
    PS C:\WINDOWS\system32> wsl --set-default-version 2
    有关与 WSL 2 的主要区别的信息,请访问 https://aka.ms/wsl2
    操作成功完成。
  3. Update the kernel

    Another command

    1
    wsl --update

    Output

    1
    2
    3
    4
    5
    6
    PS C:\WINDOWS\system32> wsl --update
    正在检查更新...
    正在下载更新... // 这里我记得他会自动下载约300MB不到的更新
    正在安装更新...
    此更改将在 WSL 下次完全重启时生效。若要强制重启,请运行“wsl --shutdown”。
    内核版本: 5.10.60.1
  4. Turn off WSL 2 to take effect

    You can also use this command to stop WSL and save memory. When you use Ubuntu again, it will automatically restart.

    1
    wsl --shutdown

Download and Install Ubuntu 20.04

Go directly to the Windows Store and search for “Ubuntu” and install Ubuntu 20.04.
Install Ubuntu 20.04

If you encounter the situation shown in the image below, close your VPN connection first. I remember that the Windows Store cannot be accessed with proxy settings enabled.

No network connection

Operations in Ubuntu 20.04

After downloading, find Ubuntu 20.04 in the Start menu and open it.

1
2
3
4
Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username:

After entering your username and password, you will enter the subsystem. That’s it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Enter new UNIX username: ruter
New password:
Retype new password:
passwd: password updated successfully
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.10.60.1-microsoft-standard-WSL2 x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

System information as of Tue Nov 23 21:15:29 CST 2021

System load: 0.17 Processes: 8
Usage of /: 0.5% of 250.98GB Users logged in: 0
Memory usage: 2% IPv4 address for docker0: 172.17.0.1
Swap usage: 0% IPv4 address for eth0: 172.19.72.129

1 update can be applied immediately.
To see these additional updates run: apt list --upgradable


The list of available updates is more than a week old.
To check for new updates run: sudo apt update


This message is shown once a day. To disable it please create the
/home/ruter/.hushlogin file.
ruter@Ruter-DPC:~$

If you encounter the error “0x800701bc” on startup, it means that the WSL2 kernel forgot to upgrade.

Critical Operation - KVM Check

Enter the command

1
root@Ruter-DPC:/home/ruter# ls /dev/kvm

If there is no output, it means that the subsystem does not support KVM. If it is supported, it will output the following:

1
2
3
root@Ruter-DPC:/home/ruter# ls /dev/kvm
/dev/kvm
root@Ruter-DPC:/home/ruter#

Installing Docker

Install Docker based on the guide Install Docker Engine on Ubuntu. Here are all the commands:

1
2
3
4
5
6
7
8
9
10
11
12
13
sudo apt-get remove docker docker-engine docker.io containerd runc
sudo apt-get update
sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

Launch Docker

1
service docker start

Download and Install Docker-OSX

Download x11-apps, which is required for displaying graphical interface (required by Docker-OSX).

1
apt install x11-apps

Run the following command:

1
2
3
4
5
6
7
8
9
10
docker run -it \
--device /dev/kvm \
-p 50922:10022 \
-p 50599:5999 \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e "DISPLAY=${DISPLAY:-:0.0}" \
-e "USERNAME=user" \
-e "PASSWORD=alpine" \
-e EXTRA="-display none -vnc 0.0.0.0:99,password=on" \
sickcodes/docker-osx:latest

You can also mount the system disk to a directory in the subsystem.

If you need to mount the system disk to a specific directory in the subsystem, modify the following command according to your needs and run it. The advantage of this approach is that regardless of how you change the running command, the disk remains in place. It’s similar to changing the CPU and graphics card of a computer. As long as the motherboard and hard disk remain the same, the system doesn’t require much modification.

1
2
3
4
5
6
7
8
9
10
11
12
13
docker run -it \
--device /dev/kvm \
-p 50922:10022 \
-p 50599:5999 \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v "/home/dockerMac/image.img":/image.img \
-v "/home/dockerMac/external.img":/exteralDisk.img \
-e "DISPLAY=${DISPLAY:-:0.0}" \
-e "IMAGE_PATH=/image.img" \
-e MASTER_PLIST_URL='https://raw.githubusercontent.com/sickcodes/osx-serial-generator/master/config-custom.plist' \
-e EXTRA='-device ide-hd,bus=sata.5,drive=DISK-TWO -drive id=DISK-TWO,if=none,file=/exteralDisk.img,format=qcow2' \
-e EXTRA="-display none -vnc 0.0.0.0:99,password=on" \
sickcodes/docker-osx:latest

Command Explanation

Port Mapping

These two commands represent port mapping, which maps the ports in Docker to the ports in the subsystem. For example, when accessing 127.0.0.1:50922 in the subsystem, it is equivalent to accessing 127.0.0.1:10022 in Docker.

1
2
-p 50922:10022
-p 50599:5999
Mounting

These two commands represent mounting the file /replace this with your directory/your system disk.img to /image.img in the Docker system. By default, Docker-OSX treats /image.img as the system disk of macOS (you can modify the path of the macOS system disk using the environment variable IMAGE_PATH). externalDisk.img can be used to create a second data disk.

1
2
-v "/这里改成你的目录/你的系统磁盘.img":/image.img
-v "/这里改成你的目录/你的第二块数据盘.img":/exteralDisk.img

To create the image.img, use qemu-img. It is recommended to start with a system disk size of at least 120GB, as it will not consume all 120GB in one go.

1
2
sudo apt install qemu-utils
qemu-img create -f qcow2 -o size=120G /这里改成你的目录/你的系统磁盘.img
Environment Variables Section (Mostly referenced from the original text)
1
2
3
4
5
-e "DISPLAY=${DISPLAY:-:0.0}" \
-e "IMAGE_PATH=/image.img" \
-e MASTER_PLIST_URL='https://raw.githubusercontent.com/sickcodes/osx-serial-generator/master/config-custom.plist' \
-e EXTRA='-device ide-hd,bus=sata.5,drive=DISK-TWO -drive id=DISK-TWO,if=none,file=/exteralDisk.img,format=qcow2' \
-e EXTRA="-display none -vnc 0.0.0.0:99,password=on" \
  1. DISPLAY (see What is ${DISPLAY:-:0.0}?)

  2. IMAGE_PATH=/image.img sets the environment variable IMAGE_PATH in the Docker container to /image.img, because Docker-OSX uses this environment variable to configure its macOS disk image. However, the command -v "/replace this with your directory/your system disk.img":/image.img mounts /replace this with your directory/your system disk.img to /image.img. Therefore, the file /image.img is actually /replace this with your directory/your system disk.img.

  3. EXTRA='-device ide-hd,bus=sata.5,drive=DISK-TWO -drive id=DISK-TWO,if=none,file=/exteralDisk.img,format=qcow2'
    This is a parameter for configuring the disk in qemu-kvm. Please refer to the relevant qemu-kvm documentation for details.

  4. EXTRA="-display none -vnc 0.0.0.0:99,password=on"
    This is for enabling VNC. The “99” represents using port 5999. Please refer to Native QEMU VNC example - Docker-OSX GitHub for more details.

Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
root@Ruter-DPC:/home/ruter# docker run -it \
> --device /dev/kvm \
> -p 50922:10022 \
> -p 50599:5999 \
> -v /tmp/.X11-unix:/tmp/.X11-unix \
> -v "/home/dockerMac/image.img":/image.img \
> -v "/home/dockerMac/externalDisk.img":/exteralDisk.img \
> -e "DISPLAY=${DISPLAY:-:0.0}" \
> -e "IMAGE_PATH=/image.img" \
> -e MASTER_PLIST_URL='https://raw.githubusercontent.com/sickcodes/osx-serial-generator/master/config-custom.plist' \
> -e EXTRA='-device ide-hd,bus=sata.5,drive=DISK-TWO -drive id=DISK-TWO,if=none,file=/exteralDisk.img,format=qcow2' \
> -e EXTRA="-display none -vnc 0.0.0.0:99,password=on" \
> sickcodes/docker-osx:latest
Unable to find image 'sickcodes/docker-osx:latest' locally
latest: Pulling from sickcodes/docker-osx
5f3642192ce7: Downloading [================================================> ] 224.6MB/232.4MB
.....
Digest: sha256:b3f36dc42598d45a9a577ea417528b4b3c844982d488cd2103e0eaa4897e600c
Status: Downloaded newer image for sickcodes/docker-osx:latest
71b97d3496e1994822e583da8ba121a7a08a5adcf27f1ae6dee8993e468f0565
ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519
nohup: appending output to 'nohup.out'
++ id -u
.......
alsa: Reason: No such file or directory
audio: Failed to create voice `adc'
usb_desc_get_descriptor: 2 unknown type 33 (len 10)
usb_desc_get_descriptor: 1 unknown type 33 (len 10)
qemu-system-x86_64: terminating on signal 2

Wait until it stops printing, then press Enter. (qemu) will appear.

1
2
3
4
5
...
usb_desc_get_descriptor: 1 unknown type 33 (len 10)
qemu-system-x86_64: terminating on signal 2

(qemu)

Reset the password by entering change vnc password root. By default, RealVNC uses the Root username for connection.

Note: Always use Ctrl + P + Q to exit. If you press Ctrl + C, the container will close, and if you press Ctrl + P and then Q, the container will be running in the background.

To stop the container, use the command docker stop 9d4e9307c852.


Continue with macOS Installation

At this point, the VNC interface will appear. Select the second “macOS installer”.

Continue with macOS Installation

Long Waiting Time (Approximately 1 Hour)

You will need to wait for about 1 hour. The screen will display “killing all process”.

First Installation Complete, Restart (2 times)

While Docker is still running, use Ctrl + C to stop the service, then enter docker ps -a to find the stopped container and enter docker start container ID.

1
2
3
4
5
6
7
(qemu) qemu-system-x86_64: terminating on signal 2
root@Ruter-DPC:/home/ruter# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9d4e9307c852 sickcodes/docker-osx:latest "/bin/bash -c 'sudo ..." 14 minutes ago Exited (0) 11 seconds ago upbeat_ellis
root@Ruter-DPC:/home/ruter# docker start 9d4e9307c852
9d4e9307c852
root@Ruter-DPC:/home/ruter#

In the above example, 9d4e9307c852 is the container ID.

The container will now run in the background. Use docker attach container ID to connect to the container.

1
docker attach 9d4e9307c852

After pressing Enter, (qemu) will appear.

1
2
3
root@Ruter-DPC:/home/ruter# docker attach 9d4e9307c852

(qemu)

Enter change vnc password root again and enter your password.

Note: Always enter change vnc password to reset the password every time Docker is started.

1
2
3
(qemu) change vnc password root
Password: ****
(qemu)

VNC Connection

Download RealVNC (or other VNC tools).

First, you need to know the IP address of the virtual machine. You can use ip addr to obtain it.

1
2
3
4
5
6
6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:a2:ac:d5 brd ff:ff:ff:ff:ff:ff
inet 172.19.68.215/20 brd 172.19.79.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:fea2:acd5/64 scope link
valid_lft forever preferred_lft forever

You will see that the IP is 172.19.68.215. Open RealVNC and enter 172.19.68.215:50599.
RealVNC

Enter “root” and you will be able to access the page.

Connecting to VNC

Once connected, you will most likely see the following:

VNC Command Line Interface

After waiting for a while, the following screen will appear:

VNC Apple Initialization

System Installation

Installation Interface

Next, the following screen will appear.
macOS Utilities

Disk Formatting

Select “Disk Utility”, find a 137GB disk, and erase it in the upper-right corner. Then close Disk Utility.

Disk Utility

Reinstall macOS

Select “Reinstall macOS” and choose the previously formatted disk.

Select Formatted Disk

Installation in Progress

Long Waiting Time (Approximately 30 Minutes)

After waiting for about half an hour, it will complete.

Second Installation Complete

At this point, there will be a bunch of English text. Stop Docker by using Ctrl + C, then enter docker restart container ID to restart Docker (don’t forget to set the VNC password). After restarting, go to VNC and select the formatted disk.

Select Macintosh HD to Boot macOS

After a simple setup (you can skip Apple ID), the process is complete.

Finished

Initial User Experience

After using it for a while, I found that the speed is acceptable. It’s much faster than running in VMware as Docker runs a KVM virtual machine, and then macOS image runs in that KVM virtual machine. However, it’s not ideal for coding and other tasks.

The system runs smoothly, but GPU acceleration is not available (although it is supported in my subsystem). I’m not sure if there are other methods to enable GPU acceleration. I have a Mac of my own, so I’m just playing around with this.

Appendix: Running without VNC

If you remove the line -e EXTRA="-display none -vnc 0.0.0.0:99,password=on" when running docker run, Docker will automatically open a QEMU window as shown above.

Running without VNC opens QEMU window

Appendix: macOS System Information and Performance

About This Mac

macOS System Information

Disk Speed Test

Disk Speed Test

Geekbench 5 Score

Host machine score: X15 R1 - GeekBench Browser

Don’t ask me why my 11800H score is so low. It’s because I disabled Turbo Boost. When Turbo Boost is disabled, the system works smoothly with the above setup, but when Turbo Boost is enabled, it can reach nearly 9000 in benchmark tests. However, the laptop has a chance to fly off the desk.

Host Machine Score

Subsystem score: Ubuntu 20.04.3 LTS - GeekBench Browser

Interestingly, the subsystem score is higher than the host machine score.

Subsystem Ubuntu 20.04 Score

Docker-OSX score: IMacPro1.1 - GeekBench Browser

Docker-OSX Score