Tutorial

Build a Frigate NVR on an Intel N100 with OpenVINO Detection

Turn an N100/N305 mini PC into a 24/7 NVR: run Frigate 0.18 in Docker, decode camera streams on the iGPU with VAAPI and run YOLOv9 object detection through OpenVINO on the same GPU.

Intermediate ⏱ 60 minutes 7 steps

What you need

  • Intel N100 / N150 / N305 mini PC with the iGPU enabled (Alder Lake-N has no NPU, so detection runs on the GPU)
  • 16 GB RAM for 3 or more cameras (8 GB is workable for one or two)
  • 128 GB+ NVMe, or a separate disk for recordings
  • Ubuntu 24.04 LTS or Debian 13 with a 6.x kernel
  • An IP camera that exposes an RTSP main stream and a lower-resolution substream
  • Wired Ethernet between the mini PC and the cameras

1Confirm the iGPU is visible to the kernel

Frigate passes the render node into the container, so the host must expose /dev/dri/renderD128. If only card0 exists, the iGPU is disabled in the BIOS (look for an iGPU or IGD option, and a DVMT / IGD memory setting of 512 MB or more on boards that have one) or the i915 driver is not loaded. vainfo then tells you which codecs the media engine can decode.

Shell
ls -l /dev/dri
sudo apt install -y vainfo
vainfo --display drm --device /dev/dri/renderD128 | head -20
NoteOn minimal Debian installs vainfo may report no driver; install the intel-media-va-driver package. Frigate bundles its own userspace drivers inside the container, so the host only needs a working kernel driver.

2Install Docker Engine and the Compose plugin

Frigate ships as a container image, so Docker Engine plus the Compose v2 plugin is all the host needs. Docker's convenience script sets up the official apt repository, which keeps later upgrades simple.

Shell
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
exec su - $USER
docker compose version
NoteLog out and back in (or run the exec line) before using docker without sudo. Add your user to the video and render groups so the container's device access matches the host: sudo usermod -aG video,render $USER.

3Create the stack and pass the GPU through

Frigate needs three mounts: /config for configuration and the model, /media/frigate for recordings, and a tmpfs for the recording segment cache so the SSD is not hammered by short-lived files. The only device to pass in for an N100 is /dev/dri/renderD128; privileged mode is not required.

Shell
sudo mkdir -p /opt/frigate/config /opt/frigate/storage
cd /opt/frigate
/opt/frigate/docker-compose.yml
services:
  frigate:
    container_name: frigate
    restart: unless-stopped
    stop_grace_period: 30s
    image: ghcr.io/blakeblackshear/frigate:stable
    shm_size: "512mb"
    devices:
      - /dev/dri/renderD128:/dev/dri/renderD128
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /opt/frigate/config:/config
      - /opt/frigate/storage:/media/frigate
      - type: tmpfs
        target: /tmp/cache
        tmpfs:
          size: 1000000000
    ports:
      - "8971:8971"
      - "8554:8554"
      - "8555:8555/tcp"
      - "8555:8555/udp"
    environment:
      TZ: "Etc/UTC"
Noteshm_size is shared memory for decoded frames: about 210 MB covers two 720p detect streams, and raising it is the fix for "Bus error" crashes. Port 5000 is the unauthenticated UI/API and should stay unpublished.

4Export the YOLOv9-t model to ONNX

Frigate 0.18 asks you to supply an object detection model. The documented route is to export YOLOv9 at 320x320 from the official repository using Docker; the tiny variant is the best fit for a 24 EU iGPU. Run it in an empty working directory, because the build writes the ONNX file into the current folder.

