Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

🐳 docker-amd-hackintosh

Run Docker on an AMD Hackintosh — no Docker Desktop, no Hypervisor.framework, no bullshit.

macOS Arch QEMU Colima License


The problem

Docker Desktop requires Apple's Hypervisor.framework (com.apple.security.hypervisor). On a genuine Intel or Apple Silicon Mac, this works fine. On an AMD Hackintosh, it crashes immediately and silently — no useful error, just failure.

Lima (the VM layer under Colima) hardcodes -machine q35,accel=hvf into every QEMU launch. HVF is Apple's hypervisor acceleration. AMD Hackintoshes don't have it. End of story — unless you do what this repo does.


The solution

We use QEMU + Colima with a compiled C wrapper that intercepts Lima's QEMU launch command and silently swaps accel=hvf for accel=tcg,thread=multi before the real QEMU binary ever sees it.

TCG (Tiny Code Generator) is QEMU's pure software emulation mode. It doesn't need Hypervisor.framework. It doesn't need a real Mac. It just works.

Lima generates:   qemu-system-x86_64 -machine q35,accel=hvf ...
Wrapper intercepts:                              ^^^^^^^^^^^
Wrapper passes:   qemu-system-x86_64 -machine q35 -accel tcg,thread=multi ...

The wrapper is a compiled binary (not a shell script) so it can be codesigned with the hypervisor entitlement — which Lima requires to trust the QEMU binary.


Requirements

Requirement Notes
macOS Sequoia (15.x) Tested on 15.7.5
AMD CPU (any) Ryzen, Threadripper, etc.
Hackintosh EFI OpenCore recommended
Homebrew Package manager
Xcode Command Line Tools For gcc — run xcode-select --install

Not tested on Intel Hackintosh. It may work, but you probably don't need it — Intel Macs support HVF natively. If you try it, open an issue and let me know.


Install

git clone https://github.com/osx86-ijb/docker-amd-hackintosh.git
cd docker-amd-hackintosh
chmod +x install.sh
./install.sh

That's it. Grab a coffee — the first boot takes 2–4 minutes because TCG is pure software emulation and has to cold-start a full Linux VM from scratch.

What the script does

  1. Installs qemu, colima, docker, docker-compose, docker-credential-helper via Homebrew
  2. Compiles a C wrapper at ~/bin/qemu-system-x86_64 that intercepts and patches the QEMU launch flags
  3. Codesigns the wrapper with the com.apple.security.hypervisor entitlement
  4. Symlinks the QEMU EFI firmware so Lima can find it relative to the wrapper
  5. Writes a minimal Colima config using cpuType: qemu64 (avoids host CPU passthrough issues)
  6. Starts the Colima VM
  7. Forwards the Docker socket from inside the VM to your host via SSH
  8. Installs a launchd agent to keep the socket forwarded across reboots
  9. Registers Colima as a brew service for auto-start on login

Manual steps (if you prefer to do it yourself)

1. Install packages

brew install qemu colima docker docker-compose docker-credential-helper

2. Configure docker-compose plugin

mkdir -p ~/.docker
cat > ~/.docker/config.json << 'EOF'
{
  "cliPluginsExtraDirs": [
    "/usr/local/lib/docker/cli-plugins"
  ]
}
EOF

3. Build and sign the QEMU wrapper

mkdir -p ~/bin

cat > /tmp/qemu_wrapper.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    char **newargs = malloc(sizeof(char*) * (argc + 4));
    int j = 0;
    for (int i = 0; i < argc; i++) {
        if (strcmp(argv[i], "q35,accel=hvf") == 0) {
            newargs[j++] = "q35";
        } else {
            newargs[j++] = argv[i];
        }
        if (i > 0 && strcmp(argv[i-1], "-machine") == 0
                  && strcmp(argv[i], "q35,accel=hvf") == 0) {
            newargs[j++] = "-accel";
            newargs[j++] = "tcg,thread=multi";
        }
    }
    newargs[j] = NULL;
    newargs[0] = "/usr/local/bin/qemu-system-x86_64";
    execv("/usr/local/bin/qemu-system-x86_64", newargs);
    return 1;
}
EOF

