Skip to content

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 scp the 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.


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
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 alpine under container 0.12.3): the kata kernel Apple ships has no virtio_gpu module loaded, no /sys/class/drm directory, no PCI device for GPU, no /dev/dri /dev/metal* /dev/accel* nodes. Even if Apple later enables virtio_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_HOST overload silently broke Mac installs — fixed via CODENAVI_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.


  1. Vendor change reference CN-71. ↩↩↩

  2. Vendor change reference CN-83. ↩

  3. Vendor change reference CN-84. ↩↩

  4. Vendor change reference CN-86. ↩↩↩

  5. Vendor change reference CN-87. ↩

  6. Vendor change reference CN-88. ↩↩

  7. Vendor change reference CN-90. ↩↩↩