Day-to-day operation once you're set up — see the quick start if you haven't installed yet.
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 |
setup --ml-only |
Set up this Mac as an ML-only network compute node |
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 |
component [name] [on|off] |
Turn the worker, ML, or dashboard on or off (no args lists them) |
ml-test |
Diagnose the ML service (health + CLIP + OCR round-trip) |
selftest |
Run every kind of work (thumbnails, HEIC, metadata, video, backups, ML) through Immich's own code on sample files; runs on its own whenever Immich or the accelerator changes version |
encoding |
Show the hardware transcoding switches, or set one: encoding hardware-video off (see below) |
encoding preset [software|hardware] |
Move every switch to one end at once |
compare <video> |
Encode one of your own files every way this Mac can and open a page with the numbers and a frame from each |
encode-compare <video> |
The same comparison as text, and the VideoToolbox quality closest to what Immich produces on its own |
uninstall |
Remove services, data, and launchd config |
If a command isn't behaving as documented here, see troubleshooting.md.
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:
- Auto-restart: launchd (
KeepAlive) restarts the monitor if it dies; the monitor restarts the worker, ML, and dashboard if they crash. - Auto-update: picks up new Immich versions (re-extracts the worker) and notifies on accelerator updates — see Updates below.
- Log rotation: caps
worker.log/ml.logso 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-acceleratorThis 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 accelerator handles Immich updates automatically:
- On every
start: an all-in-one install checks the version of the container on this Mac and re-extracts if it changed. A split install does not check here; its version check is the watcher's, below. - In
watchmode: checks every 5 minutes. If Watchtower or a manualdocker compose pullupdates Immich, the watchdog stops the worker, re-extracts the new server, and restarts. No manual intervention needed. - Manual:
immich-accelerator updateif you prefer to control the timing
A split install takes the version from the Immich you configured with setup --url, never from a container on the Mac, and so needs an api_key in its config for any of this to run.
To update the accelerator itself:
brew upgrade immich-acceleratorIf you run it as a service (watch mode, the recommended setup above), 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 startIf brew upgrade says there's nothing to do but you know a newer release exists, see Troubleshooting.
Real-time monitoring at http://your-mac:8420:
immich-accelerator dashboardShows 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 dashboard binds 0.0.0.0:8420 (LAN-accessible) by default — see security.md before exposing it beyond a trusted network.
A native menu-bar app shows accelerator health at a glance and covers the daily actions: worker / ML / dashboard status (with a NATIVE or PYTHON engine badge), start / stop / restart, run ml-test inline, open Immich, the dashboard, or logs, and launch-at-login. It reads the accelerator's own state directly (no extra services) and weighs about 90KB.
brew install --cask epheterson/immich-accelerator/immich-accelerator-menubar
open "/Applications/Immich Accelerator.app"Design inspired by Immich-Accelerator-Helper by @pl4za.
The accelerator is three separate processes, and each one can be turned off independently:
| Component | What it does | Turn it off when |
|---|---|---|
worker |
Thumbnails, video transcoding, metadata, RAW/HEIC decode | Another machine already runs the worker and you only want this Mac for ML |
ml |
Smart search (CLIP), face detection, OCR | Another machine already does ML and you want this Mac for thumbnails and VideoToolbox |
dashboard |
The web UI on port 8420 | You use the menu bar app, or you don't want a port open |
immich-accelerator component # list what's on
immich-accelerator component ml off # this Mac does thumbnails and video, not ML
immich-accelerator component worker off # this Mac does ML onlyChanges apply immediately, including to a running service, and they persist as plain keys in ~/.immich-accelerator/config.json. The menu bar app's Settings window has the same three switches, and a component you turn off disappears from the menu rather than showing as a red failure.
Turning ml off hands ML to another machine, so tell it where. The accelerator normally points the worker at its own engine on localhost. With ml off it stops setting that, and whatever you configured in Immich under Administration → Machine Learning Settings applies instead. Immich's default there is immich-machine-learning:3003, a Docker-internal hostname a worker running natively on macOS cannot resolve, so if you never changed it every ML job will fail. Either set that URL in Immich to a reachable engine, or set "ml_url" in config.json to name it here. Toggling ml restarts the worker, because that URL is fixed when the worker starts.
This goes as far as the process boundaries and no further. Video, thumbnails, metadata and RAW decode all happen inside the single Immich microservices worker, so "video but not thumbnails" isn't something the accelerator can offer. That split is Immich's job scheduler: use Administration → Jobs to pause individual queues, and see Performance tuning below for concurrency.
worker off, ml on is common enough to have its own setup flag — see ML-only network node.
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) for the default and SigLIP models |
| 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.
The ONNX models in the CLIP zoo (anything that isn't the default MLX model) let onnxruntime use every core, which is right on a Mac doing nothing else and less obviously right while the worker is transcoding on the same machine. IMMICH_ACCEL_ML_THREADS caps it without touching the worker; unset means "use every core", which stays the default.
The cap is close to free, because the parallelism saturates early. On an M4 with ViT-B-16, capping to 2 measured 179ms against 175ms uncapped, for 8 fewer threads competing with the worker. Try it if search feels like it's making the rest of the machine sluggish.
Put them in the env block of ~/.immich-accelerator/config.json:
{
"env": {
"IMMICH_ACCELERATOR_HEIC_DECODE_CONCURRENCY": "2",
"IMMICH_ACCEL_ML_THREADS": "4"
}
}They are passed to the worker and the ML service, and the accelerator reads its own from there too. Restart with brew services restart immich-accelerator to apply.
A real environment variable still wins, so running the accelerator by hand with one exported does what you would expect. The config block exists because the environment cannot be reached on a Homebrew install, not to outrank it.
Setting them in the shell environment does not work on a Homebrew install, which is why this exists: brew services generates the launch agent, it carries no environment, launchctl setenv does not reach it, and editing the plist is undone the next time the service restarts.
Only IMMICH_ACCEL* names are accepted. Everything else a service needs is worked out at startup, and anything else in that block is ignored with a warning.
Three switches decide how much of transcoding runs on the video hardware rather than on the CPU. Settings shows them under Hardware Transcoding; the CLI sets the same values.
| Switch | Variable | Default | What it does |
|---|---|---|---|
| Decoding | IMMICH_ACCEL_HW_DECODE |
on | Decodes video, thumbnails and previews with VideoToolbox |
| Video Encoding | IMMICH_ACCEL_HW_VIDEO |
on | Encodes H.264 and HEVC with VideoToolbox |
| Audio Encoding | IMMICH_ACCEL_HW_AUDIO |
on for new installs, off for upgrades | Encodes AAC with AudioToolbox (aac_at) |
Set one at a time:
immich-accelerator encoding # show all three
immich-accelerator encoding hardware-video offOr move all three at once. Software turns everything off, which is exactly what Immich's own container does, so video and thumbnails come out byte for byte what Docker produces. Hardware turns everything on:
immich-accelerator encoding preset software
immich-accelerator encoding preset hardwareAny other combination is a valid place to be, and both Settings and the CLI call it Custom.
One exception, in either position: if ffmpeg cannot decode a file at all, the wrapper falls back to QuickLook for that one thumbnail, so it did not come from ffmpeg and Docker on the same file would either produce a different thumbnail or fail outright. Nothing turns that off, because the alternative is no thumbnail. It is reached only when ffmpeg rejects the stream outright, which in practice means some HDR10 phone footage.
Audio is the one switch that is off for anyone upgrading. aac_at is faster and uses about a third less CPU, but the bytes differ from Docker's, so an existing install is never moved onto it without being asked. A new install starts at Hardware.
"Hardware" does not simply mean "faster". Immich asks ffmpeg for preset ultrafast, which is genuinely quick, so on an idle Mac the software encoder often finishes a single file sooner. What VideoToolbox buys is the rest of the machine. Measured on an M4 over 20 seconds of 1080p camera footage: software finished in 1.5 seconds but spent 12.5 seconds of CPU across about eight cores, while VideoToolbox took 2.8 seconds of wall clock and 5 seconds of CPU across about two. On a Mac also running Immich's other jobs and the machine learning engine, that is usually the trade you want.
Whether hardware also wins on quality depends on which software encode you compare it with. Against Immich's default, preset ultrafast, it wins outright, because ultrafast disables most of what x264 is good at. On a 23-second 1080p phone clip, VideoToolbox produced 7.6 MB at SSIM 0.957 where software produced 19.9 MB at 0.952, for about half the CPU. Against x264 at veryfast it no longer does: the same clip came out at 7.3 MB and SSIM 0.958 in software, slightly smaller and slightly better, for three times the CPU.
@RxChi1d measured this properly, 99 encodes across six real clips scored with MS-SSIM and LPIPS against a lossless reference, and found that at equal quality veryfast needs 2.1 to 4.8 times fewer bytes than VideoToolbox, at 7 to 13 times the CPU (#162). The data and scripts are attached there.
So VideoToolbox trades storage for CPU. On a Mac that is also running Immich's other jobs and the machine learning engine, CPU is usually the scarce resource and that is the right trade. If storage matters more to you than CPU, Software with Immich set to veryfast makes smaller files for the same quality. Either way these numbers vary with the footage, which is what the comparison commands below are for.
preset ultrafast is also the assumption both comparison commands make about your software side, because it is what Immich ships. If you have configured Immich for a different x264 preset, tell them so, or the comparison is against something you do not run:
immich-accelerator compare ~/Movies/clip.mov --preset veryfastThat one matters more than it sounds. On the 20 seconds of camera footage measured above, ultrafast produced 66.6 MB at SSIM 0.967 while veryfast produced 33.2 MB at 0.972: half the bytes at higher quality, from the same encoder. Which end of the comparison you are standing on changes the answer.
compare writes a page with a frame from each encode next to the measurements, since a table cannot tell you whether you would notice the difference. encode-compare prints the same measurements as text.
This 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.
Immich derives every storage path from IMMICH_MEDIA_LOCATION, so there is no separate setting for thumbnails (this is true in Docker too). To keep the library on slow storage while thumbnails live on an SSD, symlink the subdirectory:
immich-accelerator stop
mv /path/to/media/thumbs /Volumes/fast-ssd/thumbs
ln -s /Volumes/fast-ssd/thumbs /path/to/media/thumbsThe same works for encoded-video/. Immich follows the symlink, so reads and writes land on the SSD.
If the SSD is not mounted, that symlink points nowhere and every job writing to it would fail. The accelerator checks for this at startup and refuses to start, naming the broken path, instead of letting those jobs fail one by one.