Deployment guide

Frigate NVR

Overview

Frigate is an open source network video recorder built around real-time object detection. Instead of triggering on raw pixel change the way a traditional motion recorder does, Frigate runs each camera's stream through an object detection model. It tells you that a person, a car or an animal was seen, where it was, and for how long.

This guide covers the deployment and configuration of Frigate NVR on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images.

That distinction is the whole point of the product. Motion detection fires on headlights, rain, moving shadows and swaying branches. Object detection does not, so the alerts you get are the ones worth looking at, and the recordings you keep are the ones worth keeping.

Everything happens on your own VM. Frames are never sent to a cloud service, the detection model runs locally, and the recordings stay on your disk.

The SecureAnyCloud image ships the free and open source, MIT-licensed Frigate as the official upstream container image, pinned by digest and tied to the exact upstream commit it was built from. No camera address and no camera credential is ever baked into the image, so you connect your own cameras.

A unique gate credential and a unique Frigate admin password are generated for each VM on first boot. The gate in front of Frigate does not start until first boot has proved that an anonymous request is answered with 401. The image is backed by 24/7 SecureAnyCloud support.

Frigate(TM), Frigate NVR(TM) and the Frigate logo are trademarks of Frigate, Inc. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by or sponsored by Frigate, Inc. It ships the free and open source MIT-licensed software, unmodified. The names are used here solely to identify the software this image contains.

What you get:

  • Frigate NVR 0.17.2, the official upstream image, pinned by digest sha256:d4351369…96967010 and never by a moving tag.
  • Verified provenance. The image stamps its own build commit into its version string, and this appliance asserts that it reads 0.17.2-3d4dd3a, matching upstream commit 3d4dd3ac4b00e7257bd3412608a783001d7d77ed.
  • The CPU object detector with Google's Apache-2.0 ssdlite_mobiledet_coco model. This image has no Coral TPU, no GPU and no TensorRT.
  • An nginx access gate on port 80, installed as a host package so unattended security upgrades keep the internet-facing component patched.
  • Frigate's own authentication left enabled as a genuine second layer, with a per-VM admin password.
  • Embedded SQLite storage, with no database server of any kind on the instance.
  • A shipped dependency license inventory at /usr/share/frigate/dependency-licences.txt covering all 959 Python and frontend dependencies.
  • The verbatim configuration of every bundled ffmpeg build at /usr/share/frigate/ffmpeg-configuration.txt.

Prerequisites

  • An Azure subscription with permission to create VMs.
  • One or more IP cameras that expose an RTSP stream, reachable from the VM. You connect these yourself after launch, because nothing is preconfigured.
  • A Standard_B2ms VM (2 vCPU, 8 GiB) or larger. Read Step 10: sizing and the realistic camera load, before you commit to a size, because CPU object detection is genuinely compute-hungry.
  • Inbound TCP 80 open only to the networks your administrators connect from. This is a camera system, so treat access to it the way you would treat access to the cameras themselves.
  • An SSH key pair for administrative access.
1

Deploy from the Azure Portal

  • In the Azure Portal, go to Create a resource and search the Marketplace for Frigate NVR on Ubuntu 24.04 LTS by SecureAnyCloud.
  • Select the offer and choose Create.
  • On the Basics tab, pick your subscription, resource group and region, name the VM, and choose size Standard_B2ms.
  • Set the authentication type to SSH public key and supply your key. The admin username is azureuser.
  • On the Networking tab, allow inbound SSH (22) and HTTP (80), and restrict the source to your own address range.
  • On the Disks tab, consider increasing the OS disk if you plan to keep more than a couple of days of recordings. See Step 11: recording storage and retention.
  • Review and create the VM.
2

Deploy from the Azure CLI

az vm image terms accept --urn sac1647283583153:frigate-ubuntu-24-04:default:latest
az vm create \
  --resource-group my-resource-group \
  --name frigate-nvr \
  --image sac1647283583153:frigate-ubuntu-24-04:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard \
  --os-disk-size-gb 128
