Beta. In daily use on a Mac Mini M4 (24GB) against an Immich 2.7.x library. Stable, but back up your Immich database before your first run.
Run Immich's compute natively on Apple Silicon. Thumbnails use the fast M-series CPU, video transcoding uses VideoToolbox hardware encoding, and ML runs on Metal GPU, Neural Engine, and CoreML.
Docker handles the lightweight parts (API server, Postgres, Redis). The accelerator runs Immich's own microservices worker natively on macOS, giving it access to hardware that Docker can't reach.
Docker (lightweight) Native macOS (compute)
+-----------------------+ +-------------------------------+
| immich-server (API) | | Immich Accelerator |
| postgres |<--------->| +- Microservices worker |
| redis | DB+Redis | | +- Sharp (thumbnails) |
| | | | +- ffmpeg (VideoToolbox) |
| WORKERS_INCLUDE=api | | +- ML service |
| ML_URL=host:3003 | | +- CLIP (MLX/Metal) |
+-----------------------+ | +- Faces (Vision/ANE) |
| +- OCR (Vision/ANE) |
+-------------------------------+
The microservices worker is extracted directly from your running Immich Docker image, so it tracks whatever version you run (verified with Immich 2.7.x and 3.0.x). Always the exact same version, no source builds. The only modification is installing the macOS-native Sharp binary for image processing. Video transcoding is intercepted by a lightweight ffmpeg wrapper that remaps software encoders to VideoToolbox hardware encoders.
Nothing inside Docker is modified. We don't patch Immich, rebuild images, or replace containers. All changes are to your docker-compose.yml and can be reverted by removing a few lines.
| What we change | How | Reversible? | Risk |
|---|---|---|---|
| Add env vars to docker-compose | IMMICH_WORKERS_INCLUDE, IMMICH_MACHINE_LEARNING_URL, IMMICH_MEDIA_LOCATION |
Remove the lines | None |
| Expose Postgres/Redis ports | 5432:5432, 6379:6379 in docker-compose |
Remove the port lines | None |
| Native microservices worker | Extracted from Docker image, runs via node |
Stop the accelerator | None |
| Native ML service | Separate Python service | Stop the accelerator | None |
/build symlink (Immich 2.7+) |
/etc/synthetic.d/immich-accelerator (requires sudo once during setup) |
immich-accelerator uninstall removes it; reboot to deactivate |
Low |
Why /build? Immich 2.7+ stores absolute plugin paths like /build/corePlugin/dist/plugin.wasm in its database. Both Docker and native workers need /build to resolve. macOS SIP prevents creating root-level directories, so we use Apple's synthetic link mechanism to map /build → ~/.immich-accelerator/build-data. Setup prompts for sudo once; a reboot may be required to activate.
To fully revert: Stop the accelerator, remove the env vars and port mappings from docker-compose, docker compose up -d. Immich is back to stock.
That's it. Setup installs everything else (Docker, Node.js, ffmpeg, ML dependencies).
brew install epheterson/immich-accelerator/immich-accelerator
brew trust epheterson/immich-accelerator # Homebrew 5.1.15+: lets brew upgrade see future releases
immich-accelerator setup
If no Docker is found, setup offers to install OrbStack. If no Immich is running, setup creates the entire Docker stack for you. Just answer two questions:
~/Pictures), mounted read-only for Immich to import~/.immich-accelerator/data) for thumbnails, transcoded video, and backupsSetup generates the docker-compose, starts Immich, extracts the native worker, and starts everything. Open http://localhost:2283 to create your admin account.
For existing Immich installs, setup detects the running containers and configures the accelerator to work alongside them.
For NAS + Mac setups, see Split deployment below.
IMMICH_MEDIA_LOCATIONThis is the directory Immich uses as its media root. It contains these subdirectories: upload/, thumbs/, encoded-video/, library/, profile/, backups/. Both Docker and the native worker must see this directory at the same absolute path. Setup handles this automatically for same-machine installs.
Every command is prefixed with immich-accelerator (e.g. immich-accelerator setup).
| Command | What it does |
|---|---|
setup |
Auto-detect local Docker, extract server, configure |
setup --url URL |
Set up from a remote Immich instance |
setup --manual |
Create a config template for manual editing |
start |
Run worker + ML once in the foreground (testing only; use the service for auto-restart/update/log-rotation) |
stop |
Stop native services |
status |
Show what's running |
logs [worker\|ml] |
Tail service logs |
update |
Update to match a new Immich version |
watch |
Monitor + auto-restart/update + log rotation (what the service runs) |
dashboard |
Web UI at http://localhost:8420 |
ml-test |
Diagnose the ML service (health + CLIP + OCR round-trip) |
uninstall |
Remove services, data, and launchd config |
Real-time monitoring at http://your-mac:8420:
immich-accelerator dashboard
Shows service health, processing progress with live rates and ETAs, Apple Silicon hardware utilization, and system metrics. Mobile-friendly, check from your phone.
The dashboard and setup use the Immich API for job status and queue control. Create an API key from an admin account in Administration > API Keys with these permissions:
| Permission | Used by | Why |
|---|---|---|
job.read |
Dashboard | Show queue activity (active/waiting counts) |
job.create |
Dashboard | Re-queue button |
asset.read |
Setup | Detect upload library path |
library.read |
Setup | Detect external library paths |
All job.* and library.* endpoints require admin access. If the dashboard shows "API key invalid," make sure the key was created by an admin user.
![]()
The accelerator handles Immich updates automatically:
start: checks the Docker container version, re-extracts if it changedwatch mode: checks every 5 minutes. If Watchtower or a manual docker compose pull updates Immich, the watchdog stops the worker, re-extracts the new server, and restarts. No manual intervention needed.immich-accelerator update if you prefer to control the timingTo update the accelerator itself:
brew upgrade immich-accelerator
If you run it as a service (watch mode, the recommended setup), that's all you need: within ~30s the watcher notices the new version on disk, relaunches itself, and restarts the worker and ML service on the new code. (A detached worker survives a plain restart, so this version-aware reload is what guarantees the new code actually takes effect; brew services restart alone wouldn't reload the worker.)
If you run the worker manually instead of as a service, restart it yourself after upgrading:
immich-accelerator stop && immich-accelerator start
If brew upgrade says there's nothing to do but you know a newer release exists, see Troubleshooting.
In the Immich admin UI (Administration → Jobs), tune the per-queue concurrency for your hardware. Recommended for M4 with 24GB:
| Queue | Concurrency | Why |
|---|---|---|
| Thumbnail Generation | 4 | CPU-bound (Sharp/libvips with NEON SIMD) |
| Smart Search | 2 | GPU-serialized (MLX Metal, no benefit higher) |
| Face Detection | 3 | Neural Engine (Vision framework) |
| OCR | 3 | Neural Engine (Vision framework) |
| Metadata Extraction | 4 | I/O-bound (exiftool) |
| Video Conversion | 1 | Hardware-accelerated via VideoToolbox |
Higher isn't always better. Oversubscribing the CPU causes thrashing and actually reduces throughput.
Run the Immich Docker stack (API, Postgres, Redis) on one host and the native accelerator (worker + ML) on another. The one rule: both machines must see the same files at the same absolute paths via a shared filesystem. Setup detects a path mismatch and refuses to save a broken config.
See docs/split-deployment.md for the full guide: topology, the two ways to align paths (match the Mac in Docker, or a synthetic link on the Mac), fresh-install geodata initialization, and changing IMMICH_MEDIA_LOCATION safely.
The ML service is a managed fork of immich-ml-metal by @sebastianfredette, included as a git submodule. It replaces Immich's Docker ML container with native macOS inference. Upstream changes are reviewed before merging.
| Task | Hardware | Framework |
|---|---|---|
| CLIP embeddings | GPU (Metal) | MLX |
| Face detection | Neural Engine | Apple Vision |
| Face recognition | CPU / CoreML | InsightFace ONNX |
| OCR | Neural Engine | Apple Vision |
Contributions to the ML service are made via upstream PRs.
Run the accelerator as a background service, not with a bare immich-accelerator start. The service runs watch mode, and only watch mode gives you:
KeepAlive) restarts the monitor if it dies; the monitor restarts the worker, ML, and dashboard if they crash.worker.log/ml.log so they can't grow without bound.A plain immich-accelerator start runs once in the foreground with none of the above. Use it only for quick testing.
Homebrew install (recommended):
brew services start epheterson/immich-accelerator/immich-accelerator
This uses the formula's own service definition, survives brew upgrade, and restarts at login. Check it with brew services list. Stop with brew services stop epheterson/immich-accelerator/immich-accelerator.
Don't also install the launchd plist below if you're using
brew services. Running both double-starts the watcher.
Git / non-Homebrew install: immich-accelerator setup offers to install a launchd LaunchAgent (~/Library/LaunchAgents/com.immich.accelerator.plist). If you skipped that prompt, re-run setup and it will offer again.
Either way the service uses watch mode with KeepAlive. Confirm everything is healthy with immich-accelerator status and immich-accelerator ml-test.
The native worker runs Immich's unmodified code. The ffmpeg and image processing toolchain match Docker. The only differences are in the ML service, which uses Apple-native frameworks instead of ONNX Runtime.
| Area | Docker | Native (Accelerator) | Impact |
|---|---|---|---|
| ffmpeg | Jellyfin-ffmpeg | Jellyfin-ffmpeg (same binary, macOS arm64 build) | Identical. Same tonemapx filter, same encoders, same behavior. Downloaded automatically during setup. |
| ffmpeg encoders | Software H.264/HEVC | VideoToolbox hardware H.264/HEVC via wrapper | Hardware-encoded output has slightly different bitstream characteristics. Visually equivalent. A lightweight wrapper remaps Immich's software encoder requests to VideoToolbox hardware equivalents. Immich has no VideoToolbox option, so it logs Transcoding video ... without hardware acceleration even though the encode runs on the GPU via the wrapper. That log is expected and benign. |
| Sharp / libvips | Prebuilt linux-arm64 Sharp | Rebuilt against Homebrew system libvips | Identical image output. System libvips handles corrupt HEIF files more gracefully (matches Docker's error handling). |
| ML: CLIP | ONNX Runtime | MLX on Metal GPU | Same model, different runtime. Embeddings are numerically close but not identical (floating-point differences). Search results are equivalent. |
| ML: Face detection | ONNX Runtime | Apple Vision framework (Neural Engine) | Different model entirely. Detection accuracy is comparable; bounding boxes may differ slightly. |
| ML: Face recognition | ONNX Runtime | ONNX Runtime with CoreML | Same model, |
$ claude mcp add immich-apple-silicon \
-- python -m otcore.mcp_server <graph>