Shell
mkdir -p ~/yolo-export && cd ~/yolo-export
docker build . --build-arg MODEL_SIZE=t --build-arg IMG_SIZE=320 --output . -f- <<'EOF'
FROM python:3.11 AS build
RUN apt-get update && apt-get install --no-install-recommends -y cmake libgl1 && rm -rf /var/lib/apt/lists/*
COPY --from=ghcr.io/astral-sh/uv:0.10.4 /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 onnx-simplifier==0.4.* onnxscript
ARG MODEL_SIZE
ARG IMG_SIZE
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
ARG IMG_SIZE
COPY --from=build /yolov9/yolov9-${MODEL_SIZE}.onnx /yolov9-${MODEL_SIZE}-${IMG_SIZE}.onnx
EOF
ls -lh yolov9-t-320.onnx
sudo mkdir -p /opt/frigate/config/model_cache
sudo cp yolov9-t-320.onnx /opt/frigate/config/model_cache/yolo.onnx
NoteThe build takes a few minutes and pulls PyTorch inside a throwaway image. The COCO label map already ships in the Frigate image at /labelmap/coco-80.txt, so only the ONNX file needs to live in /config.

5Write config.yml: detector, decoding and cameras

Three blocks do the heavy lifting. detectors points OpenVINO at device GPU. model describes the 320x320 YOLOv9 input so the detector knows the tensor shape. ffmpeg.hwaccel_args uses the preset-vaapi preset, which is the recommended choice for 8th to 12th-gen Intel graphics and handles both H.264 and H.265 automatically. Detect runs on the camera's substream through go2rtc, while recording pulls the main stream directly from the camera, which is what Frigate expects for the record role.

/opt/frigate/config/config.yml
mqtt:
  enabled: false

detectors:
  ov:
    type: openvino
    device: GPU

model:
  model_type: yolo-generic
  width: 320
  height: 320
  input_tensor: nchw
  input_dtype: float
  path: /config/model_cache/yolo.onnx
  labelmap_path: /labelmap/coco-80.txt

ffmpeg:
  hwaccel_args: preset-vaapi

go2rtc:
  streams:
    driveway:
      - rtsp://viewer:[email protected]:554/Streaming/Channels/101
      - "ffmpeg:driveway#audio=aac"
    driveway_sub:
      - rtsp://viewer:[email protected]:554/Streaming/Channels/102

cameras:
  driveway:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/driveway_sub
          input_args: preset-rtsp-restream
          roles:
            - detect
        - path: rtsp://viewer:[email protected]:554/Streaming/Channels/101
          input_args: preset-rtsp-generic
          roles:
            - record
    detect:
      width: 1280
      height: 720
    objects:
      track:
        - person
        - car
        - dog
    snapshots:
      enabled: true
NoteThe URLs above follow the Dahua/Amcrest channel pattern (101 main, 102 sub). Replace them with your camera's own RTSP paths; the Frigate Add Camera Wizard can probe ONVIF and write the same block for you.

6Start Frigate and read the detector numbers

On first start Frigate creates its database and asks you to set an admin password in the web UI at port 8971. The logs state whether hardware acceleration was detected and whether the OpenVINO detector loaded; the System page then reports inference speed per detector. If inference time is longer than your camera frame interval, use fewer cameras on the same detector or move recording to a second detector definition.

Shell
cd /opt/frigate
docker compose up -d
docker compose logs -f frigate | grep -i -E "openvino|vaapi|detector|error"
docker compose exec frigate ls -l /dev/dri
NoteThere is no CPU fallback for hardware decoding. If the log complains that the VAAPI device cannot be opened, the render node was not passed through or the container user is missing the render group.

7Set retention and close the front door

Frigate 0.18 splits recording retention into continuous, motion, alert and detection windows; keeping only motion video for a few days and alert clips for a month is the usual home setup. Then reduce the attack surface: publish only 8971, keep 5000 internal, and reach the UI remotely through Tailscale or a reverse proxy instead of exposing it to the internet.

Recording block for config.yml
record:
  enabled: true
  motion:
    days: 3
  alerts:
    retain:
      days: 30
      mode: motion
  detections:
    retain:
      days: 30
      mode: motion
Apply the change
cd /opt/frigate
docker compose restart frigate
docker compose logs --tail 50 frigate
NoteEstimating disk use: five 1080p cameras recording motion only typically fit in 200 to 400 GB per week, so plan the storage pool before you extend the retention window.