# Then open the two ports you need, scoped to your own network rather than the
# internet. These run on YOUR workstation, not on the appliance.
az vm open-port --resource-group my-resource-group --name frigate-nvr --port 22 --priority 1001
az vm open-port --resource-group my-resource-group --name frigate-nvr --port 80 --priority 1002
3

Connect to your VM

ssh azureuser@<vm-ip>
4

Confirm the services are running

Three units make up the appliance: docker runs the pinned Frigate container, frigate is the container's systemd wrapper, and nginx is the access gate that fronts it.

sudo systemctl is-active docker frigate nginx

Expected output:

active active active

First boot does real work. It generates this VM's credentials, proves the gate rejects anonymous callers, starts Frigate, and sets Frigate's own admin password. Confirm that it finished:

sudo systemctl is-active frigate-firstboot.service test -f /var/lib/sac/data/frigate-firstboot.done && echo "first boot complete"

Expected output:

active first boot complete
5

Read this VM's credentials

Every credential on this machine was generated for this machine at first boot. Nothing is shared with any other SecureAnyCloud VM, and nothing is baked into the image. Show the credentials file:

sudo cat /root/frigate-credentials.txt

The file holds two separate credentials, and you need both:

FRIGATE_URL=http://<vm-ip>/
# 1. The sac gate (the browser will prompt for this first) GATE_USERNAME=frigate GATE_PASSWORD=<GATE_PASSWORD>
# 2. Frigate's own sign-in (presented after the gate) FRIGATE_USERNAME=admin FRIGATE_PASSWORD=<FRIGATE_PASSWORD>

Store both in your password manager, and then treat the file as a recovery copy.

6

Open the interface

Browse to http://<vm-ip>/.

Your browser prompts for the gate credential first. Enter GATE_USERNAME and GATE_PASSWORD.

Behind the gate, Frigate shows its own sign-in page. Enter FRIGATE_USERNAME and FRIGATE_PASSWORD.

After you sign in, the live dashboard opens. On a brand new VM it looks like this, because no cameras are configured yet. That is the state the image ships in, and it is deliberate.

7

Understand the access model

This is the most important section in the guide. An unprotected NVR streams your cameras to anyone who finds it, so understand exactly what protects this one.

Frigate listens on two ports, and they are not equivalent.

  • Port 5000 is Frigate's internal port. Upstream documents it as internal, and its own source states that requests arriving there are treated as admin with no authentication. On a stock install, this port answers /api/config. Once you add cameras, that response contains your camera RTSP URLs, including their passwords.
  • Port 8971 is Frigate's authenticated port.

On this appliance both are bound to 127.0.0.1 only. Neither is reachable from the network. Confirm it yourself:

sudo ss -lntH | awk '{print $4}' | grep -E ':(80|5000|8971)$' | sort

Expected output (only port 80 is on a routable address):

0.0.0.0:80 127.0.0.1:5000 127.0.0.1:8971 [::]:80

The only way in is nginx on port 80, and nginx is default deny at server scope. Every route inherits the gate: the interface, the entire REST API, /api/config, the MJPEG and HLS stream endpoints, the websocket that drives live view, and anything a future Frigate version adds.

Exactly one route is open. It is nginx's own static health literal, which never reaches Frigate and discloses nothing.

Check it for yourself:

for p in / /api/config /ws /vod/ /api/stats; do printf '%-14s -> %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1$p")" done printf '%-14s -> %s\n' "/nginx-health" "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/nginx-health)"

Expected output:

/ -> 401 /api/config -> 401 /ws -> 401 /vod/ -> 401 /api/stats -> 401 /nginx-health -> 200

Two real layers

Passing the gate is not the same as being signed in to Frigate. With the gate credential but no Frigate session, the API still refuses the request:

