{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "xbill",
  "title_listed": "Build in the VM, Think on the Mac GPU: Debian 13 on Apple container With a Local Gemma 4",
  "keywords": "",
  "domain": "文本处理",
  "content_type": "工程实践",
  "is_paid_flag": "否",
  "url": "https://dev.to/gde/build-in-the-vm-think-on-the-mac-gpu-debian-13-on-apple-container-with-a-local-gemma-4-2d8b",
  "publish_date": "2026-09-14",
  "corpus_stage": "",
  "notes": "",
  "id": "58b9abc10b2d",
  "collected_at": "2026-10-02T13:51:29",
  "title": "Build in the VM, Think on the Mac GPU: Debian 13 on Apple container With a Local Gemma 4 - DEV Community",
  "body_markdown": "This article walks through building a Debian 13 machine under Apple's\ncontainer\nCLI on an Apple silicon Mac, and then wiring that machine to a local LLM running on the Mac's own GPU.\nThe VM is where your app lives. The Mac is where the model thinks.\nA Linux VM under\ncontainer\ngets virtual CPUs and virtual devices and no Metal access, so running a model inside it wastes the one piece of hardware that makes a Mac good at this. The split that works is the obvious one once you see it: customize your app in a real Debian box, run it with\ncontainer machine run\n, and have it call Ollama on macOS over the VM network.\nGetting there has two halves, and the first one fails silently.\ncontainer machine create debian:13\nsucceeds. The machine is created. It just never boots, and nothing on the command line tells you why.\nThe official\ndebian:13\nimage has no\n/sbin/init\n, so a machine made from it stays\nstopped\nforever.\nA small Dockerfile adding\nsystemd-sysv\nfixes it, and two more lines stop every machine from sharing\none machine-id\n.\nThe VM reaches the Mac at\n192.168.64.1\n, with no port forwarding and no hostname to look up.\nOllama must listen on\n0.0.0.0\n, and\nbrew services restart\nsilently undoes that\n.\nFrom inside Debian,\ngemma4:e2b\nanswered at\n45.2 tok/s\n, loaded\n100% on the GPU\n.\nGemma 4 thinks before it answers, so a small token limit returns an\nempty reply\nthat looks like success.\nhttps://github.com/xbill9/apple-container-debian-tips\nThe Machine\nEverything below was run on 2026-09-13:\nMac\nVM\nHardware\nApple M3, 8 GB\n2 CPUs, 2 GB\nOS\nmacOS 27.0 (26A428)\nDebian GNU/Linux 13 (trixie)\nKernel\nDarwin\nLinux 6.18.35 aarch64\nRuntime\ncontainer\n1.4.1\nsystemd,\nrunning\nLLM\nOllama 0.33.3 on Metal\ncalls\nhttp://192.168.64.1:8000\n8 GB is the constraint that shapes every choice here.\nOne model, one 2 GB machine, and the image builder stopped when it is not building.\nAt This Point You Should Have\nAn Apple silicon Mac on macOS 26 or newer.\ncontainer\ndoes not run on anything else.\nA real Terminal window for the two steps that need a TTY.\nHomebrew, for Ollama.\nAbout 1.4 GB of disk for a Debian machine image, plus the model.\nSteps 0 through 5 build the VM. Steps 6 through 9 add the model. The two halves are independent, and\nStep 7 tests the Mac on its own before a VM is involved\n, which is what makes a failure in Step 9 easy to place.\nContainers and Machines Are Not the Same Thing\nThis distinction caused the most confusion, so it goes first.\ncontainer\nruns Linux in two ways:\nContainer\nMachine\nWhat runs\none program from the image\na whole booted Linux system (systemd)\nDisk\nthrowaway\npersistent\nYour Mac home folder\nnot mounted\nmounted at\n/Users/<you>\nYou are\nroot\nyour own Mac user (uid 501)\nOfficial\ndebian:13\nworks?\nyes, as-is\nno\nA container is fine for running one command.\nA machine is what you want for an app you keep customizing\n— packages you installed yesterday are still there, your source tree is already mounted, and services start under systemd like they would on any Debian server.\nAnd\ncontainer\nis not Docker. There is no Docker Desktop, no\ndockerd\nand no containerd process. Each container and each machine runs in its own lightweight VM, managed by Apple's own launchd agents. The images, though, are ordinary OCI images, so Docker Hub works.\nStep 0 — Install container and a Kernel\nDownload the signed\n.pkg\nfrom\nhttps://github.com/apple/container/releases\nand verify it:\npkgutil\n--check-signature\ncontainer-1.4.1-installer-signed.pkg\n# Developer ID Installer: Apple Inc. - Containerization (UPBK2H6LZM)\n# Notarization: trusted by the Apple notary service\nEnter fullscreen mode\nExit fullscreen mode\nsudo installer\nneeds a real TTY\n, so either run it in a Terminal window or use the GUI installer:\nopen container-1.4.1-installer-signed.pkg\nEnter fullscreen mode\nExit fullscreen mode\nThen start the services.\ncontainer system start\nstops to ask about downloading a Linux kernel, and a script cannot answer that prompt, so set the kernel explicitly:\ncontainer system start\ncontainer system kernel\nset\n--recommended\ncontainer system status\n#    status              running\n#    client.version      1.4.1\n#    server.version      1.4.1\nEnter fullscreen mode\nExit fullscreen mode\nThat pulls the Kata Containers kernel. Without it, nothing runs.\nStep 1 — Watch the Official Image Fail to Boot\nThis step exists because the failure is invisible.\nPull the image and look for an init:\ncontainer image pull docker.io/library/debian:13\ncontainer run\n--rm\ndebian:13 sh\n-c\n'ls -l /sbin/init; command -v ps ip less sudo; echo done'\nEnter fullscreen mode\nExit fullscreen mode\nls: cannot access '/sbin/init': No such file or directory\ndone\nEnter fullscreen mode\nExit fullscreen mode\nNo init, and no\nps\n,\nip\n,\nless\nor\nsudo\neither. A container never boots, so its image does not need an init. A machine boots a kernel, and Apple's\n/sbin.machine/init\nends with a hard-coded\nexec /sbin/init\n. Try it anyway and the logs say so:\ncontainer machine create debian:13\n--name\ndebian13-vm\n# creates, but never boots\ncontainer machine logs debian13-vm\n# /sbin.machine/init: 74: exec: /sbin/init: not found\ncontainer machine delete debian13-vm\nEnter fullscreen mode\nExit fullscreen mode\ncontainer machine logs <n>\nis the first place to look when a machine will not boot.\nApple's own docs use\nalpine:3.22\nbecause BusyBox happens to provide\n/sbin/init\n.\nStep 2 — Build a Debian Image That Has an Init\nIn an empty directory, create\nDockerfile\n:\nFROM\ndebian:13\nENV\nDEBIAN_FRONTEND=noninteractive\nRUN\napt-get update\n\\\n&&\napt-get\ninstall\n-y\n--no-install-recommends\n\\\nsystemd-sysv dbus\nsudo\nca-certificates procps less iproute2 iputils-ping curl\n\\\n&&\nrm\n-rf\n/var/lib/apt/lists/\n*\nRUN\nsystemctl mask systemd-modules-load.service\nRUN\n:\n>\n/etc/machine-id\n&&\nrm\n-f\n/var/lib/dbus/machine-id\nCMD\n[\"/sbin/init\"]\nEnter fullscreen mode\nExit fullscreen mode\nEvery line is there because something broke without it:\nLine\nWhy\nsystemd-sysv\nProvides\n/sbin/init\n, the one hard requirement\nprocps less iproute2 iputils-ping curl ca-certificates\nThe basic tools the official image leaves out.\ncurl\nis how the VM will call the model.\nsudo\nFirst boot already gives your Mac user passwordless sudo; only the package is missing\ndbus\nThe system message bus that systemd services use\nsystemctl mask systemd-modules-load.service\nApple's kernel has no loadable modules, so this unit always fails and systemd reports\ndegraded\n: > /etc/machine-id && rm -f /var/lib/dbus/machine-id\nInstalling dbus writes a machine ID into the image. Without this line\nevery machine from the image shares one ID\nCMD [\"/sbin/init\"]\nIgnored by machines, but marks this as a machine image\nThe machine-id line is the one I found the hard way. Every machine I created had the same ID, across separately built images and even from a snapshot whose\n/etc/machine-id\nwas emptied — because systemd copies dbus's build-time copy into an empty\n/etc/machine-id\nat boot. Emptying one file is not enough. Remove the other.\nBuild it. The builder holds 2 CPUs and 2 GB while it runs, and\ncontainer build\nstarts it but never stops it:\ncontainer builder start\ncontainer build\n-t\ndebian13-machine\n.\ncontainer builder stop\nEnter fullscreen mode\nExit fullscreen mode\nThe build took about 10 seconds.\ndebconf: ... Readline\nlines only mean there is no terminal.\nStep 3 — Check the Image Before You Commit to It\nThis is the check I skipped the first time:\ncontainer run\n--rm\ndebian13-machine sh\n-c\n'ls -l /sbin/init; wc -c < /etc/machine-id; ls /var/lib/dbus/machine-id'\nEnter fullscreen mode\nExit fullscreen mode\nlrwxrwxrwx 1 root root 22 Apr 13 19:38 /sbin/init ->\n../lib/systemd/systemd\n0\nls: cannot access '/var/lib/dbus/machine-id': No such file or directory\nEnter fullscreen mode\nExit fullscreen mode\nYou want\n/sbin/init\npresent, a 0-byte machine-id, and no dbus copy. Check with\nls\n, not\nreadlink -f\n—\nreadlink -f /sbin/init\nprints a path even when the file is missing.\nStep 4 — Create the Machine, and Wait for It\ncontainer machine create debian13-machine\n--name\ndev-vm\n--cpus\n2\n--memory\n2G\nEnter fullscreen mode\nExit fullscreen mode\ncreate\nreturns in about a second,\nbefore the VM has booted\n. A command sent straight away fails with an error that points in completely the wrong direction:\nError: The operation couldn’t be completed. Operation not supported on socket\nEnter fullscreen mode\nExit fullscreen mode\nThat is not a socket problem and not a TTY problem. It is a machine that has not finished booting. Poll until it answers — 13 seconds here:\nuntil\ncontainer machine run\n-n\ndev-vm\n--root\n--\ntrue\n2>/dev/null\n;\ndo\nsleep\n2\n;\ndone\nEnter fullscreen mode\nExit fullscreen mode\nThen give systemd a few more seconds.\nsystemctl is-system-running\nsays\ninitializing\nfor a while after first boot.\nStep 5 — Check It Is Really Running\ncontainer machine run\n-n\ndev-vm\n--\n'. /etc/os-release; echo \"$PRETTY_NAME\"; uname -srm; systemctl is-system-running; id; sudo -n id -u; nproc'\nEnter fullscreen mode\nExit fullscreen mode\nDebian GNU/Linux 13 (trixie)\nLinux 6.18.35 aarch64\nrunning\nuid=501(xbill) gid=20(dialout) groups=20(dialout)\n0\n2\nEnter fullscreen mode\nExit fullscreen mode\n✅\nrunning\n, not\ndegraded\nor\ninitializing\n: systemd is fully up.\n✅\nid\n: you are your Mac user, created in the VM on first boot.\n✅\nsudo -n id -u\nprinted\n0\n: passwordless sudo works.\n✅ Your Mac home folder is mounted at\n/Users/<you>\n.\nPass a command as one quoted argument.\nmachine run\njoins its arguments with spaces and re-parses them with\n/bin/bash -c\ninside the VM, so your host-side quoting is lost:\ncontainer machine run\n-n\nmkdm-test-vm\n--root\n--\necho\n'$0'\n'a    b'\n# /bin/bash a b          ($0 expanded in the VM, spaces collapsed)\nEnter fullscreen mode\nExit fullscreen mode\nSo\n-- sh -c '...'\nsilently runs the wrong command and prints nothing. The snippet is already run by bash; hand it over whole.\nStep 6 — Customize It Like a Real Box\nThis is the part a container cannot do. Anything you install lives on the machine's own disk:\ncontainer machine run\n-n\ndev-vm\n--root\n--\n'apt-get update && apt-get install -y --no-install-recommends git vim-tiny'\nEnter fullscreen mode\nExit fullscreen mode\nAnd it survives a restart:\ncontainer machine stop dev-vm\n# takes about 10 s\ncontainer machine run\n-n\ndev-vm\n--\n'git --version'\n# boots it again\n# git version 2.47.3\nEnter fullscreen mode\nExit fullscreen mode\nAfter a restart,\nmachine run\nanswers before systemd does —\nsystemctl\nsaid\nFailed to connect to system scope bus\nfor the first few seconds. Wait before using it.\nDeleting the machine deletes these changes.\nTo keep them, add the packages to the Dockerfile and rebuild, or snapshot the machine into an image.\nInteractive shells need a real Terminal window; from scripts and agent tools they fail with the same \"not supported\" error as Step 4:\ncontainer machine run\n-n\ndev-vm\n# shell as you\ncontainer machine run\n-n\ndev-vm\n--root\n# shell as root\nEnter fullscreen mode\nExit fullscreen mode\nThe short way\nSteps 2 through 5 are what\nbin/mkdebian-machine\nin the repo does, including the\n/sbin/init\ncheck, the boot poll, and stopping the builder if it was stopped before. A\n--setup\nscript runs inside the new machine as root:\nbin/mkdebian-machine new\n-t\ndebian13-machine\n-m\ndev-vm\n-p\nsudo\nbin/mkdebian-machine new\n-m\ndev-vm\n-p\n\"git vim\"\n--setup\n./setup-dev.sh\nEnter fullscreen mode\nExit fullscreen mode\n-p\npackages are baked into the image.\n--setup\nchanges only that machine.\nbin/mkdebian-machine publish dev-vm <ref>\nsnapshots a hand-customized machine into an image and scrubs your user, SSH host keys and logs first.\nWhy the Model Does Not Run in the VM\nA VM gets no GPU.\nLinux inside\ncontainer\nsees virtual CPUs and virtual devices and has no Metal access. A model running there runs on CPU, in the 2 GB you gave the machine.\nSo the model runs on\nmacOS\n, where Ollama uses the M3 GPU, and the VM calls it over the VM network:\ndev-vm      192.168.64.x         (changes on restart)\n    │  default route + DNS → 192.168.64.1\n    ▼\nvmenet0 ─ bridge100 on the Mac  192.168.64.1   ← the Mac itself on the VM network\n    │\nOllama  *:8000  →  Metal GPU\nEnter fullscreen mode\nExit fullscreen mode\n192.168.64.1\nis the Mac's address on\nbridge100\n. It is the VM's gateway and DNS server, and\nit did not change across VM restarts\n. The VM's own address did.\nThere is no\nhost.docker.internal\nor\nhost.container.internal\n. Use the IP.\nThere is no port forwarding. The VM connects straight to the Mac, which\nonly works because Ollama listens on all interfaces\n. On\n127.0.0.1\nthe VM cannot reach it.\nStep 7 — Point Ollama at the VM Network\nInstall Ollama and a model that fits in 8 GB:\nbrew\ninstall\nollama\nbrew services start ollama\nOLLAMA_HOST\n=\n127.0.0.1:8000 ollama pull gemma4:e2b\nEnter fullscreen mode\nExit fullscreen mode\nOllama runs as a Homebrew launchd service. This Mac's plist had already been customized to port 8000 with\nOLLAMA_KV_CACHE_TYPE=q4_0\nand\nOLLAMA_FLASH_ATTENTION=1\n, so only the host changes. Homebrew's own default is\n127.0.0.1:11434\n; set port 8000 too, or adjust the URLs below.\nP\n=\n~/Library/LaunchAgents/homebrew.mxcl.ollama.plist\ncp\n-p\n\"\n$P\n\"\n\"\n$P\n.bak-\n$(\ndate\n+%Y%m%d%H%M%S\n)\n\"\n/usr/libexec/PlistBuddy\n-c\n'Set :EnvironmentVariables:OLLAMA_HOST 0.0.0.0:8000'\n\"\n$P\n\"\n# restart so launchd rereads the plist\nlaunchctl bootout gui/\n$(\nid\n-u\n)\n/homebrew.mxcl.ollama\nlaunchctl bootstrap gui/\n$(\nid\n-u\n)\n\"\n$P\n\"\nlsof\n-nP\n-iTCP\n:8000\n-sTCP\n:LISTEN\n# ollama  *:8000\nEnter fullscreen mode\nExit fullscreen mode\nDo not use\nbrew services restart ollama\n.\nIt regenerates the plist from the formula, which sets only\nOLLAMA_FLASH_ATTENTION=1\nand\nOLLAMA_KV_CACHE_TYPE=q8_0\n. That silently puts\nOLLAMA_HOST\nback to localhost — and the VM stops reaching the model with no change on the VM side at all.\nThis setup is open on purpose. With\n0.0.0.0\nand the macOS application firewall off, Ollama was also reachable from the Wi-Fi network. For a demo on a home network that is the point. Anywhere else, it is the first thing to change.\nStep 8 — Test the Mac Side First\nbin/test-mac-ollama\nchecks Ollama on the Mac without a VM involved: the listener, localhost and\n192.168.64.1\n, the plist, the native, streaming and OpenAI-compatible APIs, and GPU placement. It unloads the models on exit and exits 0 only when every check passes.\nbin/test-mac-ollama\nEnter fullscreen mode\nExit fullscreen mode\n== 1. listener on port 8000\n  ollama *:8000\n  PASS  ollama listens on all interfaces, so VMs can reach it\n== 2. HTTP on http://127.0.0.1:8000\n  PASS  Ollama 0.33.3 answers on http://127.0.0.1:8000\n== 3. VM-facing address 192.168.64.1\n  PASS  Ollama answers on http://192.168.64.1:8000 (bridge100), the address VMs use\n== 4. launchd config\n  info  OLLAMA_HOST in plist: 0.0.0.0:8000\n== 5. models\n  PASS  gemma4:e2b is installed\n== 6. native API\n  23 tokens @ 43.6 tok/s, load 5.7 s\n  PASS  gemma4:e2b answered through /api/generate\n== 7. streaming\n  PASS  streaming delivers incremental chunks\n== 8. OpenAI-compatible API\n  PASS  /v1/chat/completions returned text\n== 9. GPU\n  PASS  gemma4:e2b loaded 100% on the GPU (1.6 GB)\n== 10. second model: gemma4:e2b-it-qat\n  33 tokens @ 43.3 tok/s, load 7.5 s\n  PASS  gemma4:e2b-it-qat answered through /api/generate\n  PASS  gemma4:e2b-it-qat loaded 100% on the GPU (3.3 GB)\n\n10 passed, 0 failed\nOllama works on the Mac. Next: bin/test-vm-ollama\nEnter fullscreen mode\nExit fullscreen mode\nThe order is the point.\nIf this passes and the VM test fails, the problem is the VM or the network path, not Ollama. Its check 3 is skipped until a container or machine has created\nbridge100\n, which is one more reason to build the VM first.\nStep 9 — Call the Model From the VM\nNow from inside Debian. These runs used my long-lived machine,\ndebian13-vm\n, built from the same kind of image; substitute\ndev-vm\n. The native API:\ncontainer machine run\n-n\ndebian13-vm\n--\n'curl -s http://192.168.64.1:8000/api/generate -d \"{\\\"model\\\":\\\"gemma4:e2b\\\",\\\"prompt\\\":\\\"Say hello from a Debian VM in five words.\\\",\\\"stream\\\":false,\\\"think\\\":false}\"'\n# \"response\":\"Hello from Debian VM.\"\nEnter fullscreen mode\nExit fullscreen mode\nAnd the OpenAI-compatible API, with base URL\nhttp://192.168.64.1:8000/v1\n:\ncontainer machine run\n-n\ndebian13-vm\n--\n'curl -s http://192.168.64.1:8000/v1/chat/completions -H \"Content-Type: application/json\" -d \"{\\\"model\\\":\\\"gemma4:e2b\\\",\\\"messages\\\":[{\\\"role\\\":\\\"user\\\",\\\"content\\\":\\\"Name one Debian release codename.\\\"}],\\\"reasoning_effort\\\":\\\"none\\\"}\"'\n# \"content\":\"One Debian release codename is **Bookworm**.\"\nEnter fullscreen mode\nExit fullscreen mode\nThat second endpoint is the whole argument.\nAnything in the VM that speaks the OpenAI API — your app, your agent, your test harness — takes\nhttp://192.168.64.1:8000/v1\nas its base URL and gets a model on the Mac GPU. The app is customized and run in Debian. The inference never touches the VM's 2 GB.\nStep 10 — Prove the Round Trip\nbin/test-vm-ollama\nruns the whole path from inside a machine, then asks the Mac's\n/api/ps\nwhere the model actually loaded. It needs\ncurl\nin the machine and\njq\non the Mac:\nbin/test-vm-ollama\n# debian13-vm, gemma4:e2b + gemma4:e2b-it-qat\nbin/test-vm-ollama\n-n\ndev-vm\n-m\ngemma4:e2b\n--no-qat\nEnter fullscreen mode\nExit fullscreen mode\n== 1. network path\n  PASS  VM reaches http://192.168.64.1:8000 (connect 0.002372 s, Ollama 0.33.3)\n== 2. models\n  gemma4:e2b-it-qat gemma4:e2b gemma4:e4b\n  PASS  gemma4:e2b is listed\n== 3. native API\n  reply: Debian is a free and open-source operating system that forms the basis for many other Linux distributions.\n  22 tokens @ 45.2 tok/s, load 5.4 s\n  PASS  gemma4:e2b answered through /api/generate\n== 4. streaming\n  14 chunks: 1, 2, 3, 4, 5\n  PASS  streaming delivers incremental chunks\n== 5. OpenAI-compatible API\n  PASS  /v1/chat/completions returned text\n== 6. GPU (asked from the Mac)\n  PASS  gemma4:e2b loaded 100% on the GPU (1.6 GB)\n== 7. second model: gemma4:e2b-it-qat\n  35 tokens @ 38.9 tok/s, load 12.4 s\n  PASS  gemma4:e2b-it-qat answered through /api/generate\n  PASS  gemma4:e2b-it-qat loaded 100% on the GPU (3.3 GB)\n\n8 passed, 0 failed\nEnter fullscreen mode\nExit fullscreen mode\nThe VM hop costs almost nothing.\nConnect time was 2 ms.\ngemma4:e2b\ngenerated at 45.2 tok/s through the VM against 43.6 tok/s from the Mac itself — single runs each, so that is run-to-run variation, not the VM being faster. Generation here is limited by the M3's memory, not by the network between Debian and macOS.\nThe GPU check reads\nsize_vram / size\nfrom\n/api/ps\n, the same numbers\nollama ps\nprints as\n100% GPU\n. A wrong URL fails at step 1 and exits 1; a missing machine or missing\ncurl\nexits 2.\nAn Empty Reply Means Gemma 4 Was Still Thinking\nGemma 4 is a reasoning model. With a small token limit, the whole budget goes to hidden reasoning and the answer comes back\nempty\n— with an HTTP 200 and valid JSON:\nRequest\nResult\nnative,\nnum_predict: 40\n❌\n\"response\":\"\"\n,\ndone_reason: length\nOpenAI,\nmax_tokens: 20\n❌\n\"content\":\"\"\n,\nfinish_reason: length\n;\nmessage.reasoning\nstarts\nThinking Process:\nnative,\n\"think\": false\n✅\nHello from Debian VM.\nin 6 tokens\nOpenAI,\n\"reasoning_effort\": \"none\"\n✅ an answer in 11 tokens\nOpenAI, no limit\n⚠️\nBookworm\n, but 159 tokens, mostly reasoning\nCheck\nfinish_reason\nin your app, not just the status code.\nTurn thinking off for quick answers, or leave room for the reasoning.\nWhich Model Fits in 8 GB\nModel\nQuantization\nLoaded\nSpeed\n🥇\ngemma4:e2b\nQ4_K_M\n1.7 GB\n~44 tok/s\n🥈\ngemma4:e2b-it-qat\nQ4_0, QAT (Google)\n3.6 GB\n~38–44 tok/s\n—\ngemma4:e4b\nQ4_K_M\nnot run\n9.6 GB on disk, more than this Mac's RAM\ngemma4:e2b\nis the comfortable choice. The QAT build is better quality at 4 bits, but free memory dropped to 6% with a machine running. When you are done, unload it rather than leaving 3.6 GB pinned:\ncurl\n-s\nhttp://127.0.0.1:8000/api/generate\n-d\n'{\"model\":\"gemma4:e2b-it-qat\",\"keep_alive\":0}'\nEnter fullscreen mode\nExit fullscreen mode\nWhere the Split Pays Off\nThe app side is a real Debian server.\nsystemd, apt, sudo, a persistent disk, and your Mac source tree already mounted. Customize it, snapshot it with\nmkdebian-machine publish\n, and hand the image to someone else.\nThe model side is the Mac at full speed.\nMetal, unified memory, no GPU passthrough to configure — because there is none to configure.\nThe seam is one URL.\nhttp://192.168.64.1:8000/v1\nis an OpenAI-compatible endpoint. Point the same app at a different Ollama or llama.cpp server with\n--url\nand nothing in the VM changes.\nThe VM does the work that needs Linux. The Mac does the work that needs the GPU, and neither side pretends to be the other.\nWhen Something Goes Wrong\nSymptom\nCause\nFix\nMachine stays\nstopped\n; logs show\nexec: /sbin/init: not found\nPlain\ndebian:13\n, no init\nStep 2\nOperation not supported on socket\n/\nby device\nMachine still booting, or an interactive shell with no TTY\nStep 4 poll; pass a command\nsystemctl is-system-running\n→\ndegraded\nsystemd-modules-load.service\nfailed\nMask it (Step 2)\nTwo machines share\n/etc/machine-id\nImage carries\n/var/lib/dbus/machine-id\nLast\nRUN\nline of Step 2\nA quoted command prints nothing\nArguments joined and re-parsed by bash\nOne quoted argument\nPlugin 'container-images' not found\nTyped\ncontainer images ls\ncontainer image ls\nVM gets no HTTP 200 from\n192.168.64.1:8000\nOllama back on localhost, often after\nbrew services restart\nStep 7, then\nbin/test-mac-ollama\nReply is empty,\nfinish_reason: length\nGemma 4 spent the budget thinking\nthink: false\n/\nreasoning_effort: \"none\"\nCheat Sheet\n# 0. install, start, kernel\ncontainer system start\ncontainer system kernel\nset\n--recommended\n# 1. the official image cannot boot as a machine\ncontainer run\n--rm\ndebian:13 sh\n-c\n'ls -l /sbin/init'\n# 2. build a Debian image with an init (Dockerfile above)\ncontainer builder start\ncontainer build\n-t\ndebian13-machine\n.\ncontainer builder stop\n# 3. check it\ncontainer run\n--rm\ndebian13-machine sh\n-c\n'ls -l /sbin/init; wc -c < /etc/machine-id'\n# 4. create, then wait for it\ncontainer machine create debian13-machine\n--name\ndev-vm\n--cpus\n2\n--memory\n2G\nuntil\ncontainer machine run\n-n\ndev-vm\n--root\n--\ntrue\n2>/dev/null\n;\ndo\nsleep\n2\n;\ndone\n# 5. check it\ncontainer machine run\n-n\ndev-vm\n--\n'systemctl is-system-running; id; sudo -n id -u'\n# 6. customize it\ncontainer machine run\n-n\ndev-vm\n--root\n--\n'apt-get update && apt-get install -y git'\n# 2-5, the short way\nbin/mkdebian-machine new\n-t\ndebian13-machine\n-m\ndev-vm\n-p\nsudo\n# 7. Ollama on all interfaces, restarted with launchctl\n/usr/libexec/PlistBuddy\n-c\n'Set :EnvironmentVariables:OLLAMA_HOST 0.0.0.0:8000'\n~/Library/LaunchAgents/homebrew.mxcl.ollama.plist\nlaunchctl bootout gui/\n$(\nid\n-u\n)\n/homebrew.mxcl.ollama\nlaunchctl bootstrap gui/\n$(\nid\n-u\n)\n~/Library/LaunchAgents/homebrew.mxcl.ollama.plist\n# 8. the Mac on its own\nbin/test-mac-ollama\n# 9. the model from the VM\ncontainer machine run\n-n\ndev-vm\n--\n'curl -s http://192.168.64.1:8000/v1/models'\n# 10. the whole round trip\nbin/test-vm-ollama\n-n\ndev-vm\n# clean up\ncontainer machine stop dev-vm\ncontainer machine delete dev-vm\ncontainer image\nrm\ndebian13-machine\nEnter fullscreen mode\nExit fullscreen mode\nSummary\nThe goal of this article was to get a persistent Debian 13 machine running under Apple's\ncontainer\nCLI, and then let apps inside it use a local LLM on the Mac's GPU. The key to the solution was splitting the two: build an image the machine can actually boot, and leave the model on macOS where Metal is, reached over the VM network at\n192.168.64.1\n. The results were:\n🟢 The official\ndebian:13\nimage cannot boot as a machine; adding\nsystemd-sysv\nplus a machine-id scrub gives Debian 13 with systemd\nrunning\n, your Mac user and passwordless sudo\n🟢 Packages installed in the machine survived a restart\n🟢 From the VM,\ngemma4:e2b\nanswered through the native, streaming and OpenAI-compatible APIs at 45.2 tok/s, loaded 100% on the GPU\n🟢\nbin/test-mac-ollama\nreported\n10 passed, 0 failed\nand\nbin/test-vm-ollama\nreported\n8 passed, 0 failed\n⚠️\nbrew services restart ollama\nsilently reverts Ollama to localhost, and the VM loses the model\n⚠️ Gemma 4 returns an empty answer with a small token limit unless thinking is turned off\nScope: one Apple M3 Mac with 8 GB on macOS 27.0 (26A428),\ncontainer\n1.4.1, a Debian 13 machine with 2 CPUs and 2 GB, Ollama 0.33.3 with\nOLLAMA_KV_CACHE_TYPE=q4_0\nand flash attention on. Each throughput figure is a single run from the test scripts on 2026-09-13, and the model replies vary from run to run.\nThe strategy for running a customized Debian machine on Apple container with a local LLM on the Mac GPU was validated with an incremental step by step approach.\nReferences\napple-container-debian-tips | GitHub\napple/container | GitHub\nOllama\nGemma 4 on Ollama",
  "metadata": {
    "title": "Build in the VM, Think on the Mac GPU: Debian 13 on Apple container With a Local Gemma 4 - DEV Community",
    "description": "Apple's container CLI 1.4.1 cannot boot the official debian:13 image as a machine, because it has no /sbin/init. A small Dockerfile fixes that and gives you a persistent Debian 13 VM with systemd, your Mac user and your home folder. The VM has no GPU, so Ollama runs on macOS and the VM calls it at 192.168.64.1:8000. From inside Debian, gemma4:e2b answered at 45.2 tok/s, loaded 100% on the M3 GPU. Tagged with debian, macos, ollama, linux.",
    "keywords": "debian, macos, ollama, linux, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/gde/build-in-the-vm-think-on-the-mac-gpu-debian-13-on-apple-container-with-a-local-gemma-4-2d8b",
    "og:title": "Build in the VM, Think on the Mac GPU: Debian 13 on Apple container With a Local Gemma 4",
    "og:description": "Apple's container CLI 1.4.1 cannot boot the official debian:13 image as a machine, because it has no /sbin/init. A small Dockerfile fixes that and gives you a persistent Debian 13 VM with systemd, your Mac user and your home folder. The VM has no GPU, so Ollama runs on macOS and the VM calls it at 192.168.64.1:8000. From inside Debian, gemma4:e2b answered at 45.2 tok/s, loaded 100% on the M3 GPU.",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fxbill9%2Fapple-container-debian-tips%2Fmain%2Fdocs%2Farticle%2Fdevto-cover.9231382f.jpg",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "Build in the VM, Think on the Mac GPU: Debian 13 on Apple container With a Local Gemma 4",
    "twitter:description": "Apple's container CLI 1.4.1 cannot boot the official debian:13 image as a machine, because it has no /sbin/init. A small Dockerfile fixes that and gives you a persistent Debian 13 VM with systemd, your Mac user and your home folder. The VM has no GPU, so Ollama runs on macOS and the VM calls it at 192.168.64.1:8000. From inside Debian, gemma4:e2b answered at 45.2 tok/s, loaded 100% on the M3 GPU.",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fxbill9%2Fapple-container-debian-tips%2Fmain%2Fdocs%2Farticle%2Fdevto-cover.9231382f.jpg"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/58b9abc10b2d/code/body-000.txt",
      "chars": 184,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-001.txt",
      "chars": 42,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-002.txt",
      "chars": 184,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-003.txt",
      "chars": 142,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-004.txt",
      "chars": 63,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-005.txt",
      "chars": 209,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-006.txt",
      "chars": 371,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-007.txt",
      "chars": 85,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-008.txt",
      "chars": 115,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-009.txt",
      "chars": 150,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-010.txt",
      "chars": 77,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-011.txt",
      "chars": 78,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-012.txt",
      "chars": 83,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-013.txt",
      "chars": 143,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-014.txt",
      "chars": 113,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-015.txt",
      "chars": 134,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-016.txt",
      "chars": 118,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-017.txt",
      "chars": 165,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-018.txt",
      "chars": 117,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-019.txt",
      "chars": 134,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-020.txt",
      "chars": 219,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-021.txt",
      "chars": 97,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-022.txt",
      "chars": 364,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-023.txt",
      "chars": 20,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-024.txt",
      "chars": 987,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-025.txt",
      "chars": 247,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-026.txt",
      "chars": 337,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-027.txt",
      "chars": 142,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-028.txt",
      "chars": 856,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-029.txt",
      "chars": 93,
      "language": "text"
    },
    {
      "file": "attachments/58b9abc10b2d/code/body-030.txt",
      "chars": 1563,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://github.com/xbill9/apple-container-debian-tips",
      "label": "https://github.com/xbill9/apple-container-debian-tips",
      "type": "a"
    },
    {
      "href": "https://github.com/apple/container/releases",
      "label": "https://github.com/apple/container/releases",
      "type": "a"
    },
    {
      "href": "https://github.com/apple/container",
      "label": "apple/container | GitHub",
      "type": "a"
    },
    {
      "href": "https://ollama.com/",
      "label": "Ollama",
      "type": "a"
    },
    {
      "href": "https://ollama.com/library/gemma4",
      "label": "Gemma 4 on Ollama",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:48:31Z",
  "source_url": "https://dev.to/gde/build-in-the-vm-think-on-the-mac-gpu-debian-13-on-apple-container-with-a-local-gemma-4-2d8b",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 24578,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/58b9abc10b2d/pages/source.html",
  "local_html": "attachments/58b9abc10b2d/pages/article.html",
  "body_base_path": "attachments/58b9abc10b2d/pages/",
  "body_markdown_path": "attachments/58b9abc10b2d/pages/article.md"
}