Self-hosted NVR · Build guide

REC · FRIGATE 0.17

Frigate camera server — from a working 4‑camera build

A self-hosted, local-first NVR: it records your RTSP cameras, runs real AI object detection on the edge (no cloud), and hands events to Home Assistant. This is the shape of a running install, trimmed to what you need to stand up your own.

4 cameras Coral edge TPU Intel QuickSync decode Docker one file MQTT → Home Assistant

What you're building

Source
RTSP cameras
Each camera gives two streams — a high‑res main and a low‑res sub.
Engine
Frigate
Coral TPU detects objects; Intel iGPU decodes video. go2rtc restreams for live view.
Output
Recordings · Events · HA
Continuous + event clips on disk; alerts pushed to Home Assistant over MQTT.

Hardware

What it runs on

PartWhat / why
HostAny Linux box with an Intel CPU that has an iGPU (QuickSync) for cheap video decode. A small SFF/mini-PC is plenty.
DetectorGoogle Coral Edge TPU (USB or PCIe) — runs detection at ~near-zero CPU. Optional: skip it and detect on CPU or the iGPU via OpenVINO.
CamerasAny RTSP cameras that expose a main + sub stream (most Dahua / Hikvision / Reolink do).
StorageA disk sized for your retention. ~1 camera at 1080p continuous ≈ a few GB/day; tune the retain days below.

The one pattern that matters

Detect on the sub-stream, record the main

This is the trick that keeps detection cheap and recordings sharp. Point Frigate at both of each camera's streams and give them different jobs:

Per camera

Main stream → record role (full quality, saved to disk).
Sub stream (≈720p) → detect role (small + fast for the TPU).
The main is also restreamed by go2rtc so the live view is crisp without a second camera connection.

Then tell each camera which objects to track — this cuts false alerts hard.

CameraTracks
front_doorperson, cat
front_yardperson, car
drivewayperson, car, cat  ·  + motion mask on a noisy corner
shedperson, car, cat  ·  + motion mask

Deploy · the simple way

docker-compose.yml

The original build runs Frigate in an LXC via Podman — this Compose file is the friendlier equivalent. Drop it in a folder, put your config.yml in ./config, and docker compose up -d.

docker-compose.ymledit the marked lines
services:
  frigate:
    container_name: frigate
    image: ghcr.io/blakeblackshear/frigate:stable
    restart: unless-stopped
    shm_size: "256mb"          # raise for >2 cameras (see Frigate docs)
    devices:
      - /dev/apex_0:/dev/apex_0                 # Coral PCIe — omit if no Coral
      - /dev/dri/renderD128:/dev/dri/renderD128 # Intel iGPU (hwaccel)
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./config:/config
      - ./media:/media                          # recordings live here
      - type: tmpfs
        target: /tmp/cache
        tmpfs: { size: 1000000000 }
    ports:
      - "5000:5000"     # web UI
      - "8554:8554"     # go2rtc RTSP
      - "8555:8555/tcp"
      - "8555:8555/udp"
    environment:
      FRIGATE_RTSP_PASSWORD: "your_camera_password"

Deploy · the brains

config/config.yml

The whole system is this one file. Below is the working shape with credentials and IPs replaced by <placeholders> — set your own and add cameras by copying the front_door block.

config.yml<PASSWORD> · <CAM_IP> = yours
mqtt:
  enabled: false            # true + host: if you use Home Assistant

detectors:
  coral:
    type: edgetpu
    device: pci:0          # USB Coral -> "usb"; no Coral -> use cpu/openvino

ffmpeg:
  hwaccel_args: preset-intel-qsv-h264   # Intel iGPU decode

record:
  enabled: true
  retain: { days: 2, mode: all }   # keep everything 2 days
  alerts:     { retain: { days: 2 } }
  detections: { retain: { days: 10 } }  # keep clips w/ objects 10 days

go2rtc:                       # hi-res restream for the live view
  streams:
    front_door:
      - rtsp://admin:<PASSWORD>@<CAM_IP>:554/cam/realmonitor?channel=1&subtype=0

cameras:
  front_door:
    ffmpeg:
      inputs:
        - path: rtsp://admin:<PASSWORD>@<CAM_IP>:554/cam/realmonitor?channel=1&subtype=0
          roles: [record]         # main stream = recording
        - path: rtsp://admin:<PASSWORD>@<CAM_IP>:554/cam/realmonitor?channel=1&subtype=02
          roles: [detect]         # sub stream  = detection
    detect: { width: 1280, height: 720 }
    objects:
      track: [person, cat]        # per-camera object list

Those RTSP paths are Dahua's subtype=0 (main) / subtype=02 (sub). Other brands differ — check your camera or the Frigate docs for the sub-stream URL.

Bring it up

Setup order

  1. Install Docker + the Compose plugin on the host.
  2. If using a Coral, install its driver on the host first (Frigate's docs cover USB and PCIe — PCIe needs the gasket/apex kernel module).
  3. Make a folder with docker-compose.yml and a config/config.yml (from above).
  4. Start it: docker compose up -d, then open http://host-ip:5000.
  5. Confirm each camera shows a picture and the detector shows a low inference time (Coral ≈ 8 ms).
  6. Tune per-camera objects, add motion masks over noisy areas, and set your retain days to fit the disk.

Worth knowing

Gotchas

  • Coral is optional. No TPU? Set the detector to cpu or openvino (uses the Intel iGPU). Everything else stays the same.
  • Always detect on the sub-stream. Pointing detection at the main stream will bury the CPU/TPU for no benefit.
  • Give it scratch space. The tmpfs cache + shm_size matter; bump shm_size as you add cameras.
  • Home Assistant is optional — flip mqtt.enabled on and point it at your broker to get camera entities + events.
  • go2rtc restream is what makes the live view smooth; keep the main stream listed there.