curl -s -o /dev/null -w 'with gate credential only: %{http_code}\n' \
  -u '<GATE_USERNAME>:<GATE_PASSWORD>' http://127.0.0.1/api/config

Expected output:

with gate credential only: 401

Sign in to Frigate as well and the same call succeeds:

# Wait for Frigate to be answering before asking it anything. for i in $(seq 1 45); do code=$(curl -s -o /dev/null -w '%{http_code}' -u '<GATE_USERNAME>:<GATE_PASSWORD>' http://127.0.0.1/api/version) case "$code" in 401|200) break ;; esac sleep 2 done
JAR=$(mktemp)
curl -s -o /dev/null -c "$JAR" -u '<GATE_USERNAME>:<GATE_PASSWORD>' \
  -X POST -H 'Content-Type: application/json' \
  -d '{"user":"<FRIGATE_USERNAME>","password":"<FRIGATE_PASSWORD>"}' \
  http://127.0.0.1/api/login
curl -s -o /dev/null -w 'with both credentials: %{http_code}\n' \
  -b "$JAR" -u '<GATE_USERNAME>:<GATE_PASSWORD>' http://127.0.0.1/api/config rm -f "$JAR"

Expected output:

with both credentials: 200

The gate cannot start unconfigured

nginx.service carries two ConditionPathExists guards plus an ExecStartPre preflight check. It refuses to start in either of these cases:

  • This VM's gate credential is missing, empty, or stored as anything other than a password hash.
  • The gate map is still in the deny-all form the image ships with.

The marker that permits nginx to start is created by first boot only after it has proved four things. It runs the real per-VM gate configuration against a throwaway loopback-only listener, and checks that:

  • An anonymous request gets exactly 401.
  • A one-character change to the password is refused.
  • A wrong username is refused.
  • A tampered gate cookie is refused.

In other words, if provisioning ever goes wrong, this appliance serves nothing rather than serving your cameras to the internet.

8

Add your first camera

Nothing about your cameras is preconfigured, so this is where you take over. For each camera, you need an RTSP URL, usually of the form rtsp://username:password@camera-ip:554/some/path. Your camera's manual or its web interface gives the exact path.

The simplest route is the interface. From the dashboard, choose Add Camera, or go to Settings > Cameras > Management.

You can also edit the configuration file directly. It lives at /var/lib/frigate/config/config.yml on the VM, and you can also edit it in the interface under Config.

A minimal first camera looks like this. Note the two streams: a small one for detection and the full-quality one for recording. This split is what makes CPU detection viable, and it matters more on this appliance than on any other setup.

cameras: front_door: ffmpeg: inputs:
# Low resolution substream -> object detection. Keep this SMALL. - path: rtsp://USER:PASSWORD@192.168.1.50:554/substream roles: [detect]
# Full quality main stream -> recording, copied without re-encoding. - path: rtsp://USER:PASSWORD@192.168.1.50:554/mainstream roles: [record] detect: width: 640 height: 480 fps: 5 objects: track: - person - car

After editing, choose Save & Restart in the configuration editor, or restart the service:

sudo systemctl restart frigate
# Frigate takes 20-40 seconds to load its model and reopen its ports. Wait for
# it rather than assuming — a request sent too early gets 502 from the gate. for i in $(seq 1 45); do code=$(curl -s -o /dev/null -w '%{http_code}' -u '<GATE_USERNAME>:<GATE_PASSWORD>' http://127.0.0.1/api/version)
# Warm-up goes 502 (gate cannot reach Frigate) -> 500 -> 401 (Frigate is up and
# asking for its OWN sign-in). Only 401 or 200 means it is genuinely ready. case "$code" in 401|200) break ;; esac sleep 2 done echo "Frigate is back (gate probe returned $code)"

Keep camera passwords out of the configuration file where you can. Frigate supports environment substitution. Put the secret in an environment file readable only by root, and reference it as {FRIGATE_CAMERA_PASSWORD} in the URL.

9

Confirm detection is running

