CodeNavi on macOS¶
This guide covers running CodeNavi on macOS — Intel or Apple Silicon. For Linux / WSL / GPU setups see setup.md.
TL;DR: Mac is a great search/MCP client (sub-2 s queries at full quality parity with the GPU dev box). It is a poor indexing host under Docker because the embedder runs CPU-only inside the Linux VM (no Metal/MPS access from any container runtime). Two production-quality paths to get an index onto the Mac: (1) pre-build on a Linux GPU box and
scpthe LanceDB tables — see cloud-reindex.md; (2) a vendor-assisted remote-embedder setup — the proprietary Buric-E4B embedder served from a LAN/cloud GPU box you control; contact support to set it up. Generic-embedder APIs (OpenAI, Cohere) work but lose ~30 retrieval-quality points on code; not recommended. Native MPS install is fast at indexing but regresses retrieval quality.7
What works on Mac¶
| Status | Notes | |
|---|---|---|
| Search & query | ✅ at-parity with GPU host | ~1.2 s/query post-warmup |
MCP server (/mcp) |
✅ | works with Claude Code / Cursor / Gemini CLI |
Web UI (:8765/ui) |
✅ | static SPA served by the MCP server (same origin) |
| Chat (host Ollama) | ✅ | reachable via host.docker.internal:11434 |
| Indexing under Docker | ⚠️ very slow (CPU-only) | ~100× slower than the silicon's actual capability — not a Mac fault, a Linux-VM-no-Metal limitation that affects every Docker runtime equally |
| Indexing native (MPS) | ✅ in code4 | needs setup.sh --local; not yet validated end-to-end on a real Mac |
If your install requires indexing on the Mac itself, plan on the native install. If you can pre-build the index on a Linux GPU host and ship it, the Docker path on Mac is fully viable.
Recommended path: colima (headless Docker)¶
colima is the supported headless Docker runtime on macOS for CodeNavi. Mature, full docker compose semantics, what CodeNavi has actually been benchmarked end-to-end on.
# 1. Prereqs (Homebrew)
brew install colima docker docker-compose
# 2. Compose v2 plugin symlink (Homebrew installs docker-compose as a standalone binary)
mkdir -p ~/.docker/cli-plugins
ln -sf /opt/homebrew/opt/docker-compose/bin/docker-compose ~/.docker/cli-plugins/docker-compose
# 3. Start the VM. The 4B embedder loads in fp32 (~16 GB) on CPU, so the VM needs
# ≥20 GB — i.e. a ≥32 GB Mac. On a 16 GB Mac, don't index or serve under Docker;
# ship a pre-built index from a GPU box and query it (see the TL;DR paths above).
colima start --cpu 6 --memory 20 --disk 30 --vm-type=vz --arch aarch64
# 4. Verify
docker version --format 'client:{{.Client.Version}} server:{{.Server.Version}}'
docker compose version
Then run CodeNavi as documented in air-gapped.md:
cd ~/codenavi
./setup.sh # auto-detects macOS, sets TORCH_INDEX=cpu, writes deploy/.env
cd deploy
TRANSFORMERS_OFFLINE=0 docker compose up -d # first run, downloads models
Recommended colima resources by Mac¶
| Mac | --cpu |
--memory |
--disk |
|---|---|---|---|
| Mac mini M4 / 16 GB | — | — | — |
| MacBook Pro M-series / 32 GB | 8 | 20 | 50 |
| Mac Studio / 64 GB+ | 10 | 28 | 100 |
Leave 6 GB+ of host RAM for macOS and any LLM you want to run on the host (Ollama). The 4B Buric-E4B embedder needs ~16 GB resident on CPU, so a 16 GB Mac can't run it under Docker and still leave macOS headroom — on 16 GB, ship a pre-built index from a GPU box and skip local indexing/serving. Size the VM ≥20 GB on 32 GB+ Macs.
Alternative: Apple Container (container CLI)¶
Apple shipped a native container runtime at WWDC 2025 (brew install container + brew install container-compose). Lighter than colima — no qemu/lima — uses Apple's native VirtualizationFramework. Not yet a complete colima replacement for CodeNavi as of container 0.12.3 / container-compose 0.11.0, but worth re-checking.
| colima | Apple container |
|
|---|---|---|
| Compose support | full (up, down, ps, logs, exec, restart, build, …) |
minimal: only up, down, build |
.env substitution in compose |
yes | none (even with --env-file, vars stay literal) |
build.context honored |
yes | no (looks in compose-file dir) |
extra_hosts: host-gateway |
yes | no (kata net has no host gateway) |
| Named volumes | yes | no (bind-mounts only) |
| Daemon weight | lima + qemu/vz | native VirtualizationFramework only |
| Boot time (warm) | ~3 s | ~5 s for first container after kernel install |
| Image build time on M4 | ~10 min | ~3 min (Apple BuildKit shim is fast) |
| Metal/MPS access in container | ❌ none | ❌ none (same Linux-VM limitation — verified) |
| Maturity for CodeNavi | ✅ tested end-to-end | ✅ tested via wrapper; raw compose ergonomics rough |
Why Apple Container doesn't unlock MPS (verified via device probe inside
alpineundercontainer 0.12.3): the kata kernel Apple ships has novirtio_gpumodule loaded, no/sys/class/drmdirectory, no PCI device for GPU, no/dev/dri /dev/metal* /dev/accel*nodes. Even if Apple later enablesvirtio_gpu, exposing Metal to a Linux guest requires a Metal-aware host renderer (analogous to virglrenderer/venus for OpenGL/Vulkan) — which doesn't exist because Metal is macOS-only. Native install is the only Metal path on Mac. This is the same limitation colima has — neither container runtime can give Linux access to Apple GPU.
Quick smoke test:
brew install container container-compose
container system start # auto-installs the kata kernel on first run
container run --rm alpine:latest sh -c 'uname -a'
If you want to use Apple Container for CodeNavi, use the bundled wrapper that bypasses container-compose's gaps and gives you the full ops loop. It calls container build and container run directly with the right flags:
cd ~/codenavi
brew install container container-compose
container system start
./mac/run-apple-container.sh build # ~3 min on M4
./mac/run-apple-container.sh up
./mac/run-apple-container.sh status # /health + container row
./mac/run-apple-container.sh logs -f
./mac/run-apple-container.sh exec -- ls /app/index
./mac/run-apple-container.sh restart
./mac/run-apple-container.sh down
The wrapper reads deploy/.env (set DEPLOY_DIR=/path/to/cn-deploy to point elsewhere) and respects CODENAVI_HOST, CODENAVI_PORT, RAM_LIMIT, CPUS, CODEBASE_PATH, CODEBASE_CONFIG, RERANKER_MODEL, HF_TOKEN. First run downloads ~3 GB of HF models into $DEPLOY_DIR/hf-cache/; subsequent runs auto-detect and switch to offline mode.
Why not raw container-compose up? Today (container-compose 0.11.0) it doesn't substitute ${VAR:-default} from your env, doesn't honor build.context, can't translate extra_hosts: host-gateway, doesn't support named volumes, and gives you only up/down/build — no logs, ps, exec, restart. The wrapper is ~150 lines of bash that does what compose would, plus the ops verbs. Re-check this section when container-compose ships substitution + the missing verbs.
Native install (MPS acceleration) — not production-ready¶
Status (2026-05-15): native install with MPS gives a real ~21× indexing speedup but regresses retrieval quality by ~16 points on the laravel benchmark (86 % in Docker → 64-70 % native), regardless of CPU or MPS device. Investigated thoroughly7: we eliminated torch version (tried 2.6, 2.9, 2.12), BLAS implementation (Apple Accelerate AND conda-forge OpenBLAS), reranker on/off, and Python version (3.13 vs 3.14) as causes. The 16-point gap survives every combination. Likely culprits — Darwin libc vs glibc, Apple libomp vs GNU OpenMP, sentence-transformers Mac code paths, threading reduction order — would each require multi-day bisection. Until somebody invests that effort, use the Docker path on Mac for production; native install is fine for indexing-throughput experiments only.
The only way to use the M-series GPU + Neural Engine for embedding inference is to run CodeNavi natively, outside any container.
cd ~/codenavi
./setup.sh --local # creates venv at ./venv, installs requirements
source venv/bin/activate
./reindex.sh --full # uses MPS automatically via device detection
./start.sh # runs the MCP server + web UI (server.py) on host
The MPS code path (core/embedder.py:_get_device()) auto-detects:
CUDA > MPS (Apple Silicon, native install only) > CPU
Override via CODENAVI_DEVICE=cuda|mps|cpu if needed.
Validated indexing throughput: 14.2 chunks/sec on M4 16 GB (~21× faster than CPU-in-Docker on the same machine). Validated retrieval quality: 70 % on laravel (vs 86 % in Docker7). Don't ship native as a production install path.
Tradeoffs: native install gives you MPS but loses container isolation. The container is read-only and runs as a non-root appuser; the native install runs as your user with whatever access your shell has. Choose by threat model.
Gotchas¶
1. Docker.app cannot be launched over SSH¶
macOS sandbox blocks open -a Docker and launchctl asuser ... open -a Docker from any non-GUI session. Symptom:
$ ssh user@mac "open -a Docker"
The application /Applications/Docker.app cannot be opened for an unexpected reason
This blocks any remote / scripted Mac install via SSH. Use colima or Apple Container instead — both work fine over SSH.
2. Multi-user Mac brew prefix ownership¶
When /opt/homebrew was installed by user A and user B (e.g. a dedicated codenavi service user) needs to install CodeNavi-related packages, brew refuses with "directories not writable" even if user B is in the admin group.
Fix:
sudo chown -R <user-b> /opt/homebrew
# Optional: NOPASSWD sudo for headless installs
echo "<user-b> ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/<user-b>-nopasswd
sudo chmod 440 /etc/sudoers.d/<user-b>-nopasswd
Single-user Macs are unaffected.
3. CODENAVI_HOST is the host-side port-forward bind, not the container-side uvicorn bind¶
If you want to expose CodeNavi on the LAN instead of just localhost, set CODENAVI_HOST=0.0.0.0 in deploy/.env. CODENAVI_BIND (default 0.0.0.03) handles the inside-container bind so Docker's port forward always reaches uvicorn regardless of CODENAVI_HOST. Don't conflate the two.
4. First-run model download requires TRANSFORMERS_OFFLINE=0¶
docker compose up -d defaults to offline mode after first run. The very first run needs:
TRANSFORMERS_OFFLINE=0 docker compose up -d
…to fetch the cross-encoder reranker (~80 MB); the Buric-E4B code embedder is baked into the image. Subsequent restarts use the cached reranker.
If you forget and the reranker fails to download, you'll see this ERROR-level log6 in /app/logs/codenavi.log:
Reranker failed to load — search quality reduced ~10pts.
If first run, restart with TRANSFORMERS_OFFLINE=0 to download the model.
5. Indexing on Mac under Docker is slow — index elsewhere¶
A 3.2k-file PHP project (~36k chunks) takes ~12 hours of CPU time inside the Mac Docker container. This is fundamental to running x86-server-class embedding inference (the 4B Buric-E4B embedder in fp32) on CPU; the M4 silicon can do it in minutes via Metal but Docker's Linux VM has no Metal access. See Native install for the native-MPS workaround.4
For demos on a Mac: pre-build the index on a Linux GPU machine, copy index/lancedb/ to the Mac, configure the codebase via CODEBASE_CONFIG env var. Demonstrated working in our own Mac dogfood1 (1.5 GB transfer for 5 codebases = 85 k chunks; queries ran at full parity with the dev GPU box).
Verifying your install¶
# /health should return ok + non-zero chunks per codebase
curl -s http://localhost:8765/health | python3 -m json.tool
# Run the canonical benchmark
docker exec codenavi python3 tools/benchmark.py --codebase laravel
A clean Mac install at parity with the GPU dev box: laravel scored 86% PASS on the standard benchmark suite, with several other testbed codebases in the same run landing in the 40-70% range (numbers from our own Mac dogfood benchmark1) — those lower scores reflect harder, less-documentation-heavy codebases, not a Mac-specific regression; each matched its GPU-box score within 6 points.
Reporting issues¶
Mac-specific issues found during our own Mac dogfood1, all since fixed:
- tree-sitter-perl wheel was x86_64-only and blocked arm64 builds — fixed via PyPI 1.0.2 wheels2
CODENAVI_HOSToverload silently broke Mac installs — fixed viaCODENAVI_BIND3- Mac M-series indexing is CPU-only via Docker — native MPS code shipped, awaiting validation4
- silent 0-result search returns — a loud WARNING is now logged5
- default install ran without the reranker — the default flipped to ms-marco6
If you hit something else, please open an issue with /health output and docker logs codenavi 2>&1 | tail -50.