gcc -o ~/bin/qemu-system-x86_64 /tmp/qemu_wrapper.c

Sign it:

cat > /tmp/hypervisor.entitlements << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.hypervisor</key>
    <true/>
</dict>
</plist>
EOF

codesign -s - --entitlements /tmp/hypervisor.entitlements ~/bin/qemu-system-x86_64

4. Add ~/bin to your PATH

echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
export PATH="$HOME/bin:$PATH"

5. Symlink the firmware

Lima looks for firmware relative to the QEMU binary path. Since our wrapper is in ~/bin, we need to make the firmware findable there too:

mkdir -p ~/.local/share/qemu
ln -s /usr/local/share/qemu/edk2-x86_64-code.fd ~/.local/share/qemu/edk2-x86_64-code.fd

6. Write the Colima config

mkdir -p ~/.colima/default
cat > ~/.colima/default/colima.yaml << 'EOF'
cpu: 4
memory: 8
disk: 60
vmType: qemu
arch: x86_64
cpuType: qemu64
runtime: docker
mountType: sshfs
EOF

Why cpuType: qemu64? Using the default host CPU type on an AMD Hackintosh tells QEMU to pass through your actual CPU, which can trigger HVF attempts. qemu64 is a generic baseline x86_64 virtual CPU that plays nicely with TCG.

7. Start Colima

colima start

The VM will boot. Colima may exit with an error about Docker state — that's a known first-boot quirk. As long as you see the 4 SSH requirements satisfied in the output, the VM is up.

8. Forward the Docker socket

Colima's daemon didn't finish, so we forward the socket manually:

rm -f ~/.colima/default/docker.sock
ssh -F ~/.colima/_lima/colima/ssh.config \
    -o ControlMaster=no \
    -o ControlPath=none \
    -L $HOME/.colima/default/docker.sock:/run/docker.sock \
    -N lima-colima &

9. Test it

docker ps
docker run --rm hello-world

10. Make it survive reboots

Install the LaunchAgent for persistent socket forwarding:

cat > ~/Library/LaunchAgents/com.docker.sock-forward.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.docker.sock-forward</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>rm -f $HOME/.colima/default/docker.sock; /usr/bin/ssh -F $HOME/.colima/_lima/colima/ssh.config -o ControlMaster=no -o ControlPath=none -L $HOME/.colima/default/docker.sock:/run/docker.sock -N lima-colima</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardErrorPath</key>
    <string>/tmp/docker-sock-forward.log</string>
</dict>
</plist>
EOF

launchctl load ~/Library/LaunchAgents/com.docker.sock-forward.plist
brew services start colima

How it actually works (the deep dive)

Why Docker Desktop fails

Docker Desktop uses com.apple.security.hypervisor — a macOS entitlement that grants access to Apple's Hypervisor.framework. On a genuine Apple Mac, this is fine. On an AMD Hackintosh, the kernel-level hypervisor support either isn't present or isn't properly exposed. The result: instant silent crash.

Why colima start alone doesn't work

Colima uses Lima under the hood to manage VMs. Lima detects macOS and unconditionally passes -machine q35,accel=hvf to QEMU. This flag tells QEMU to use HVF acceleration. There's no config option to disable it — it's hardcoded in Lima's Go source at runtime.

Why a shell script wrapper doesn't work

Lima checks that the QEMU binary is properly codesigned with the hypervisor entitlement before launching it. A shell script can't be codesigned. A compiled binary can.

Why we compile a C wrapper