The CPU detector does the actual work. Confirm that it is running and see how fast it is on your hardware:

JAR=$(mktemp)
curl -s -o /dev/null -c "$JAR" -u '<GATE_USERNAME>:<GATE_PASSWORD>' \
  -X POST -H 'Content-Type: application/json' \
  -d '{"user":"<FRIGATE_USERNAME>","password":"<FRIGATE_PASSWORD>"}' \
  http://127.0.0.1/api/login
STATS=$(curl -s -b "$JAR" -u '<GATE_USERNAME>:<GATE_PASSWORD>' http://127.0.0.1/api/stats)
echo "$STATS" | jq -r '.detectors | to_entries[] | "detector \(.key) inference \(.value.inference_speed) ms"'
echo "$STATS" | jq -r '"cameras configured: \(.cameras | length)"' rm -f "$JAR"

On a fresh Standard_B2s build VM with no cameras this reports roughly:

detector cpu1 inference 10.0 ms cameras configured: 0

The same numbers are visible in the interface under System > General, along with per-process CPU and memory.

If you see Safe Mode in the interface, Frigate rejected your configuration file and fell back to a minimal one. The banner names the offending line. Fix that line and restart.

10

Sizing, and the realistic camera load

This appliance ships CPU-only detection. There is no Coral TPU, no GPU and no TensorRT in this image. That is a deliberate constraint, and it bounds how many cameras a given VM can carry.

Upstream is direct about this, and so is this guide. Every time Frigate starts here, it logs:

frigate.detectors WARNING : CPU detectors are not recommended and should only be used for testing or for trial purposes.

That warning is upstream's, it is accurate, and this image does not hide it. CPU detection genuinely works and is useful at small scale, but it is not what you should build a twenty-camera installation on.

This appliance is a good fit if you are evaluating Frigate, running one or two cameras at home or on a small site, or proving out zone and object configuration before investing in hardware. If you need many cameras or high frame rates, you need a hardware accelerator, which an image for general-purpose Azure VM sizes cannot provide.

Frigate is efficient by design, which is what makes CPU detection workable at all:

  • Object detection runs only on regions where motion was already detected, not on every frame.
  • Motion detection runs on the low-resolution detect substream, not the full-quality stream.
  • Recording copies the camera's existing H.264 stream rather than re-encoding it, which is nearly free.

In practice, that adds up to the following guidance:

  • Standard_B2s (2 vCPU / 4 GiB RAM): 1 camera. Workable for a single camera, but RAM is tight for an NVR.
  • Standard_B2ms (2 vCPU / 8 GiB RAM): 1 to 2 cameras. This is the recommended default.
  • Standard_B4ms (4 vCPU / 16 GiB RAM): 3 to 4 cameras. This is the right choice for a small property.
  • Standard_B8ms (8 vCPU / 32 GiB RAM): 5 to 8 cameras. Returns diminish here, so consider hardware acceleration instead.

These figures assume the detect substream at 640x480 and 5 fps per camera, recording by stream copy, and no transcoding. They are guidance, not a guarantee. A busy driveway with constant motion costs far more detection time than a quiet back garden.

These things push you up a size, or past what CPU detection can sensibly do:

  • A detect stream larger than 640x480, or a detect fps above 5.
  • Enabling any transcoding rather than stream copy for recordings.
  • Continuous 24/7 recording on several cameras at once.
  • Optional extras such as face recognition, license plate recognition or semantic search, which are considerably heavier than plain object detection.

If you need more cameras than the guidance above suggests, CPU detection is the wrong tool, and you need a hardware accelerator. This image does not provide one. Upstream Frigate supports Google Coral TPUs, Intel/AMD GPUs and NVIDIA TensorRT, none of which is available in an image built for general-purpose Azure VM sizes.

11

Recording storage and retention

Recordings and snapshots are written to /var/lib/frigate/media on the OS disk. The directory is 0750 root:root, so it is not world-readable. nginx is configured with no root, alias or autoindex directive anywhere, so recordings are reachable only through Frigate, which is behind the gate.

sudo stat -c '%a %U:%G %n' /var/lib/frigate/media

Expected output:

750 root:root /var/lib/frigate/media

An NVR fills a disk faster than almost anything else you will run. A rough rule for a single 1080p H.264 camera at a typical 2 Mbps:

  • 2 Mbps is about 0.9 GB per hour, so roughly 21 GB per camera per day of continuous recording.
  • Motion-only recording typically costs a fraction of that. The fraction depends entirely on how busy the scene is.

The image therefore ships conservative retention defaults, so that adding a camera cannot silently fill your disk:

  • Continuous recording: off (record.continuous.days: 0).
  • Motion recording: 2 days.
  • Alerts and detections: 14 days.
  • Snapshots: 7 days.

Raise these deliberately, once you have sized storage. Check what you are actually using:

df -h /var/lib/frigate/media
sudo du -sh /var/lib/frigate/media 2>/dev/null || true

Adding a dedicated data disk

No data disk is baked into this image, on purpose. The right size depends entirely on your camera count, resolution and retention, so a fixed size captured into the image would be wrong for nearly everyone. To move recordings onto a dedicated disk, attach one and mount it:

# Run this on YOUR workstation, not on the appliance.
az vm create --help >/dev/null
# (az CLI is not installed on the VM itself)
az vm disk attach --resource-group my-resource-group --vm-name frigate-nvr \
  --name frigate-media --new --size-gb 512 --sku StandardSSD_LRS

Then, on the VM:

sudo systemctl stop frigate DEV=/dev/disk/azure/scsi1/lun0
sudo mkfs.ext4 -F -L frigate-media "$DEV"
UUID=$(sudo blkid -s UUID -o value "$(readlink -f "$DEV")")
echo "UUID=$UUID /var/lib/frigate/media ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab
sudo mount /var/lib/frigate/media
sudo chown root:root /var/lib/frigate/media && sudo chmod 0750 /var/lib/frigate/media
sudo systemctl start frigate
12

What is in the image, and under what license

Frigate itself is MIT-licensed. This appliance ships the official upstream container image unmodified, pinned by digest. The build refuses to proceed unless the image's own version string identifies the exact upstream commit whose license tree was reviewed.

Two things deserve a specific call-out.

ffmpeg. Frigate bundles ffmpeg, and ffmpeg's license depends entirely on how it was compiled. The builds in this image are compiled --enable-gpl --enable-version3, which makes those binaries GPL-3.0 rather than the LGPL an unconfigured ffmpeg would be. Neither build is compiled --enable-nonfree, so both are freely redistributable.

The complete, verbatim configuration line of every bundled build is recorded on the VM:

grep -E '^ffmpeg version|^===' /usr/share/frigate/ffmpeg-configuration.txt
# `grep -c` exits 1 when the count is zero, which is the HEALTHY answer here.
echo "--enable-nonfree occurrences: $(grep -c -- '--enable-nonfree' /usr/share/frigate/ffmpeg-configuration.txt || true)"

Expected output (two builds, and zero nonfree):

=== /usr/lib/ffmpeg/5.0/bin/ffmpeg === ffmpeg version n5.1-2-g915ef932a3-20220731 Copyright (c) 2000-2022 the FFmpeg developers === /usr/lib/ffmpeg/7.0/bin/ffmpeg === ffmpeg version n7.0.2-18-g3e6cec1286-20240919 Copyright (c) 2000-2024 the FFmpeg developers --enable-nonfree occurrences: 0

Those builds include the libx264 (H.264) and libx265 (H.265/HEVC) encoders, which are patent-encumbered. Frigate's default recording path copies your camera's existing stream rather than re-encoding it, so a default deployment does not use them. If you enable transcoding, assessing your codec licensing position is your responsibility.

Detection models. Every bundled model is a Google or Intel Apache-2.0 asset. Nothing is research-only or non-commercial. The CPU detector this appliance uses is Google's ssdlite_mobiledet_coco.

The full resolved license inventory for all 959 Python and frontend dependencies ships on the VM:

grep -vc '^#' /usr/share/frigate/dependency-licences.txt awk -F'\t' '!/^#/{print $4}' /usr/share/frigate/dependency-licences.txt | sort | uniq -c | sort -rn | head -6

The complete notices, including the trademark position, are here:

sudo head -20 /usr/share/frigate/THIRD-PARTY-NOTICES

Day-to-day operation

sudo systemctl restart frigate
# restart after a configuration change
sudo systemctl status frigate
# check the service
sudo journalctl -u frigate -n 50
# recent logs

Frigate's own logs are also visible in the interface under System > Logs.

To change the Frigate admin password, use Settings > Users in the interface. To change the SecureAnyCloud gate password, run:

# Verify the command shape against a scratch file first, so a typo cannot lock
# you out of your own appliance:
sudo htpasswd -bBc /tmp/frigate-gate-example frigate 'replace-with-your-own-password'
sudo rm -f /tmp/frigate-gate-example

When the result is what you want, apply the change and reload the gate:

sudo htpasswd -bB /etc/nginx/frigate.htpasswd frigate 'your-new-strong-password'
sudo systemctl reload nginx

Security hardening

  • Restrict port 80 to your own network. This is a camera system. The gate is strong, but the smallest attack surface is the one nobody can reach.
  • Put TLS in front. The appliance speaks plain HTTP by design, so it works on a bare IP. For anything beyond a private network, terminate TLS with a certificate for a real hostname.
  • Rotate both credentials after your first sign-in and store them in a password manager.
  • Keep camera passwords out of config.yml by using environment substitution.
  • Unattended security upgrades are enabled, so the OS and nginx receive later security updates.

Troubleshooting

The browser asks for a password twice. That is correct. The first prompt is the SecureAnyCloud gate, and the second is Frigate's own sign-in. They are different credentials, and both are in /root/frigate-credentials.txt.

Everything returns 401, including with the right password. Confirm that nginx started with sudo systemctl status nginx. If nginx refused to start, the preflight guard blocked it. Check sudo journalctl -u nginx -n 30 for a frigate-preflight message that names what is missing.

Frigate says Safe Mode. Your config.yml failed validation, and Frigate fell back to a minimal configuration. The banner names the offending line. A common cause is copying a configuration written for Frigate 0.16. record.retain was removed in 0.17 and split into record.continuous and record.motion.

A camera will not connect. Test the RTSP URL from the VM itself before you suspect Frigate:

# ffprobe is NOT on PATH inside the Frigate container — use its absolute path.
# Confirm the tool is where it should be:
sudo docker exec frigate /usr/lib/ffmpeg/7.0/bin/ffprobe -version | head -1

Then probe your own camera with that same absolute path. A working camera prints its codec and resolution. A bad URL, wrong credential or unreachable camera prints a connection error instead:

sudo docker exec frigate /usr/lib/ffmpeg/7.0/bin/ffprobe -v error \
  -show_entries stream=codec_name,width,height \
  -of default=noprint_wrappers=1 "rtsp://USER:PASSWORD@CAMERA-IP:554/substream"

Detection is slow or the CPU is pinned. Check that the detect stream is the small substream and not the main stream. Check that detect.fps is 5 rather than the camera's native rate. If the settings are already minimal, you have reached the limit of CPU detection for that camera count. See Step 10.

The disk is filling. Lower retention in config.yml, or attach a data disk as described in Step 11.

Support

This image is published and supported by SecureAnyCloud. For help with the image, the deployment guide or the appliance configuration, contact SecureAnyCloud support. For questions about Frigate itself, use the upstream project's documentation at docs.frigate.video and its community.

Deploy on Azure

Launch Frigate NVR on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure