Skip to content

Instantly share code, notes, and snippets.

@harb70
Last active January 7, 2026 21:05
Show Gist options
  • Select an option

  • Save harb70/0ca2fa85b70b242575d8c050a2a66ada to your computer and use it in GitHub Desktop.

Select an option

Save harb70/0ca2fa85b70b242575d8c050a2a66ada to your computer and use it in GitHub Desktop.
Frigate 0.17 in Apple Container with Silicon detector

How-To: Frigate on Apple Container with Apple Silicon NPU

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.

Prerequisites

  • Mac with Apple Silicon (M1/M2/M3/M4)
  • macOS 15+ (Sequoia) with Apple Container CLI
  • Python 3.11+
  • RTSP-compatible cameras
  • MQTT broker (optional)

Architecture

┌─────────────────────────┐     ┌──────────────────────┐
│  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)

Step 1: Prepare the YOLO Model

Option A: Use a pre-compiled model

Download yolov9-t-320.onnx from the FrigateDetector project releases.

Option B: Compile the model with Apple Container

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
EOF

Step 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.json

Advanced 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)


Step 2: Install FrigateDetector

FrigateDetector is a macOS application that:

  • Loads the ONNX model
  • Listens on ZMQ (port 5555)
  • Runs inference via CoreML/Neural Engine

Structure

~/frigate/
├── FrigateDetector.app/
│   └── Contents/
│       ├── MacOS/FrigateDetector
│       └── Resources/app/
│           ├── detector/zmq_onnx_client.py
│           ├── models/yolo.onnx
│           ├── venv/
│           └── run.sh

Copy the model

mkdir -p ~/frigate/FrigateDetector.app/Contents/Resources/app/models
cp yolov9-t-320.onnx ~/frigate/FrigateDetector.app/Contents/Resources/app/models/yolo.onnx

Step 3: Configure Frigate

Create the directory structure

mkdir -p ~/frigate/{config/model_cache,storage}
cp yolov9-t-320.onnx ~/frigate/config/model_cache/yolo.onnx

Minimal configuration (~/frigate/config/config.yml)

mqtt:
  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

Important notes

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

Step 4: Configure NAT

Apple Container uses an isolated bridge network (192.168.64.x). To access cameras on other networks, you need to configure NAT.

Identify your network interfaces

# List interfaces
networksetup -listallhardwareports

# Typically:
# en0 = Ethernet
# en1 = Wi-Fi

Create NAT rules

cat > ~/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)
EOF

Enable NAT

sudo cp ~/frigate/pf-nat-rules.conf /etc/pf.anchors/frigate-nat.conf
sudo pfctl -f /etc/pf.anchors/frigate-nat.conf -e

Make NAT persistent (LaunchDaemon)

cat > ~/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.plist

Step 5: NAS Storage (optional)

To store recordings on a Synology NAS via NFS.

Configure NFS share on Synology

  1. DSM > Control Panel > Shared Folder
  2. Select the folder, NFS Permissions tab
  3. 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

Mount NFS on the Mac

# 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

Make mount persistent (fstab)

# 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 0

Options explained:

  • bg: mount in background if NAS unavailable at boot
  • soft: timeout instead of blocking indefinitely
  • intr: allows interrupting blocked operations
  • tcp: more reliable than UDP

Configure Frigate for NAS

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"

Step 6: Frigate Startup Script

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.sh

Note: Port 8971 with authentication. Port 5000 (no auth) is not exposed.


Step 7: FrigateDetector Auto-start

Create the LaunchAgent

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

Manual control

# Start
launchctl start com.frigate.detector

# Stop
launchctl stop com.frigate.detector

# View logs
tail -f ~/frigate/detector.log

Step 8: Container Auto-restart

Apple Container does not support --restart=always like Docker. Solution: a watch script.

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.sh

Watch LaunchAgent

cat > ~/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

Utility scripts

# 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.sh

Usage:

~/frigate/frigate-stop.sh   # Full stop (watch + container)
~/frigate/frigate-start.sh  # Start (enables watch which starts container)

Step 9: Container System Auto-start

Apple Container requires the system service to be started before containers can be launched.

Create the LaunchDaemon

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.plist

Manual control

container system start   # Start the service
container system stop    # Stop the service

Step 10: Startup and Verification

Startup order

  1. Container System (auto-starts at boot via LaunchDaemon)
  2. NAT (auto-starts at boot via LaunchDaemon)
  3. FrigateDetector (auto-starts at login via LaunchAgent)
  4. Frigate Watch (auto-starts at login, launches container)

Verification

# 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')
"

Expected result

{
  "detectors": {
    "apple_silicon": {
      "inference_speed": 11.5
    }
  }
}

Troubleshooting

Container cannot reach cameras

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>

"Model not ready" in Frigate logs

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.sh

MQTT disconnects in a loop

Symptom: 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_container

Slow inference (>100ms)

Symptom: Very high inference time instead of ~11ms.

# Check provider being used
grep -i "provider" ~/frigate/detector.log
# Should display: CoreMLExecutionProvider

If the provider is not CoreML, check the coremltools installation in the venv.

SHM too small (memory errors)

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/shm

Permission denied on /media/frigate

Symptom: [Errno 13] Permission denied: '/media/frigate/recordings/...'

# Check storage folder permissions
ls -la ~/frigate/storage/

# Fix if needed
chmod -R 755 ~/frigate/storage/

Apple Container vs Docker Limitations

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

Performance

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

File Structure

~/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

Resources


License

This guide is distributed under the MIT License.

@jctull
Copy link

jctull commented Jan 5, 2026

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.

@harb70
Copy link
Author

harb70 commented Jan 6, 2026

You’re welcome 🖖
Don’t forget to delete or comment the lines you added to reste the password before restarting container ( did this several times 😅)

@jctull
Copy link

jctull commented Jan 7, 2026

You’re welcome 🖖 Don’t forget to delete or comment the lines you added to reste the password before restarting container ( did this several times 😅)

Despite my other challenges, I did manage to catch that one before getting caught in a loop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment