Frigate 0.17 NVR Setup Guide: NVIDIA GPU, Docker Compose & go2rtc (2026)
Set up Frigate with Reolink cameras and an NVIDIA GPU using Docker, go2rtc, and TensorRT. This guide shows how to reduce CPU usage, stabilize RTSP streams, and get fast, accurate object detection.
Why I Rewrote This Frigate NVR Guide for 2026
I published the original version of this guide in October 2025, and it got you to a working, reasonable Frigate setup. The practical instructions were mostly fine. But after rebuilding my own Frigate stack from scratch in July 2026 (adding a second camera, migrating credentials, and reading the updated Frigate 0.17 docs, and testing a bit what works vs what is said), I discovered that the story I told about why it works was wrong in several places.
The TensorRT "secret sauce" section? Inaccurate. The shared memory formula? Wrong. The Docker Compose snippet shipped two dead Jetson-only leftovers that worked in older (pre v17.x) Frigate versions. The config had a plaintext RTSP password sitting in a world-readable file for months. This is how everyone starts, it's not the end of the world, but I could do (and show) better.
This rewrite corrects all of that. Every claim is verified against my running system (RTX 3060, Frigate 0.17.2, Reolink RLC-822A + Reolink WiFi doorbell) and backed by the Frigate source code. No invented numbers, no assumed behaviour. It also includes the Reolink go2rtc fix so you get a stable & steady stream from your cams!
TL;DR: What You Need to Know
• Use the:stable-tensorrtimage tag (it ships GPU-capable ONNX Runtime).
• Set your detector totype: onnx. Do NOT setdevice: Tensorrt.
• Use go2rtc to restream: one connection to the camera, consumed locally by Frigate.
• Keep credentials in your stack.envfile (mode 600), not inconfig.yml.
• Use{FRIGATE_VAR}syntax in go2rtc streams; never${FRIGATE_VAR}.
• The SHM formula keys off detect resolution, not camera resolution.
Frigate Hardware Requirements & Prerequisites
This guide assumes you have:
- A Linux host with Docker and Docker Compose installed
- An NVIDIA GPU (preferably) with working drivers (I'm running an RTX 3060 12 GB, driver
590.48.01) - The NVIDIA Container Toolkit configured (
nvidia-smiworks inside containers) - One or more Reolink cameras on your local network
- Guide will work for any camera, but is written with Reolink in mind as that's what I use and know.
My actual rig:
- GPU: NVIDIA RTX 3060 (12 GB VRAM, Ampere, compute capability 8.6)
- Camera 1: Reolink RLC-822A (4K/8MP PoE) at
10.20.0.2 - Camera 2: Reolink WiFi Doorbell (5MP, 4:3) at
10.20.0.3 - Frigate: 0.17.2 (
ghcr.io/blakeblackshear/frigate:stable-tensorrt) - Storage: Recordings on a ZFS raidz2 pool (
/brain), config on SSD
Frigate High Level Architecture
This flowchart gives a high level overview of the architecture and flow of data processing in Frigate:
flowchart TD
%% =============================
%% CAMERA SOURCE
subgraph CameraSource ["📷 Reolink Source"]
CAM["🎥 IP Camera"]
end
%% =============================
%% STABILIZATION LAYER (The "Secret Sauce")
subgraph Stabilizer ["🛡️ Stabilization Layer"]
RTC["go2rtc (Internal)"]
CAM -->|"Raw RTSP"| RTC
end
%% =============================
%% PROCESSING PIPELINE
subgraph Pipeline ["⚙️ Processing Pipeline"]
RTC -->|"1. Sub-stream"| DEC["🧩 NVDEC (GPU Decode)"]
DEC -->|"2. Frames"| MOTION["🌀 Motion Detection"]
MOTION -->|"3. Crop"| TRT["⚡ TensorRT (NVIDIA GPU)"]
end
%% =============================
%% NVR CORE & OUTPUT
subgraph Core ["🧠 Frigate NVR Core"]
RTC -->|"4. Main-stream"| NVR["Recordings Engine"]
TRT -->|"5. Metadata"| NVR
NVR -->|"6. Store"| DISK["💾 ZFS / SSD Storage"]
NVR -->|"7. Alert"| HA["🔔 Home Assistant"]
end
%% =============================
%% STYLING (Dark Mode Optimized)
style CAM fill:#2d3436,stroke:#0984e3,stroke-width:2px,color:#dfe6e9
style RTC fill:#2d3436,stroke:#00cec9,stroke-width:3px,color:#fff
style DEC fill:#2d3436,stroke:#6c5ce7,stroke-width:2px,color:#dfe6e9
style TRT fill:#2d3436,stroke:#fdcb6e,stroke-width:3px,color:#fff
style NVR fill:#2d3436,stroke:#e17055,stroke-width:2px,color:#dfe6e9
style DISK fill:#2d3436,stroke:#b2bec3,stroke-width:2px,color:#dfe6e9
style HA fill:#2d3436,stroke:#d63031,stroke-width:2px,color:#dfe6e9
%% SUBGRAPH STYLING
style CameraSource fill:transparent,stroke:#636e72,stroke-dasharray: 5 5
style Stabilizer fill:transparent,stroke:#636e72,stroke-dasharray: 5 5
style Pipeline fill:transparent,stroke:#636e72,stroke-dasharray: 5 5
style Core fill:transparent,stroke:#636e72,stroke-dasharray: 5 5
Part 1: Docker Compose Setup
Here is the complete, production-ready compose.yaml service definition for Frigate on a desktop NVIDIA GPU. I've stripped the two dead Jetson-only leftovers that my original guide included.
services:
frigate:
container_name: frigate
image: ghcr.io/blakeblackshear/frigate:stable-tensorrt
restart: unless-stopped
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: "256mb"
ports:
- "5000:5000" # Web UI
- "554:554" # RTSP feeds
- "8554:8554" # go2rtc RTSP
- "8555:8555/tcp" # WebRTC (TCP)
- "8555:8555/udp" # WebRTC (UDP)
environment:
- TZ=America/Toronto
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,video,utility
# Camera credentials (sourced from .env mode 600)
- FRIGATE_RTSP_USER=${FRIGATE_RTSP_USER}
- FRIGATE_RTSP_PASSWORD=${FRIGATE_RTSP_PASSWORD}
volumes:
- /path/to/your/frigate/recordings:/media/frigate
- /path/to/your/frigate/config:/config
- /etc/localtime:/etc/localtime:ro
- type: tmpfs
target: /transcode
tmpfs:
size: 1000000000 # 1GB
Optionally, you can specify more advanced network settings as well:
networks:
vlanx_cameranet:
ipv4_address: 10.0.0.x (whatever subnet you made for cams)What I Removed (And Why)
My original guide told you to add two things that do nothing on desktop NVIDIA GPUs in Frigate v0.17.x:
YOLO_MODELS=yolov7-320,yolov7-640- This environment variable drives the.trtengine build in Frigate's Jetson-only TensorRT detector path. On a desktop GPU with theonnxdetector, it builds nothing and only adds dead weight to container startup. Mymodel_cache/tensorrt/directory was completely empty after months of running this. Remove it./path/to/model_cache/tensorrt:/trt-modelsvolume mount - Nothing is ever written to/trt-modelson desktop GPUs. If you ever deliberately enable TensorRT EP, its cache path is/config/model_cache/tensorrt/ort/trt-engines, which your existing/configmount already covers.
The .env File (Mode 600)
Create a .env file alongside your compose.yaml and lock its permissions immediately:
# Frigate camera credentials
# Consumed by: compose → container env → Frigate's {FRIGATE_*} substitution
FRIGATE_RTSP_USER=your_camera_username
FRIGATE_RTSP_PASSWORD=your_camera_password
# If you have a second camera with different credentials:
# FRIGATE_DOORBELL_USER=admin
# FRIGATE_DOORBELL_PASSWORD=different_password
chmod 600 .envWhy this matters: My original config had the RTSP password hardcoded in config.yml, which was mode 644 (world-readable). Any local user on the box could read the camera password. Worse - if you misconfigured your firewall or reverse proxy, anyone on the internet could simply drop into your Frigate instance and open your config from the webgui! Frigate supports environment variable substitution for any variable prefixed with FRIGATE_ - use it. Keep the credential in your stack .env file, pass it through compose, and reference it in the Frigate config with {FRIGATE_RTSP_PASSWORD} (note: no dollar sign; more on that critical detail below).
Part 2: Build the YOLOv9 ONNX Model
Frigate's ONNX detector needs a model file. The project provides an official export Dockerfile that converts a YOLOv9 PyTorch model to ONNX format. This runs entirely on CPU (it is a model format conversion step, not a GPU compilation step).
# Clone the Frigate repo
git clone https://github.com/blakeblackshear/frigate.git
cd frigate
# Build the exporter (uses the official Dockerfile)
docker build \
--build-arg MODEL_SIZE=s \
--build-arg IMG_SIZE=640 \
-f docker/main/Dockerfile.onnx \
-t frigate-onnx-export .
# Copy the exported model out
docker run --rm -v /path/to/your/frigate/config/model_cache/onnx:/out \
frigate-onnx-export \
cp /models/yolov9_640-s.onnx /out/
Note: This is the recommended way to run things by the Frigate project.
Choosing Model Size: Why 320 Might Be Better Than 640
In my original guide, I wrote that I chose IMG_SIZE=640 because it matched my camera's native resolution. That reasoning was wrong: model input resolution has no relationship to camera resolution. Frigate crops a region of motion from the full frame and scales it to whatever input size the model expects.
The Frigate docs actually argue against 640 for most setups:
"Frigate is optimized for320x320. Frigate is specifically designed to compensate for the smaller model by cropping a region of motion from the full frame and zooming into it before running detection, so a320x320model is actually better at small and distant objects, not worse."
I run 640 because I have the GPU headroom (RTX 3060, single camera, ~15 ms inference), but start with 320 if you're unsure. It will cut inference time by roughly two-thirds and can actually detect distant objects better. Only step up to 640 if you have the GPU budget and are testing on your own footage.
Part 3: Complete Frigate Configuration
This is the exact configuration pattern running on my system, with real values that you should replace where appropriate. Every setting was verified against the Frigate 0.17 source and my live API output.
📄 View Full YAML Configuration (Click to Expand)
mqtt:
enabled: false
# Global NVIDIA hardware decode for every stream (H.264 and H.265)
ffmpeg:
hwaccel_args: preset-nvidia
# ---------------------------------------------------------------
# Detector: ONNX on CUDA (the correct modern NVIDIA path)
# ---------------------------------------------------------------
detectors:
onnx_0:
type: onnx
model:
model_type: yolo-generic
path: /config/model_cache/onnx/yolov9_640-s.onnx
input_tensor: nchw
input_pixel_format: rgb
input_dtype: float
width: 640
height: 640
# Global defaults
detect:
enabled: true
fps: 5
# Recording (0.16+ schema)
record:
enabled: true
sync_recordings: true
continuous:
days: 8
motion:
days: 8
alerts:
retain:
days: 30
detections:
retain:
days: 30
snapshots:
enabled: true
clean_copy: true
timestamp: false
bounding_box: true
crop: false
height: 2160
retain:
default: 16
objects:
person: 16
birdseye:
mode: motion
objects:
track:
- person
- bicycle
- car
- motorcycle
- bird
- cat
- dog
- backpack
# ---------------------------------------------------------------
# go2rtc: restream so Frigate opens ONE connection to the camera
# ---------------------------------------------------------------
go2rtc:
streams:
porch_main:
- rtsp://{FRIGATE_RTSP_USER}:{FRIGATE_RTSP_PASSWORD}@YOUR_CAMERA_IP:554/h265Preview_01_main
porch_sub:
- rtsp://{FRIGATE_RTSP_USER}:{FRIGATE_RTSP_PASSWORD}@YOUR_CAMERA_IP:554/h264Preview_01_sub
cameras:
porch:
enabled: true
ffmpeg:
hwaccel_args: preset-nvidia
inputs:
- path: rtsp://127.0.0.1:8554/porch_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/porch_main
input_args: preset-rtsp-restream
roles:
- record
output_args:
record: preset-record-generic-audio-copy
detect:
width: 640 # Your sub-stream's actual resolution
height: 360
fps: 5
snapshots:
required_zones: [Front_yard]
motion:
threshold: 35
contour_area: 20
improve_contrast: true
zones:
Front_yard:
coordinates: 0.471,0.995,0.803,0.394,0.912,0.446,0.894,0.529,0.896,0.664,0.84,0.989
loitering_time: 0
review:
alerts:
required_zones: Front_yard
version: 0.17-0
🕵️ Understanding the Frigate config.yml (Line-by-Line)
If you are curious about what that massive block of YAML is actually doing under the hood, here is the breakdown of the "Secret Sauce".
1. The MQTT Block
mqtt:
host: 192.168.X.X # Your MQTT BrokerWhat it does: This is Frigate's nervous system. Frigate doesn't send notifications to your phone natively; it fires raw JSON "Events" to your MQTT Broker. Home Assistant listens to that broker and triggers your automations.
2. The go2rtc Block (The Stabilizer)
go2rtc:
streams:
camera_main:
- ffmpeg:rtsp://{USER}:{PASS}@10.20.0.2:554/h265Preview_01_main
camera_sub:
- ffmpeg:rtsp://{USER}:{PASS}@10.20.0.2:554/h264Preview_01_subWhat it does: As illustrated in the Architecture Diagram, this is the internal proxy. Instead of Frigate opening multiple direct connections to your IP camera, the camera streams once to go2rtc. Frigate then pulls everything it needs internally from 127.0.0.1.
3. Hardware Acceleration & AI Inference
detectors:
tensorrt:
type: tensorrt
device: 0
cameras:
porch:
ffmpeg:
hwaccel_args: preset-nvidiaWhat it does: This is what keeps your server's CPU from melting down.
- preset-nvidia offloads the heavy video decoding to your NVIDIA GPU's NVDEC chip.
- tensorrt tells Frigate to send those decoded images to the GPU's CUDA cores to run the AI Object Detection model.
4. The Camera Roles (record vs detect)
inputs:
- path: rtsp://127.0.0.1:8554/camera_sub
roles:
- detect
- path: rtsp://127.0.0.1:8554/camera_main
roles:
- recordWhat it does:
- detect: Uses the low-resolution, low-framerate "Sub-stream". This is what the AI analyzes.
- record: Uses the high-resolution 4K/2K "Main-stream". The AI never looks at this. It is pulled strictly to be written to your hard drive so you have crisp footage.
5. Masks, Zones, and Object Filters
motion:
mask: 0.109,0.263,0.344...
zones:
Front_porch:
coordinates: 0.997,0.683,0.919...
review:
alerts:
required_zones:
- Front_porchWhat it does:
- Masks: Tells Frigate to completely ignore moving pixels in certain areas (like trees).
- Zones & Alerts: Allows you to draw polygons over areas of interest. By gating your alerts with required_zones, Home Assistant will only push a notification if a person physically steps onto your porch.
Part 4: Why go2rtc Restreaming Matters
Without go2rtc, Frigate opens two direct RTSP connections to your camera: one for the detect sub-stream and one for the record main stream. Many cameras, Reolink included, handle this poorly. On my system, I was seeing 9 watchdog-triggered ffmpeg restarts per week with "Connection timed out" and "CSeq expected" errors.
With go2rtc restreaming, a single process opens one connection to the camera and serves it locally over 127.0.0.1:8554. Frigate then consumes both streams from localhost without any camera-side connection juggling.
After enabling go2rtc on my system: zero ffmpeg restarts.
Stream Path Discovery for Reolink
Reolink cameras use predictable RTSP paths. Probe yours with ffprobe to confirm:
# Main stream (record: 4K H.265 on most Reolink PoE cameras)
ffprobe -v quiet -print_format json -show_streams \
rtsp://user:pass@CAMERA_IP:554/h265Preview_01_main
# Sub stream (detect: typically 640x360 or 640x480, H.264)
ffprobe -v quiet -print_format json -show_streams \
rtsp://user:pass@CAMERA_IP:554/h264Preview_01_sub
Key things to note from your probe results:
- Sub-stream resolution: This is what you set in
detect.width/detect.height. My RLC-822A sub is 640×360 (16:9). My doorbell sub is 640×480 (4:3). Don't assume; probe it. - Audio codec: If it's already AAC (Reolink usually is), use
preset-record-generic-audio-copyinstead ofaudio-aacto avoid a pointless 24/7 re-encode.
Part 5: How Frigate Uses TensorRT for Inference (Correcting My Original Guide)
This is the big correction. My original guide claimed that Frigate "automatically converts the ONNX model to a TensorRT engine on first boot" with "kernel auto-tuning" that "analyzes your specific GPU architecture." None of that is true for this setup. I thought this was happening due to the lengthy build process and usually what occurs when building apps / compiling things from source.
What Actually Happens
When you run type: onnx on the :stable-tensorrt image (which is the correct config), Frigate loads your ONNX model file through ONNX Runtime's CUDAExecutionProvider. No TensorRT engine is ever built. No kernel auto-tuning occurs. The model stays in ONNX format and runs on your GPU via CUDA. This is the magic basically which makes it so efficient.
Evidence from my own system:
/config/model_cache/tensorrt/was completely empty after months of running. No engine was ever built.- Startup logs show
ONNX: loading → loadedin ~1.5 seconds. A real TensorRT engine build takes minutes. - The YOLOv9 export script prints
device=cpu. It's a CPU-side PyTorch→ONNX format conversion. No GPU is involved at all. - Reading the Frigate source (
frigate/util/model.py):TensorrtExecutionProvideris only appended whendevice == "Tensorrt"is explicitly set.
Something better than TensorRT for this use case: CUDA Graphs. In
detection_runners.py, when providers[0] == "CUDAExecutionProvider" and the model type is eligible (which yolo-generic is), Frigate wraps the ONNX session in a CudaGraphRunner with enable_cuda_graph: True. CUDA Graphs eliminate per-inference kernel-launch overhead by capturing the entire inference pipeline as a replayable graph on the GPU. My measured inference time with this path: 14.3 to 16.5 ms on an RTX 3060 with a single camera.
⚠️ Why You Should NOT Set device: Tensorrt
You can force the literal TensorRT execution provider by adding device: Tensorrt to your detector config. On desktop NVIDIA hardware, this is very likely slower, for two reasons I verified in the source code:
- It disables CUDA Graphs. Inside the container,
ort.get_available_providers()returns['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']. TensorRT sorts first. When you setdevice: Tensorrt, it becomesproviders[0]and Frigate skips the CUDA-Graph fast path entirely. - It runs FP32, not FP16. The
trt_fp16_enableflag inutil/model.pyis gated behind arequires_fp16argument that the ONNX detector never passes. So you'd lose the single biggest performance win that TensorRT offers on Ampere tensor cores.
Frigate's own source comment (from it's source code) puts it plainly: "TensorrtExecutionProvider uses too much memory without options to control it, so it is not enabled by default."
The classic type: tensorrt detector with .trt engine files is Jetson-only since Frigate 0.16. On desktop NVIDIA, type: onnx on the -tensorrt image is the intended path. Confirmed by maintainer @NickM-27 in Frigate discussion #22114.
Part 6: Shared Memory (SHM) and the Correct Formula
My original guide said "for each 4K cam, just set at least 128mb" and gave a simplified formula. That formula was wrong in two ways: it missed the frame buffer multiplier and the log overhead, and it implied you should use your camera's resolution. The formula uses your detect resolution.
The Correct Formula (From Frigate's Own ShmCalculator)
(detect_width × detect_height × 1.5 × 20 + 270480) ÷ 1048576 + ~40 MB (logs)Frigate stores its log output in /dev/shm as well, which accounts for the additional ~40 MB baseline.
Real Numbers From My Two-Camera System
| Camera | Detect Resolution | SHM Required |
|---|---|---|
| Porch (RLC-822A) | 640 × 360 | 6.85 MB |
| Doorbell (WiFi) | 640 × 480 | 9.05 MB |
| Logs | — | ~40 MB |
| Total | ~56 MB |
I'm running comfortably on a 256mb allocation with ~200 MB of headroom. Notice: both cameras record in 4K/5MP, but the SHM formula only cares about the detect sub-stream (640×360 and 640×480). This is why "128 MB per 4K camera" over-provisions: a 4K camera detecting on a 640×480 sub-stream needs about 9 MB, not 128.
Part 7: Recording Schema (Frigate 0.16+)
If you're following older guides (including my original), you'll see this recording schema:
# ❌ Pre-0.16 schema (OUTDATED)
record:
retain:
days: 8
mode: all
The current 0.16+ schema separates continuous, motion, alert, and detection retention:
# ✅ Current 0.16+ schema
record:
enabled: true
sync_recordings: true
continuous:
days: 8 # 8 days of 24/7 footage
motion:
days: 8 # 8 days of motion-triggered segments
alerts:
retain:
days: 30 # 30 days of alert clips
detections:
retain:
days: 30 # 30 days of detection clips
Both schemas may currently work due to backwards compatibility, but use the current one because it gives you granular control over what type of footage you keep and for how long.
Part 8: Security (Credentials, File Permissions, and the ${VAR} Trap)
This section comes from a real incident on my own system. It cost me a camera outage and taught me something that isn't documented anywhere I could find.
Never Hardcode RTSP Credentials in config.yml
My original config had rtsp://frigate:[email protected]:554/... directly in config.yml. That file was mode 644 (world-readable). Any local user on the box could read the camera password.
The fix: Frigate substitutes any environment variable prefixed with FRIGATE_. Put your credentials in your stack's .env file (mode 600), pass them through Docker Compose, and reference them in config.yml with curly braces:
# In config.yml — correct
go2rtc:
streams:
camera_main:
- rtsp://{FRIGATE_RTSP_USER}:{FRIGATE_RTSP_PASSWORD}@CAMERA_IP:554/...
Also lock down your config directory:
chmod 600 /path/to/frigate/config/config.yml
chmod 600 /path/to/frigate/config/.jwt_secret
chmod 600 /path/to/frigate/config/frigate.db*
⚠️ The ${VAR} vs {VAR} Trap (A Real War Story)
This mistake cost me a camera lockout. Using ${FRIGATE_RTSP_PASSWORD} in a go2rtc stream URL looks right and fails in a way that looks like a password problem, not a syntax problem.
What happens under the hood: At container start, Frigate's /usr/local/go2rtc/create_config.py runs Python's str.format(**FRIGATE_ENV_VARS) over each go2rtc stream. With a $ present, .format() consumes the braces and leaves the dollar sign, producing rtsp://$frigate:$MyPassword@.... go2rtc then logs "wrong user/pass" and retries every ~10 seconds.
The consequence: Before I spotted it, go2rtc generated 44 failed RTSP login attempts in about two minutes. My Reolink camera locked the account. It then rejected the correct credential with HTTP 401, from any client, long after I fixed the config. The camera was reachable over HTTP the whole time, which made it look like a configuration fault rather than a lockout.
Why pre-flight validation didn't catch it: I validated the config using FrigateConfig.parse_yaml, which passed. But that's the pydantic layer, which does not substitute go2rtc.streams (the EnvString substitution applies only to an allowlist: camera ffmpeg paths, ONVIF, MQTT, proxy, genai). Validation passed while proving nothing about the field I actually changed.
The Only Check That Proves It's Right
After starting the container, inspect the generated go2rtc config:
docker exec frigate sh -c 'grep rtsp:// /dev/shm/go2rtc.yaml'Every URL must show real credentials and contain no $. If you see a dollar sign, stop the container immediately.
If You Get Locked Out of a Reolink Camera
- Stop the container immediately: every retry extends the lockout window (
docker stop frigate). - Recovery is camera-side: Wait for the lockout to expire (varies by model), or power-cycle the camera to clear it immediately.
- Do not keep testing until you've verified the go2rtc syntax is correct using the
grepcommand above.
Part 9: Multi-Camera Considerations
When I added my Reolink WiFi doorbell as a second camera, I hit several things worth knowing:
Probe Your Streams Instead of Assuming
My doorbell's streams are completely different from my PoE camera:
| RLC-822A (PoE) | WiFi Doorbell | |
|---|---|---|
| Main stream | H.265, 3840×2160, 25 fps | H.264, 2560×1920, 20 fps |
| Sub stream | H.264, 640×360, 10 fps | H.264, 640×480, 10 fps |
| Ext stream | Not tested | Does not exist (404) |
| Aspect ratio | 16:9 | 4:3 |
| RTSP user | frigate | admin |
Key takeaways:
- The sub-stream aspect ratio differs (16:9 vs 4:3), so don't copy
detect.width/detect.heightfrom one camera to another. - If your cameras use different credentials, you need separate
FRIGATE_variable pairs in your.env. - Both H.264 and H.265 main streams work fine with
preset-nvidiabecause NVDEC handles both.
Watch for Global vs. Per-Camera Settings
In my original config, I had snapshots.required_zones: [Front_porch] at global scope. That's fine for one camera, but it silently breaks when a second camera is added: every snapshot on the new camera gets suppressed because it has no zone by that name. Move camera-specific zone requirements under cameras.<name>.snapshots.
Tune Motion Before Trusting Detection
After adding my doorbell, it was burning ~15.8 detection fps while producing zero events (compared to ~2.3 detection fps on the tuned porch camera). That's the signature of untuned motion where the camera's field of view triggers constant motion (street traffic, foliage, watermark overlay), causing the detector to run on every frame and find nothing.
Always tune motion in the Frigate UI (Settings → Motion Tuner) before relying on a new camera's detection output. Mask irrelevant areas, then verify detection_fps drops to a sensible baseline.
Part 10: Audio Recording (Copy, Don't Re-Encode)
Reolink cameras output AAC audio natively. My original guide used preset-record-generic-audio-aac, which is -c:v copy -c:a aac (a 24/7 AAC to AAC re-encode that produces an identical result for no benefit).
Use preset-record-generic-audio-copy instead (-c copy). Your CPU will thank you.
When would you use audio-aac? Only if your camera outputs a non-AAC audio codec (like G.711 or PCM) that you want converted to AAC for broader playback compatibility. If it's already AAC, just copy it.
Frigate TensorRT Performance & Measured Results
Real numbers from my RTX 3060, measured via Frigate's own API after the rebuild:
| Metric | Before Rebuild | After Rebuild |
|---|---|---|
| Inference speed | 21.24 ms | 14.27 ms |
| Camera/process FPS | 10.1 / 10.1 | 5.0 / 5.0 (intended) |
| Skipped FPS | 0.0 | 0.0 |
| FFmpeg restarts (1 week) | 9 | 0 |
| go2rtc | Not in use | Both streams: 1 producer + 1 consumer |
The inference improvement came from reducing detect.fps from 10 to 5. My actual detection rate was only 4.6 fps anyway, so dropping the target to 5 eliminated unnecessary frame processing overhead. The ffmpeg restart fix came from go2rtc restreaming.
How to Verify Your Setup
After starting Frigate, run through this checklist:
# 1. Check go2rtc credential substitution (MOST IMPORTANT)
docker exec frigate sh -c 'grep rtsp:// /dev/shm/go2rtc.yaml'
# Must show real credentials, NO dollar signs
# 2. Check detector stats
docker exec frigate curl -s localhost:5000/api/stats \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('Detectors:', d['detectors']); print('Cameras:', {k: {kk: vv for kk, vv in v.items() if kk in ('camera_fps','detection_fps','skipped_fps')} for k, v in d.get('cameras', d.get('camera_metrics', {})).items()})"
# 3. Check go2rtc stream health
docker exec frigate curl -s localhost:1984/api/streams
# Each stream should show producers/consumers with bytes flowing
# 4. Check GPU utilisation
nvidia-smi
If all goes well, you'll see this in your console after running nvidia-smi
Sun Aug 30 13:15:48 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 590.48.01 Driver Version: 590.48.01 CUDA Version: 13.1 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce RTX 3060 Off | 00000000:2D:00.0 Off | N/A |
| 40% 43C P2 44W / 170W | 554MiB / 12288MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| 0 N/A N/A 1379205 G labwc 2MiB |
| 0 N/A N/A 1380388 C frigate.detector:onnx_0 274MiB |
| 0 N/A N/A 1700638 C /usr/lib/ffmpeg/7.0/bin/ffmpeg 127MiB |
| 0 N/A N/A 1907527 C /usr/lib/ffmpeg/7.0/bin/ffmpeg 133MiB |
+-----------------------------------------------------------------------------------------+Notice it says frigate.detector:onnx_0 in there eh? NOICE! Final testing...
Browse to http://IPyouPicked:5000 and you should see frigate webGUI open and your first camera in the Birds' Eye view already!
Obviously seeing the name of your GPU at the bottom with a little green dot means things are likely functioning smoothly as well. Once you click system metrics (bottom left corner, cog wheel!), you should now have a wonderfully informant view like this:

Check the tabs at the top - General, Storage & Cameras. All should load and all should display some stats & info. Between a live view of your cameras and these pages, you've successfully setup Frigate, accelerated by your GPU with a hardware based neural net trained AI!
🎬 Exporting Your Frigate Footage (Evidence Retrieval)
You’ve set up the NVR, the YOLOV neural net trained model is detecting objects, and now you have caught something. Whether it’s a friendly fox visiting your porch or a porch pirate stealing a package, you need to get that video OFF the server and onto your computer. Maybe it's just because you recorded something funny and want to share it even?
Frigate handles this differently than traditional NVRs. It splits "Events" (Object detections) from "Recordings" (24/7 Timeline).
Method 1: The "Quick Grab" (Downloading an Event)
If Frigate decides something is an alert, and tags it as such, it will already be ready for review and download instantly.
- Navigate to "Review": Click the Review (or Events) tab in the sidebar. This feeds you a list of all recent detections.
- Select the Event: Click on the thumbnail of the event you want. The video player will pop up.
- Download: Look for the Export Icon (usually a downward arrow ⬇️) in the player controls at the top of the screen.
- Result: Select the exact timeframe of the event as shown in your clip!
Method 2: The "Custom Export" (Timeline & Time-Lapse)
Sometimes misses the start of an event, or you want to export a 1-hour chunk of time to show the police everything that happened between 2:00 PM and 3:00 PM.
- Navigate to "Explore" (or "Export"): Click Export in the sidebar menu.
- Create New Export: Click the + or New Export button.
- Configure the Clip:
- Camera: Select the camera (e.g.,
front_porch). - Date/Time: precise Start and End times.
- Playback Factor: Choose Realtime for normal video, or Time-lapse to compress 24 hours into minutes (great for construction projects or weather watching!).
- Camera: Select the camera (e.g.,
- Start Export: Click Create.
- Note: This does not download instantly. Your NVIDIA GPU (or CPU) is now working in the background to stitch those files together.
📥 Retrieving the File
Frigate processes exports in the background so you don't have to keep your browser open.
- Go to the Exports tab in the sidebar.
- You will see your request in the list.
- Processing: The server is still stitching.
- Ready: The file is done.
- Click the Download icon next to the finished file to save the
.mp4to your device.
Core Lab Pro Tip: If you are exporting massive files (like 8 hours of 4K footage), it is often faster to map the /media/frigate/exports folder in Docker to a shared folder on your NAS/PC. That way, you can grab the files directly over your network via SMB/NFS instead of downloading them through the web browser!What's Next?
Now that Frigate is running, you need to connect it to Home Assistant to get those rich notifications on your phone.👇
Leave a comment below on your Frigate setup or how this guide worked out for you! This part of our ongoing Cybersecurity series for Homelabs.

Frequently Asked Questions
Do I need a Coral TPU or an NVIDIA GPU?
Either works. The Google Coral is power-efficient and great if you're running on a low-power system. An NVIDIA GPU is faster for multi-camera setups and handles hardware video decoding (NVDEC) alongside detection. I chose NVIDIA because I already had an RTX 3060 in the box. Both are well-supported in Frigate.
Does Frigate use TensorRT on my desktop NVIDIA GPU?
Not by default, and probably not the way you think. The :stable-tensorrt image ships GPU-capable ONNX Runtime where your model runs on CUDA with CUDA Graphs, not through a compiled TensorRT engine. The classic type: tensorrt detector with .trt files is Jetson-only since Frigate 0.16. See "Part 5" above for the full explanation and source-code evidence.
Why does the YOLOv9 build take so long?
The Docker build installs PyTorch and its dependencies, then runs a CPU-side model export from PyTorch to ONNX format. It is not performing any GPU compilation or TensorRT optimization. It is slow because the dependency install is large and the export itself is compute-intensive on the CPU.
Should I use the sub-stream or main stream for detection?
Always use the sub-stream for detection. Frigate's detection pipeline crops and scales motion regions down to the model's input size (e.g. 640×640 or 320×320) regardless of the source resolution. Feeding it a 4K main stream would waste decode resources without improving detection quality. Use the main stream for recording only.
How do I calculate the right SHM size?
Use the correct formula: (detect_width × detect_height × 1.5 × 20 + 270480) ÷ 1048576 per camera, plus ~40 MB for logs. Key point: it's the detect resolution, not the camera's native resolution. A 4K camera detecting on a 640×480 sub-stream needs about 9 MB, not 128 MB.
Why am I getting "wrong user/pass" errors in go2rtc?
Check whether your go2rtc stream URLs use ${FRIGATE_VAR} instead of {FRIGATE_VAR}. The dollar sign breaks Python's str.format() substitution and produces garbled credentials. Stop Frigate immediately to avoid camera lockout, fix the syntax, then verify with docker exec frigate sh -c 'grep rtsp:// /dev/shm/go2rtc.yaml'.
Written by

