This guide explains how to install Frigate 0.17 on Mac with Apple Silicon using Apple Container (native alternative to Docker) with detection via the Neural Engine.
- Mac with Apple Silicon (M1/M2/M3/M4)
- macOS 15+ (Sequoia) with Apple Container CLI
- Python 3.11+
- RTSP-compatible cameras
- MQTT broker (optional)
┌─────────────────────────┐ ┌──────────────────────┐
│ Apple Container │ │ Native macOS │
│ ┌───────────────────┐ │ ZMQ │ ┌────────────────┐ │
│ │ Frigate │◄─┼─────┼─►│ FrigateDetector│ │
│ │ (NVR) │ │:5555│ │ (ONNX) │ │
│ └───────────────────┘ │ │ └────────────────┘ │
│ 192.168.64.x │ │ ▼ │
└─────────────────────────┘ │ Neural Engine │
│ └──────────────────────┘
│ NAT (pfctl)
▼
RTSP Cameras
Why this architecture?
- Apple Container cannot access the Neural Engine (Linux isolation)
- FrigateDetector runs on native macOS to use CoreML
- Communication via ZMQ (tcp://IP:5555)
Download yolov9-t-320.onnx from the FrigateDetector project releases.
Step 1: Create the Dockerfile
mkdir -p yolo && cd yolo
cat > Dockerfile <<'EOF'
FROM python:3.11 AS build
ARG MODEL_SIZE=t
ARG IMG_SIZE=320
RUN apt-get update && apt-get install --no-install-recommends -y libgl1 cmake && rm -rf /var/lib/apt/lists/*
COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/
WORKDIR /yolov9
ADD https://github.com/WongKinYiu/yolov9.git .
RUN uv pip install --system -r requirements.txt
RUN uv pip install --system onnx==1.18.0 onnxruntime onnxsim onnxscript
ADD https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-${MODEL_SIZE}-converted.pt yolov9-${MODEL_SIZE}.pt
RUN sed -i "s/ckpt = torch.load(attempt_download(w), map_location='cpu')/ckpt = torch.load(attempt_download(w), map_location='cpu', weights_only=False)/g" models/experimental.py
RUN python3 export.py --weights ./yolov9-${MODEL_SIZE}.pt --imgsz ${IMG_SIZE} --simplify --include onnx
FROM scratch
ARG MODEL_SIZE=t
ARG IMG_SIZE=320
COPY --from=build /yolov9/yolov9-${MODEL_SIZE}.onnx /yolov9-${MODEL_SIZE}-${IMG_SIZE}.onnx
EOFStep 2: Build the image
This step downloads dependencies and converts the PyTorch model to ONNX.
# Build the image (this may take several minutes)
container build -t yolov9-build .Important: The build must complete successfully before proceeding to the next step.
Step 3: Extract the ONNX model
Once the image is built, extract the ONNX file. Since the image uses FROM scratch, extraction is done via the OCI archive.
# Save the image as a tar archive
container image save yolov9-build -o yolov9-build.tar
# List contents to identify the largest blob (~7 MB)
tar -tvf yolov9-build.tar
# Extract the blobs (adapt the hash based on previous output)
tar -xf yolov9-build.tar blobs/sha256/
# Identify the largest file (the one containing the model)
ls -la blobs/sha256/
# Extract the ONNX model from the compressed blob
gunzip -c blobs/sha256/<HASH_OF_LARGEST_FILE> | tar -xf -Note: The blob hash varies per build. Use
ls -la blobs/sha256/to identify the ~7 MB file.
Step 4: Verification
# Verify the file was extracted successfully
ls -la yolov9-t-320.onnx
# Expected: file of approximately 8 MB
# Clean up temporary files
rm -rf blobs/ yolov9-build.tar oci-layout index.jsonAdvanced build options
To compile a different model size:
# Small model (s) with 640 resolution
container build --build-arg MODEL_SIZE=s --build-arg IMG_SIZE=640 -t yolov9-build .Available sizes: t (tiny), s (small), m (medium), c (compact), e (extended)
FrigateDetector is a macOS application that:
- Loads the ONNX model
- Listens on ZMQ (port 5555)
- Runs inference via CoreML/Neural Engine
~/frigate/
├── FrigateDetector.app/
│ └── Contents/
│ ├── MacOS/FrigateDetector
│ └── Resources/app/
│ ├── detector/zmq_onnx_client.py
│ ├── models/yolo.onnx
│ ├── venv/
│ └── run.sh
mkdir -p ~/frigate/FrigateDetector.app/Contents/Resources/app/models
cp yolov9-t-320.onnx ~/frigate/FrigateDetector.app/Contents/Resources/app/models/yolo.onnxmkdir -p ~/frigate/{config/model_cache,storage}
cp yolov9-t-320.onnx ~/frigate/config/model_cache/yolo.onnxmqtt:
enabled: true
host: <MQTT_BROKER_IP> # Your MQTT broker IP
port: 1883
user: <MQTT_USER> # Optional
password: <MQTT_PASSWORD> # Optional
# ZMQ detector to FrigateDetector on macOS
detectors:
apple_silicon:
type: zmq
endpoint: tcp://<MAC_IP>:5555 # Mac IP (not localhost!)
# YOLO model
model:
model_type: yolo-generic
width: 320
height: 320
input_tensor: nchw
input_dtype: float
path: /config/model_cache/yolo.onnx
# No hardware acceleration in the container
ffmpeg:
hwaccel_args: []
go2rtc:
streams:
camera1:
- rtsp://<CAM_USER>:<CAM_PASSWORD>@<CAM_IP>:554/stream
camera1_sub:
- rtsp://<CAM_USER>:<CAM_PASSWORD>@<CAM_IP>:554/stream_sub
cameras:
camera1:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/camera1
input_args: preset-rtsp-restream
roles:
- record
- path: rtsp://127.0.0.1:8554/camera1_sub
input_args: preset-rtsp-restream
roles:
- detect
detect:
enabled: true
width: 640
height: 480
objects:
track:
- person
- car
- dog
- cat
record:
enabled: true
alerts:
retain:
days: 10
detections:
retain:
days: 10
version: 0.17-0| Parameter | Value | Note |
|---|---|---|
endpoint |
tcp://<MAC_IP>:5555 |
Real Mac IP, not localhost or host.docker.internal |
model |
Root level | Not inside detectors |
objects.track |
COCO classes | Do not include license_plate (not supported by generic YOLO) |
hwaccel_args |
[] |
No hardware acceleration in Linux container |
Apple Container uses an isolated bridge network (192.168.64.x). To access cameras on other networks, you need to configure NAT.
# List interfaces
networksetup -listallhardwareports
# Typically:
# en0 = Ethernet
# en1 = Wi-Ficat > ~/frigate/pf-nat-rules.conf <<'EOF'
# NAT for Apple Container to external networks
# Adapt interfaces according to your configuration
# NAT via Ethernet (en0)
nat on en0 from 192.168.64.0/24 to any -> (en0)
# NAT via Wi-Fi (en1) - if needed
nat on en1 from 192.168.64.0/24 to any -> (en1)
EOFsudo cp ~/frigate/pf-nat-rules.conf /etc/pf.anchors/frigate-nat.conf
sudo pfctl -f /etc/pf.anchors/frigate-nat.conf -ecat > ~/frigate/com.frigate.nat.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.frigate.nat</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/pfctl</string>
<string>-f</string>
<string>/etc/pf.anchors/frigate-nat.conf</string>
<string>-e</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/var/log/frigate-nat.log</string>
</dict>
</plist>
EOF
sudo cp ~/frigate/com.frigate.nat.plist /Library/LaunchDaemons/
sudo launchctl load /Library/LaunchDaemons/com.frigate.nat.plistTo store recordings on a Synology NAS via NFS.
- DSM > Control Panel > Shared Folder
- Select the folder, NFS Permissions tab
- Add a rule for the Mac IP (e.g., 10.0.10.4)
- Squash: No mapping or Map all users to admin
- Privilege: Read/Write
- Check: Allow connections from non-privileged ports
# Create mount point
sudo mkdir -p /Volumes/frigate-nas
# Manual mount (test)
sudo mount -t nfs -o rw,resvport 10.0.10.50:/volume1/pve/data/frigate /Volumes/frigate-nas
# Verify
touch /Volumes/frigate-nas/test.txt && rm /Volumes/frigate-nas/test.txt# Edit fstab (safe method)
sudo vifs
# Add this line:
10.0.10.50:/volume1/pve/data/frigate /Volumes/frigate-nas nfs rw,bg,soft,intr,tcp 0 0Options explained:
bg: mount in background if NAS unavailable at bootsoft: timeout instead of blocking indefinitelyintr: allows interrupting blocked operationstcp: more reliable than UDP
Modify start-frigate.sh to use NAS storage:
container run -d \
--name "$CONTAINER_NAME" \
-m 2048M \
-v ~/frigate/config:/config \
-v /Volumes/frigate-nas:/media/frigate \ # NAS instead of ~/frigate/storage
-p 8971:8971 \
-p 8554:8554 \
-p 8555:8555 \
"$IMAGE"Apple Container does not support --shm-size. Solution: remount /dev/shm after startup.
cat > ~/frigate/start-frigate.sh <<'EOF'
#!/bin/bash
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
set -e
CONTAINER_NAME="frigate"
SHM_SIZE="512M"
IMAGE="ghcr.io/blakeblackshear/frigate:0.17.0-beta1-standard-arm64"
echo "=== Frigate Apple Container Launcher ==="
# Stop existing container
if container list 2>/dev/null | grep -q "$CONTAINER_NAME"; then
echo "Stopping existing container..."
container stop "$CONTAINER_NAME" 2>/dev/null || true
sleep 2
fi
if container list -a 2>/dev/null | grep -q "$CONTAINER_NAME"; then
container rm "$CONTAINER_NAME" 2>/dev/null || true
fi
# Launch container
echo "Starting Frigate..."
container run -d \
--name "$CONTAINER_NAME" \
-m 2048M \
-v ~/frigate/config:/config \
-v ~/frigate/storage:/media/frigate \
-p 8971:8971 \
-p 8554:8554 \
-p 8555:8555 \
"$IMAGE"
# Wait for startup
echo "Waiting for startup..."
sleep 5
# Verify container is running
if ! container list | grep -q "$CONTAINER_NAME"; then
echo "Error: Container failed to start"
exit 1
fi
# Remount /dev/shm with correct size
echo "Configuring SHM to $SHM_SIZE..."
container exec "$CONTAINER_NAME" mount -o remount,size="$SHM_SIZE" /dev/shm
# Verify
SHM_ACTUAL=$(container exec "$CONTAINER_NAME" df -h /dev/shm | tail -1 | awk '{print $2}')
echo "SHM configured to: $SHM_ACTUAL"
echo ""
echo "=== Frigate started ==="
echo "Web interface: http://localhost:8971 (with auth)"
echo "RTSP: rtsp://localhost:8554"
EOF
chmod +x ~/frigate/start-frigate.shNote: Port 8971 with authentication. Port 5000 (no auth) is not exposed.
cat > ~/frigate/com.frigate.detector.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.frigate.detector</string>
<key>WorkingDirectory</key>
<string>$HOME/frigate/FrigateDetector.app/Contents/Resources/app</string>
<key>ProgramArguments</key>
<array>
<string>$HOME/frigate/FrigateDetector.app/Contents/Resources/app/venv/bin/python3</string>
<string>detector/zmq_onnx_client.py</string>
<string>--model</string>
<string>AUTO</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>$HOME/frigate/detector.log</string>
<key>StandardErrorPath</key>
<string>$HOME/frigate/detector.log</string>
</dict>
</plist>
EOF
# Install and enable
cp ~/frigate/com.frigate.detector.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.frigate.detector.plist# Start
launchctl start com.frigate.detector
# Stop
launchctl stop com.frigate.detector
# View logs
tail -f ~/frigate/detector.logApple Container does not support --restart=always like Docker. Solution: a watch script.
cat > ~/frigate/watch-frigate.sh <<'EOF'
#!/bin/bash
# Frigate container watch - Auto-restart if stopped
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
CONTAINER_NAME="frigate"
CHECK_INTERVAL=10
LOG_FILE="$HOME/frigate/watch.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
log "Starting Frigate watch"
while true; do
if ! container list 2>/dev/null | grep -q "$CONTAINER_NAME.*running"; then
log "Container stopped - Restarting..."
"$HOME/frigate/start-frigate.sh" >> "$LOG_FILE" 2>&1
if container list 2>/dev/null | grep -q "$CONTAINER_NAME.*running"; then
log "Container restarted successfully"
else
log "ERROR: Failed to restart"
fi
sleep 30
fi
sleep "$CHECK_INTERVAL"
done
EOF
chmod +x ~/frigate/watch-frigate.shcat > ~/frigate/com.frigate.watch.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.frigate.watch</string>
<key>ProgramArguments</key>
<array>
<string>$HOME/frigate/watch-frigate.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>$HOME/frigate/watch.log</string>
<key>StandardErrorPath</key>
<string>$HOME/frigate/watch.log</string>
</dict>
</plist>
EOF
cp ~/frigate/com.frigate.watch.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.frigate.watch.plist# Stop script
cat > ~/frigate/frigate-stop.sh <<'EOF'
#!/bin/bash
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
echo "=== Stopping Frigate ==="
launchctl stop com.frigate.watch 2>/dev/null
if container list 2>/dev/null | grep -q "frigate.*running"; then
container stop frigate
fi
echo "=== Frigate stopped ==="
EOF
# Start script
cat > ~/frigate/frigate-start.sh <<'EOF'
#!/bin/bash
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
echo "=== Starting Frigate ==="
launchctl start com.frigate.watch 2>/dev/null
sleep 15
if container list 2>/dev/null | grep -q "frigate.*running"; then
echo "=== Frigate started ==="
echo "Web interface: http://localhost:8971"
fi
EOF
chmod +x ~/frigate/frigate-stop.sh ~/frigate/frigate-start.shUsage:
~/frigate/frigate-stop.sh # Full stop (watch + container)
~/frigate/frigate-start.sh # Start (enables watch which starts container)Apple Container requires the system service to be started before containers can be launched.
sudo tee /Library/LaunchDaemons/com.apple.container.system.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.apple.container.system</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/container</string>
<string>system</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
sudo launchctl load /Library/LaunchDaemons/com.apple.container.system.plistcontainer system start # Start the service
container system stop # Stop the service- Container System (auto-starts at boot via LaunchDaemon)
- NAT (auto-starts at boot via LaunchDaemon)
- FrigateDetector (auto-starts at login via LaunchAgent)
- Frigate Watch (auto-starts at login, launches container)
# Check FrigateDetector
pgrep -f zmq_onnx_client && echo "✅ Detector OK"
# Check NAT
sudo pfctl -s nat | grep -q "192.168.64" && echo "✅ NAT OK"
# Check Frigate
container list | grep frigate && echo "✅ Container OK"
# Check inference stats
curl -s http://localhost:8971/api/stats | python3 -c "
import sys, json
data = json.load(sys.stdin)
speed = data['detectors']['apple_silicon']['inference_speed']
print(f'✅ Inference: {speed:.1f}ms')
"{
"detectors": {
"apple_silicon": {
"inference_speed": 11.5
}
}
}Symptom: Video stream unavailable, RTSP connection errors.
# Check NAT
sudo pfctl -s nat
# Reload rules
sudo pfctl -f /etc/pf.anchors/frigate-nat.conf -e
# Test connectivity from container
container exec frigate ping -c 3 <CAM_IP>Symptom: WARNING : Model not ready, returning zero detections
# Verify model exists
ls -la ~/frigate/FrigateDetector.app/Contents/Resources/app/models/yolo.onnx
# Check detector logs
tail -20 ~/frigate/detector.log
# Restart detector then Frigate
launchctl stop com.frigate.detector
launchctl start com.frigate.detector
sleep 3
~/frigate/start-frigate.shSymptom: MQTT connected followed by MQTT disconnected every second.
Probable cause: Two Frigate instances with the same MQTT client_id.
Solution: Stop the other instance or configure a unique client_id:
mqtt:
client_id: frigate_apple_containerSymptom: Very high inference time instead of ~11ms.
# Check provider being used
grep -i "provider" ~/frigate/detector.log
# Should display: CoreMLExecutionProviderIf the provider is not CoreML, check the coremltools installation in the venv.
Symptom: Shared memory errors, dropped frames.
# Check current size
container exec frigate df -h /dev/shm
# If < 512M, remount
container exec frigate mount -o remount,size=512M /dev/shmSymptom: [Errno 13] Permission denied: '/media/frigate/recordings/...'
# Check storage folder permissions
ls -la ~/frigate/storage/
# Fix if needed
chmod -R 755 ~/frigate/storage/| Feature | Docker | Apple Container | Solution |
|---|---|---|---|
--shm-size |
✅ | ❌ | Manual remount after startup |
host.docker.internal |
✅ | ❌ | Use real Mac IP |
| GPU/NPU access | ✅ (with config) | ❌ | External detector via ZMQ |
| Host network mode | ✅ | ❌ | NAT via pfctl |
| Named volumes | ✅ | ❌ | Bind mounts only |
| Metric | M1 | M2 | M3 | M4 |
|---|---|---|---|---|
| Inference (ms) | ~15 | ~13 | ~12 | ~11 |
| Provider | CoreML | CoreML | CoreML | CoreML |
| Neural Engine | ✅ | ✅ | ✅ | ✅ |
Model configuration:
- Model: YOLOv9-t
- Resolution: 320x320
- Format: ONNX
~/frigate/
├── config/
│ ├── config.yml # Frigate configuration
│ └── model_cache/
│ └── yolo.onnx # YOLO model for Frigate
├── storage/ # Video recordings
├── FrigateDetector.app/ # macOS detector application
│ └── Contents/Resources/app/
│ ├── models/yolo.onnx # YOLO model for detector
│ └── venv/ # Python environment
├── frigate-start.sh # Simple start
├── frigate-stop.sh # Full stop
├── start-frigate.sh # Container startup (used by watch)
├── watch-frigate.sh # Auto-restart watch
├── pf-nat-rules.conf # NAT rules
├── com.frigate.nat.plist # NAT LaunchDaemon
├── com.frigate.detector.plist # Detector LaunchAgent
├── com.frigate.watch.plist # Watch LaunchAgent
├── detector.log # Detector logs
└── watch.log # Watch logs
/etc/pf.anchors/frigate-nat.conf # NAT rules (system copy)
/Library/LaunchDaemons/com.apple.container.system.plist # Container System service
/Library/LaunchDaemons/com.frigate.nat.plist # NAT service
~/Library/LaunchAgents/com.frigate.detector.plist # Detector service
~/Library/LaunchAgents/com.frigate.watch.plist # Watch service
- Frigate Documentation
- Frigate GitHub
- Apple Silicon Detector - FrigateDetector for macOS
- Apple Container Documentation
- ZMQ Detector Discussion
This guide is distributed under the MIT License.
I commented on a fork the other day, posting here:
This is excellent! I am failing to figure out how to locate the default admin login/password once I get the container running. Any hints? I am not familiar with this, and poking around with "container logs frigate" seems not to make it apparent for me. Thanks for sharing this!
edit: Resolved with:
auth:
reset_admin_password: true
in the config.yml and running 'container logs -f frigate' in the terminal during startup.