After many unsuccessful attempts, I was finally able to virtualize macOS to test iOS apps. But before you do, you should know this is not a stable solution and has some performance issues. I need to do this for some reason.
We will be using QEMU to emulate the mac and inside of it we will be using xCode to emulate the iOS. This process will not be easy. Docker OSX’s github repository has an explanation of how to use iPhone via usb instead of emulator, but I don’t have an iPhone :(.
What is Docker OSX?
Docker OSX is a docker image that uses QEMU so that we can emulate an operating system.
You can learn more about Docker here.
Hardware parameters when emulating iOS with Docker
My computer specs are considered pretty decent for doing this, however, I still feel lag when using Docker OSX + xCode + Visual Studio Code + Dev Server. (The machine is like a fireplace :v)
- OS: Manjaro Linux x86_64
- Kernels: 4.19.220-1-MANJARO
- Shell: zsh 5.8
- Resolution: 1440×900
- DE: GNOME 41.2
- WM: Mutter
- WM Theme: Orchis-orange-compact
- Icons: Win11-purple-dark [GTK2/3]
- Terminal: gnome-terminal
- CPU: Intel i7-3770 (8) @ 3,900GHz
- GPUs: NVIDIA GeForce GTX 1050 Ti
- RAM: 4105MiB / 15985MiB
- SSDs: Crucial BX500 240gb (It is recommended to use SSD)
Setting
First, need to install docker on your computer. I use Manajaro, so I just open a terminal and enter the following commands:
Install docker: pacman -S docker
Run docker: systemctl start docker.service
Enable docker to start on system:systemctl enable docker.service
Check docker:docker run hello-world
Next, we will download docker osx and run it with the command below:
docker run -it --device /dev/kvm -p 50922:10022 -e DEVICE_MODEL="iMacPro1,1" -e WIDTH=1440 -e HEIGHT=900 -e RAM=8 -e INTERNAL_SSH_PORT=23 -e AUDIO_DRIVER=alsa -e CORES=2 -v /tmp/.X11-unix:/tmp/.X11-unix -e "DISPLAY=${DISPLAY:-:0.0}" -e GENERATE_UNIQUE=true -e MASTER_PLIST_URL=https://raw.githubusercontent.com/sickcodes/osx-serial-generator/master/config-custom.plist sickcodes/docker-osx:big-sur
You can check the meaning of each flag at the docker osx github. But to summarize briefly, I have specified resolution, memory, cpu cores, version and other things.
Then the docker osx will be downloaded and initialized.
When the emulator opens, select macOS Base System
When the system is started, selectDisk Utility
Now, we will find the prepared partition and select Erase.
To format, the options should be exactly like the image below:
Click Erase, wait for the process to finish and then you can close the Disk Utility window.
Next, choose Reinstall macOS Big Sur
, accept the terms and select the partition we just created macOS and start the installation. (This can take anywhere from 30 minutes to an hour.)
The system will reboot on its own (or not), I have to do it manually because it doesn’t reboot. In that case, close the QEMU window.
In the terminal, we will enter:
docker ps -a
To know what our container ID is and then you need to enter the command below:
docker start ID
After that, the system will automatically reboot (or not), so close QUEMU and restart the container.
When the system boots, select the option macOS Installer
and wait for the process to finish. When finished, the system will reboot.
So macOS has been successfully installed. Choose macOS
.
The welcome screen of macOS has appeared, now you need to set up macOS.
When configuring the system, but don’t sign in with AppleID.
When that process is complete, our screen will appear and we will wait until the dock appears because then the system will be most stable.
Now, we will use brew to install packages faster.
Open terminal in macOS and install brew
with the command below:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Enter your password and wait for the process to finish.
Now, we will install xcode on Apple Store.
At this point, we need to log in with AppleID.
Then open xcode and accept the terms and wait for the dependencies to be installed.
Next go to Preferences -> Locations -> Command-Line Tools, select the xCode version.
When the installation is complete, we will reopen the terminal and install cocoapods with the command below. It serves as xCode’s packager manager.
brew install cocoapods
Finally, our macOS will be installed and configured to run the projects.
Run apps with React Native
Next, we’ll try to run a simple React Native program. I only test on iOS.
Open tterminal and enter the following commands:
Install node: brew install node
Install yarn (optional): npm install -g yarn
Create RN project: npx react-native init teste
If it asks you to reinstall cocoapods, choose the option with brew.
Access the project directory: cd teste
Access the ios folder: cd ios
Install dependencies: pod install
Back to the root directory: cd ..
List all available devices (optional): xcrun simctl list devices
Run the project with xcode: npx react-native run-ios --simulator="iPhone 13"
Run the application with Cordova
Next is to run the project with Quasar to check if everything we’ve done is working.
Quasar uses Cordova/Capacitor for iOS and Android.
Install node: brew install node
Install yarn: npm install -g yarn
Install quasar: yarn global add @quasar/cli
Install cordova: yarn global add cordova
*Create a project with Quasar: quasar create teste
Access the project directory: cd teste
Add cordova to your project: quasar mode add cordova
Access the cordova folder: cd src-cordova
Add iOS to the project: cordova platform add ios
Verify that everything is ok: cordova requirements
List all available devices (optional): cordova emulate iOS --list
Install dependencies: yarn
Back to the root directory: cd ..
Install dependencies: yarn
Run quasar with development mode on iOS: quasar dev -m iOS -e "iPhone 8, 15.2"
Connecting folders via SSH
Now our application is running on macOS, but there will be some problems: Opening the code editor or IDE inside macOS is a very bad experience because of the slowness, errors, keyboard mapping, etc. So I been working on a solution to make a file connection using SSH.
Alternatively, I can open the development server inside of macOS and make a direct connection from my linux or macOS so it updates both sides, like bidirectional. This ensures we take full advantage of features in development mode, such as fast refresh.
Connecting from linux to mac
First, we need to allow connection over ssh through mac. To do this, we need to open the terminal and enter:
Command to open edit ssh config file: sudo nano /etc/ssh/sshd_config
Find PasswordAuthentication
and put yes
and remove the # in the first line.
Save the file again.
Go to System Preferences -> Sharing -> Remote Login and enable all users:
Command to restart ssh: sudo launchctl stop com.openssh.sshd && sudo launchctl start com.openssh.sshd
Now, in the linux terminal:
Install sshfs: sudo pacman -S sshfs
Get the container IP: docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ID_CONTAINER
Create folder: mkdir projeto
Command to open a new connection to mac: sudo sshfs USER_MAC@IP_CONTAINER:/PATH/OF/PROJECT/ON/MAC /PATH/ON/LINUX -p 23
Eg:
Okay, now we can open VSCode on linux and update files directly on mac.
Connect from Mac to Linux
Same as above, but the sshfs package on mac can be installed with the command below:
Install sshfs: brew install --cask macfuse && brew install gromgit/fuse/sshfs-mac
On Linux:
Command to edit ssh config file: sudo nano /etc/ssh/sshd_config
Find PasswordAuthentication
and put yes
and remove the # in the first line.
Save the file.
Command to restart SHH on Manjaro: sudo systemctl restart sshd.service
On Mac, we will create a folder to open the connection.
Create folder: mkdir projeto
Command to open a new connection to linux: sudo sshfs USER_LINUX@IP_HOST:/PATH/LINUX /PATH/MAC -p 23
When we type the command, an error occurs.
Open preferences and click Allow
Restart your mac.
Now we can open the connection: (My SSH is open on a different port, but default is 22)
Once done, we can update from either side. That’s it, very suitable for remote work, code team.