The wrapper sits in ~/bin/qemu-system-x86_64, which is first in PATH. When Lima calls qemu-system-x86_64, it gets our wrapper. The wrapper:

  1. Iterates over all arguments Lima passes
  2. When it sees -machine q35,accel=hvf, replaces it with -machine q35
  3. Immediately after that argument, injects -accel tcg,thread=multi as separate flags
  4. execvs the real QEMU binary at /usr/local/bin/qemu-system-x86_64 with the patched args

The thread=multi flag enables multi-threaded TCG, which significantly improves performance.

Why the Docker socket needs manual forwarding

Colima's own daemon process normally handles forwarding the Docker socket from inside the VM to your host. On first boot with this setup, Colima's daemon exits before completing that step due to a state-tracking issue. The SSH port forward we set up replicates exactly what Colima's daemon would have done — it's the same mechanism Colima uses internally.


Performance

TCG is slower than native virtualization. This is unavoidable — we're doing pure software CPU emulation. In practice:

  • Container startup: slightly slower than Docker Desktop on a real Mac
  • Running workloads: near-native once the container is up (workloads run on the Linux kernel inside the VM, not through emulation)
  • Image pulls: network-speed limited, effectively no difference
  • Build times: somewhat slower for CPU-intensive builds

For most development workflows — web servers, databases, CI runners, etc. — the performance is perfectly usable. If you're doing heavy compilation inside Docker, expect maybe 2–3x slower than a native setup.


Troubleshooting

docker ps returns EOF

The socket forward isn't active. Run:

rm -f ~/.colima/default/docker.sock
ssh -F ~/.colima/_lima/colima/ssh.config \
    -o ControlMaster=no \
    -o ControlPath=none \
    -L $HOME/.colima/default/docker.sock:/run/docker.sock \
    -N lima-colima &

VM won't start after a reboot

Colima may start before the SSH config is ready. Give it a moment, then:

colima start

Check VM status:

LIMA_HOME=~/.colima/_lima limactl list

could not find firmware error

The firmware symlink is missing or broken:

mkdir -p ~/.local/share/qemu
ln -sf /usr/local/share/qemu/edk2-x86_64-code.fd ~/.local/share/qemu/edk2-x86_64-code.fd

QEMU binary does not seem properly signed warning

Re-sign the wrapper:

codesign -s - --entitlements /tmp/hypervisor.entitlements ~/bin/qemu-system-x86_64

If /tmp/hypervisor.entitlements was cleaned up, recreate it first (see Step 3 above).

Colima says already running, ignoring but Docker doesn't work

The VM is up but the socket forward is dead. Kill and restart:

launchctl unload ~/Library/LaunchAgents/com.docker.sock-forward.plist
launchctl load ~/Library/LaunchAgents/com.docker.sock-forward.plist

Check what the VM is actually doing

LIMA_HOME=~/.colima/_lima limactl list
LIMA_HOME=~/.colima/_lima limactl shell colima sudo systemctl status docker

Uninstall

# Stop everything
colima stop
brew services stop colima
launchctl unload ~/Library/LaunchAgents/com.docker.sock-forward.plist

# Remove files
rm -f ~/bin/qemu-system-x86_64
rm -f ~/.local/share/qemu/edk2-x86_64-code.fd
rm -f ~/Library/LaunchAgents/com.docker.sock-forward.plist
rm -rf ~/.colima

# Uninstall packages (optional)
brew uninstall docker docker-compose docker-credential-helper colima qemu

Contributing

Found a bug? Got it working on a different setup? Open an issue or PR. Especially interested in:

  • Results on different AMD CPUs (Threadripper, older Ryzen generations, etc.)
  • Results on different macOS versions
  • Performance benchmarks vs native setups
  • Any Lima/Colima version updates that break or fix things

License

MIT — do whatever you want with it. If this saved you hours of pain, a star on the repo is appreciated.


Built through pure trial, error, and stubbornness. If Apple doesn't want you running their OS on AMD hardware, that's their problem.

About

Run Docker on AMD Hackintosh via QEMU + Colima — bypasses Hypervisor.framework entirely using TCG software emulation. No Docker Desktop required.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages