> ## Content Index
> Fetch the complete content index at: https://corelab.tech/llms.txt
> Use this file to discover other available public pages before exploring further.

# Frigate 0.17 NVR Setup Guide: NVIDIA GPU, Docker Compose & go2rtc (2026)
- URL: https://corelab.tech/setupfrigate/
- Published: 2025-10-07T18:01:00.000Z
- Updated: 2026-10-29T03:45:05.000Z
- Description: 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.
- Author: Core Lab Joe
- Tags: Frigate, Docker, NVIDIA, #2025, #table-of-content

## 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-tensorrt` image tag (it ships GPU-capable ONNX Runtime).  
> • Set your detector to `type: onnx`. Do **NOT** set `device: Tensorrt`.  
> • Use go2rtc to restream: one connection to the camera, consumed locally by Frigate.  
> • Keep credentials in your stack `.env` file (mode 600), not in `config.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](https://corelab.tech/homelab-hardware-guide/) with Docker and [Docker Compose](https://corelab.tech/setupcompose/) installed
- An NVIDIA GPU (preferably) with working drivers (I'm running an **RTX 3060 12 GB**, driver `590.48.01`)
- The [NVIDIA Container Toolkit](https://corelab.tech/nvidiatoolkit/) configured (`nvidia-smi` works 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.

```YAML
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](https://corelab.tech/setupcompose/#%F0%9F%8E%93-advanced-networking-breaking-the-walls) as well:

```YAML
    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:

1. `**YOLO_MODELS=yolov7-320,yolov7-640**` \- This environment variable drives the `.trt` engine build in Frigate's **Jetson-only** TensorRT detector path. On a desktop GPU with the `onnx` detector, it builds nothing and only adds dead weight to container startup. My `model_cache/tensorrt/` directory was **completely empty** after months of running this. Remove it.
2. **`/path/to/model_cache/tensorrt:/trt-models` volume mount** \- Nothing is ever written to `/trt-models` on desktop GPUs. If you ever deliberately enable TensorRT EP, its cache path is `/config/model_cache/tensorrt/ort/trt-engines`, which your existing `/config` mount 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 .env
```

**Why 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](https://corelab.tech/homelab-security-audit-openvas-guide/) 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).

```BASH
# 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 for `320x320`. 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 a `320x320` model 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) 

```yaml

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

```yaml
mqtt:
  host: 192.168.X.X # Your MQTT Broker
```

**What 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)

```yaml
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_sub
```

**What 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

```yaml
detectors:
  tensorrt:
    type: tensorrt
    device: 0
    
cameras:
  porch:
    ffmpeg:
      hwaccel_args: preset-nvidia
```

**What 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)

```yaml
      inputs:
        - path: rtsp://127.0.0.1:8554/camera_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/camera_main
          roles:
            - record
```

**What 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

```yaml
    motion:
      mask: 0.109,0.263,0.344... 
    zones:
      Front_porch:
        coordinates: 0.997,0.683,0.919...
    review:
      alerts:
        required_zones:
          - Front_porch
```

**What 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:

```BASH
# 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-copy` instead of `audio-aac` to 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:**

1. `/config/model_cache/tensorrt/` was **completely empty** after months of running. No engine was ever built.
2. Startup logs show `ONNX: loading → loaded` in \~1.5 seconds. A real TensorRT engine build takes minutes.
3. The YOLOv9 export script prints `device=cpu`. It's a CPU-side PyTorch→ONNX format conversion. No GPU is involved at all.
4. Reading the Frigate source (`frigate/util/model.py`): `TensorrtExecutionProvider` is only appended when `device == "Tensorrt"` is explicitly set.

📡

****So What's Actually Delivering the Speed?**  
  
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:

1. **It disables CUDA Graphs.** Inside the container, `ort.get_available_providers()` returns `['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']`. TensorRT sorts *first*. When you set `device: Tensorrt`, it becomes `providers[0]` and Frigate **skips the CUDA-Graph fast path** entirely.
2. **It runs FP32, not FP16.** The `trt_fp16_enable` flag in `util/model.py` is gated behind a `requires_fp16` argument 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](https://github.com/blakeblackshear/frigate/discussions/22114?ref=corelab.tech).

---

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

```BASH
(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:

```bash
# ❌ Pre-0.16 schema (OUTDATED)
record:
  retain:
    days: 8
    mode: all

```

The current 0.16+ schema separates continuous, motion, alert, and detection retention:

```yaml
# ✅ 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.

👉

Time based alerting is only available if you integrate Home Assistant and Frigate. Home Assistant can use blueprints to control when alerts are triggered, more on that below! Credit to u/[lneepoch2](https://www.reddit.com/user/lneepoch2/?ref=corelab.tech) on helping me find this though - [https://github.com/blakeblackshear/frigate/issues/13038](https://github.com/blakeblackshear/frigate/issues/13038?ref=corelab.tech)

---

## 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:MyPassword@10.20.0.2: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:

```yaml
# In config.yml — correct
go2rtc:
  streams:
    camera_main:
      - rtsp://{FRIGATE_RTSP_USER}:{FRIGATE_RTSP_PASSWORD}@CAMERA_IP:554/...

```

Also lock down your config directory:

```bash
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:

```bash
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

1. **Stop the container immediately:** every retry extends the lockout window (`docker stop frigate`).
2. **Recovery is camera-side:** Wait for the lockout to expire (varies by model), or power-cycle the camera to clear it immediately.
3. **Do not keep testing** until you've verified the go2rtc syntax is correct using the `grep` command 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.height` from 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-nvidia` because 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:

```bash
# 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`

```BASH
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:

![Frigate system metrics dashboard. Beautifully low detection times showing at roughly ~12ms.](https://corelab.tech/content/images/2026/08/New-Frigate-Statistics-Dashboardwebp-1.webp)

Frigate system metrics dashboard. Beautifully low detection times!

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!

📡

****Final note**: As you can see in my snip above, my average detection inference speed is about 12ms. This is extremely fast! 12 ***thousands** of a second... This is the advantage of setting up Frigate properly!

---

## **🎬 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.

1. **Navigate to "Review":** Click the **Review** (or **Events**) tab in the sidebar. This feeds you a list of all recent detections.
2. **Select the Event:** Click on the thumbnail of the event you want. The video player will pop up.
3. **Download:** Look for the E**xport 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.

1. **Navigate to "Explore" (or "Export"):** Click **Export** in the sidebar menu.
2. **Create New Export:** Click the **+** or **New Export** button.
3. **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!).
4. **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.

1. Go to the **Exports** tab in the sidebar.
2. You will see your request in the list.
  - *Processing:* The server is still stitching.
  - *Ready:* The file is done.
3. Click the **Download** icon next to the finished file to save the `.mp4` to 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**](https://corelab.tech/ultimate-home-assistant-guide/) to get those rich notifications on your phone.👇

[Home Automation with Docker & Home Assistant 2025Since roughly 2012, I’ve been wrestling with home automation – chasing the promise of a truly connected home. Early attempts involved frustrating configurations and unreliable systems. I was even trying to stream media on wireless “G” if that gives you any indication of when I began hoping I could automate![](https://corelab.tech/content/images/icon/Glowing_CPU-94.png)Core LabCore Lab Joe![](https://corelab.tech/content/images/thumbnail/photo-1514803400321-3ca29fc47334-3)](https://corelab.tech/ultimate-home-assistant-guide/)

**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](https://corelab.tech/cybersecroadmap/).

[A Practical Cybersecurity Roadmap for HomelabsLearn to protect your data, services and privacy with actionable steps and clear tutorials. So, you’ve built an incredible homelab. You’re spinning up services in Docker, managing your media, and maybe even self-hosting your own website. You are the master of your own data. But with every new service you![](https://corelab.tech/content/images/icon/Glowing_CPU-27.png)Core LabCore Lab Joe![](https://corelab.tech/content/images/thumbnail/ShieldsUp_gud.png)](https://corelab.tech/cybersecroadmap/)

## Sign up for Core Lab

Forge Your Digital Fortress: Self-Hosting, Homelabs & VPS Mastery. Unlock the power of your own digital space. Build resiliency and take control of your data!

Subscribe 

Email sent! Check your inbox to complete your signup. 

No spam. Unsubscribe anytime.

---

## 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'`.